My purchasing application has a main page that contains a frame. Users can navigate pages in that frame. I provide a back button that allows them to navigate back. I want to put a tooltip on that button that lets them know what they are going to navigate back to. This is more difficult than I had expected.
I solved the problem by populating and reading the Name property of the Navigation.JournalEntry. The Name property is automatically populated by the Framework as you navigate according to the following rules.
- The attached Name attribute.
- Title.
- WindowTitle and the uniform resource identifier (URI) for the current page
- The uniform resource identifier (URI) for the current page
<Page
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
JournalEntry.Name="JournalEntry Name"
Title="Title"
>
<!--Page Content-->
</Page>
But if you want to make the tooltip dynamic it is easiest to bind to a property. Lets walk through the process of doing this. We will create a project in Visual Studio 2015 that has a main page, an "EnterName" page where the user enters their name and navigates to a second page called "Hello". The second page will show a back button with a tool tip that shows the title from the prior page.
I will use a modified version of MVVM to build the project.
Start a new WPF application in Visual Basic (File -> New -> Project) and call it BackButtonToolTip.
We will have a MainWindow.xaml and xaml.vb. Replace MainWindow.xaml with this XAML which defines a Title textblock for the application, a back button, and a frame.
<Window x:Class="MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.CommandBindings>
<CommandBinding Command="PreviousPage" CanExecute="PreviousPage_CanExecute" Executed="PreviousPage_Executed"></CommandBinding>
</Window.CommandBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0">
<TextBlock Margin="10,0,10,0" FontSize="20" VerticalAlignment="Center" Text="{Binding MyTitle}"/>
<Button Name="PreviousButton" Command="PreviousPage" VerticalAlignment="Center" Cursor="Hand" ToolTip="{Binding Path=PreviousButtonToolTip}">
<Button.Template>
<ControlTemplate>
<TextBlock FontFamily="Webdings" Text="3" FontSize="24">
<TextBlock.RenderTransform>
<ScaleTransform ScaleX="1.5"/>
</TextBlock.RenderTransform>
</TextBlock>
</ControlTemplate>
</Button.Template>
<Button.Style>
<Style TargetType="Button">
<Setter Property="Visibility" Value="Visible"></Setter>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Visibility" Value="Collapsed"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
<Frame Name="PageFrame" Grid.Row="1" NavigationUIVisibility="Hidden"></Frame>
</Grid>
</Window>
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Height="350" Width="525"
DataContext="{Binding RelativeSource={RelativeSource Self}}">
<Window.CommandBindings>
<CommandBinding Command="PreviousPage" CanExecute="PreviousPage_CanExecute" Executed="PreviousPage_Executed"></CommandBinding>
</Window.CommandBindings>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"></RowDefinition>
<RowDefinition Height="*"></RowDefinition>
</Grid.RowDefinitions>
<StackPanel Orientation="Horizontal" Grid.Row="0">
<TextBlock Margin="10,0,10,0" FontSize="20" VerticalAlignment="Center" Text="{Binding MyTitle}"/>
<Button Name="PreviousButton" Command="PreviousPage" VerticalAlignment="Center" Cursor="Hand" ToolTip="{Binding Path=PreviousButtonToolTip}">
<Button.Template>
<ControlTemplate>
<TextBlock FontFamily="Webdings" Text="3" FontSize="24">
<TextBlock.RenderTransform>
<ScaleTransform ScaleX="1.5"/>
</TextBlock.RenderTransform>
</TextBlock>
</ControlTemplate>
</Button.Template>
<Button.Style>
<Style TargetType="Button">
<Setter Property="Visibility" Value="Visible"></Setter>
<Style.Triggers>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Visibility" Value="Collapsed"></Setter>
</Trigger>
</Style.Triggers>
</Style>
</Button.Style>
</Button>
</StackPanel>
<Frame Name="PageFrame" Grid.Row="1" NavigationUIVisibility="Hidden"></Frame>
</Grid>
</Window>
Now we need to put a couple of dummy event handlers in our code behind to make it look like this...
Class MainWindow
Private Sub PreviousPage_CanExecute(sender As System.Object, e As System.Windows.Input.CanExecuteRoutedEventArgs)
End Sub
Private Sub PreviousPage_Executed(sender As System.Object, e As System.Windows.Input.ExecutedRoutedEventArgs)
End Sub
End Class
Now we have an EnterName we can reference it in the Frame tag of the MainWindow. Change it to look like this...
<Frame Name="PageFrame" Grid.Row="1" NavigationUIVisibility="Hidden" Source="EnterName.xaml"></Frame>
The EnterName page will have a prompt, a textbox, and a navigation button. I altered the background so you can see the navigation more clearly. Note the use of "KeepAlive". You need this to persist your page's properties when you navigate back to it. When the project is complete, check out the effect of setting this false. Note the line Title="{Binding PageTitle}". By modifying the PageTitle property we can control the Name property of the NavigationJournalEntry object that defines this page in the Frame's BackStack, because it will be populated from the Title property of this page. Don't worry, it all becomes clearer later.
Here is your XAML for EnterName.xaml...
<Page x:Class="EnterName"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-Namespace:BackButtonToolTip"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
DataContext="{Binding RelativeSource={RelativeSource self}}"
Background="AliceBlue"
KeepAlive="true"
Title="{Binding PageTitle}">
Title="{Binding PageTitle}">
<Page.CommandBindings>
<CommandBinding Command="NextPage" CanExecute="NextPage_CanExecute" Executed="NextPage_Executed"></CommandBinding>
</Page.CommandBindings>
<Grid Height="30">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"></ColumnDefinition>
<ColumnDefinition Width="80"></ColumnDefinition>
<ColumnDefinition Width="auto"></ColumnDefinition>
</Grid.ColumnDefinitions>
<TextBlock Grid.Column="0" Text="Enter Name:" FontSize="18"/>
<TextBox Grid.Column="1" Text="{Binding UsersName, UpdateSourceTrigger=PropertyChanged}"></TextBox>
<Button Grid.Column="2" Command="NextPage" Content="Navigate"/>
</Grid>
</Page>
We need to create two more dummy event handlers in EnterName.xaml.vb.
Class EnterName
Private Sub NextPage_CanExecute(sender As Object, e As CanExecuteRoutedEventArgs)
End Sub
Private Sub NextPage_Executed(sender As Object, e As ExecutedRoutedEventArgs)
End Sub
End Class
Now when we run the application we can see something. Not much, but something.
Now we can build the backing store for the textbox, add the INotifyPropertyChanged code, and wire up the [Navigate] button. Change the EnterName.xaml.vb to look like this...
Imports System.ComponentModel
Class EnterName
Implements INotifyPropertyChanged
Private _UsersName As String = ""
Public Property UsersName As String
Get
Return _UsersName
End Get
Set(value As String)
If _UsersName <> value Then
_UsersName = value
NotifyPropertyChanged("UsersName")
PageTitle = "Back to 'Enter Users Name (" & _UsersName & ")'"
PageTitle = "Back to 'Enter Users Name (" & _UsersName & ")'"
End If
End Set
End Property
Private _PageTitle As String = ""
Public Property PageTitle As String
Get
Return _PageTitle
End Get
Set(value As String)
If _PageTitle <> value Then
_PageTitle = value
NotifyPropertyChanged("PageTitle")
End If
End Set
End Property
Private Sub NextPage_CanExecute(sender As Object, e As CanExecuteRoutedEventArgs)
Public Property PageTitle As String
Get
Return _PageTitle
End Get
Set(value As String)
If _PageTitle <> value Then
_PageTitle = value
NotifyPropertyChanged("PageTitle")
End If
End Set
End Property
Private Sub NextPage_CanExecute(sender As Object, e As CanExecuteRoutedEventArgs)
e.CanExecute = (UsersName.Length > 0)
End Sub
Private Sub NextPage_Executed(sender As Object, e As ExecutedRoutedEventArgs)
Dim Frame As Frame = DirectCast(Application.Current.MainWindow.FindName("PageFrame"), Frame)
Frame.Navigate(New Hello(UsersName))
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(PropertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(PropertyName))
End Sub
Private Sub EnterName_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
DirectCast(Application.Current.MainWindow, MainWindow).MyTitle = "Get Name"
End Sub
End Class
The NextPage_CanExecute event handler has been modified to only allow navigation if the user has entered something in the UsersName textbox.
The NextPage_Executed event handler has been modified to navigate to a different page. The act of navigation puts the current page on the Frame's BackStack where it can be interrogated later.
We add the standard get/set handlers for our two properties but we also modify the PageTitle property whenever the user modifies the UsersName property.
At this point we have some errors because we have not yet defined our Hello page or the MyTitle property on the MainWindow. Don't panic.
The EnterName_Loaded event handler has been added. It simply reaches to the application's main window and updates a property. We need to enhance MainPage to define and use that property. Modify MainWindow.xaml.vb to look like this...
Imports System.ComponentModel
Class MainWindow
Implements INotifyPropertyChanged
Private _MyTitle As String = ""
Public Property MyTitle As String
Get
Return _MyTitle
End Get
Set(value As String)
If _MyTitle <> value Then
_MyTitle = value
NotifyPropertyChanged("MyTitle")
End If
End Set
End Property
Private _PreviousButtonToolTip As String = ""
Public Property PreviousButtonToolTip As String
Get
Return _PreviousButtonToolTip
End Get
Set(value As String)
If _PreviousButtonToolTip <> value Then
_PreviousButtonToolTip = value
NotifyPropertyChanged("PreviousButtonToolTip")
End If
End Set
End Property
Private Sub PreviousPage_CanExecute(sender As System.Object, e As System.Windows.Input.CanExecuteRoutedEventArgs)
End Sub
Private Sub PreviousPage_Executed(sender As System.Object, e As System.Windows.Input.ExecutedRoutedEventArgs)
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(PropertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(PropertyName))
End Sub
End Class
Before we can continue we need to write the Hello page that we will navigate to (you need at least two pages to demonstrate navigation).
Add another page and call it "Hello". It will display the user name entered in the EnterName page. The XAML looks like this...
<Page x:Class="Hello"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:BackButtonToolTip"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300"
DataContext="{Binding RelativeSource={RelativeSource self}}"
Background="AntiqueWhite">
<StackPanel Orientation="Horizontal">
<TextBlock Text="Hello "/>
<TextBlock Text="{Binding UsersName}"></TextBlock>
</StackPanel>
</Page>
The code behind is fairly trivial too...
Imports System.ComponentModel
Class Hello
Implements INotifyPropertyChanged
Private _UsersName As String = ""
Public Property UsersName As String
Get
Return _UsersName
End Get
Set(value As String)
If _UsersName <> value Then
_UsersName = value
NotifyPropertyChanged("UsersName")
End If
End Set
End Property
Public Sub New(UsersName As String)
InitializeComponent()
Me.UsersName = UsersName
End Sub
Public Event PropertyChanged As PropertyChangedEventHandler Implements INotifyPropertyChanged.PropertyChanged
Private Sub NotifyPropertyChanged(PropertyName As String)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(PropertyName))
End Sub
Private Sub Hello_Loaded(sender As Object, e As RoutedEventArgs) Handles Me.Loaded
DirectCast(Application.Current.MainWindow, MainWindow).MyTitle = "Hello"
End Sub
End Class
Note the Hello_Loaded event handler reaches through to the parent page and updates the title property to "Hello". As this is something every page in our application would do, I would refactor it into a function, but I'll let you do that. The Hello page also has a different background color.
If we run the application now we will be able to enter a name and navigate to the "Hello" page.
The back button is still not visible because it's visibility is bound to its enabled state and we haven't told it when can be executed. Let's do that now. Modify the PreviousPage event handlers in MainWindow to look like this...
Private Sub PreviousPage_CanExecute(sender As System.Object, e As System.Windows.Input.CanExecuteRoutedEventArgs)
If PageFrame IsNot Nothing AndAlso PageFrame.NavigationService.CanGoBack() Then
e.CanExecute = True
PreviousButtonToolTip = PageFrame.BackStack.Cast(Of Navigation.JournalEntry).FirstOrDefault.Name
End If
End Sub
Private Sub PreviousPage_Executed(sender As System.Object, e As System.Windows.Input.ExecutedRoutedEventArgs)
If PageFrame.NavigationService.CanGoBack Then
PageFrame.NavigationService.GoBack()
End If
End Sub
What we did here is to enable the back button (and consequently make it visible) whenever the frame has a page to go back to. We also find the first element of the BackStack and pull the name from it to assign to the tooltip. When the back button is clicked we execute GoBack.
Now we can see the entire functionality of the solution.
No comments:
Post a Comment