A user on the Expression Blend/SketchFlow forums asked a question about how to navigate between pages in xaml.
There are a number of solutions to this problem, including using a hyperlink item inside an element such as a textblock.
<TextBlock>
<Hyperlink NavigateUri="Page3.xaml">
<Run Text="Page 3"/>
</Hyperlink>
</TextBlock>
However, this will only work when contained within a Page element, and won’t work if you want to have navigation UI in the same page containing a frame. To enable both of these scenarios in an easy to use way, I wrote a simple NavigationAction behavior that will navigate to a Page from either inside a Page, or from an element that is inside the same screen as a Frame element.
The Action created below may be attached to any item inside a page, or any item on a screen that has a Frame object being used to navigate to pages. Set the PageProperty to the filename of the page to be shown.
- To use this behavior in Blend, add a new Action item to your project, and replicate the code provided below in your action.
- Build your project.
- In the asset panel, find your new Action, and drag it onto an element on the artboard.
- Select the behavior in the Objects and Timeline panel
- Open the properties panel
- Set the Page property to the page you want loaded
- Adjust the trigger event to the event you would like, most likely MouseLeftButtonUp or Down
Source code and example usage are provided below.
The FindChild method is a modified version of code posted here: http://stackoverflow.com/questions/636383/wpf-ways-to-find-controls
public class NavigateAction : TriggerAction<DependencyObject>
{
public static readonly DependencyProperty PageProperty = DependencyProperty.Register("Page", typeof(string), typeof(NavigateAction), new PropertyMetadata(null));
public string Page
{
get { return GetValue(PageProperty) as string; }
set { SetValue(PageProperty, value); }
}
private NavigationService navigationService;
public NavigateAction()
{
NavigationWindow navigationWindow = Application.Current.MainWindow as NavigationWindow;
if (navigationWindow != null)
{
this.navigationService = navigationWindow.NavigationService;
}
}
protected override void Invoke(object o)
{
if (this.navigationService == null)
{
// Try to find a frame in the main window.
Frame frame = FindChild<Frame>(Application.Current.MainWindow);
if (frame != null)
{
this.navigationService = frame.NavigationService;
}
}
if (this.navigationService != null)
{
this.navigationService.Navigate(new Uri(this.Page, UriKind.Relative));
}
}
public static T FindChild<T>(DependencyObject parent) where T : DependencyObject
{
if (parent == null) return null;
T foundChild = null;
int childrenCount = VisualTreeHelper.GetChildrenCount(parent);
for (int i = 0; i < childrenCount; i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
T childType = child as T;
if (childType == null)
{
foundChild = FindChild<T>(child);
if (foundChild != null)
{
return foundChild;
}
}
else
{
return (T)child;
}
}
return foundChild;
}
}
}
Using the behavior on siblings of a Frame element:
<Window xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:local="clr-namespace:WpfApplication21" x:Class="WpfApplication21.MainWindow" x:Name="Window" Title="MainWindow" Width="640" Height="480"> <Grid> <Grid.RowDefinitions> <RowDefinition Height="0.925*"/> <RowDefinition Height="0.075*"/> </Grid.RowDefinitions> <Frame x:Name="MainFrame" Content="" Source="Pages\Page1.xaml" Margin="0,0,0,4"/> <StackPanel Grid.Row="1" Orientation="Horizontal"> <Ellipse Fill="Red" Stroke="Black" Width="100"> <i:Interaction.Triggers> <i:EventTrigger EventName="MouseLeftButtonUp"> <local:NavigateAction Page="Pages/Page1.xaml"/> </i:EventTrigger> </i:Interaction.Triggers> </Ellipse> <Ellipse Fill="#FF00FF1C" Stroke="Black" Width="100"> <i:Interaction.Triggers> <i:EventTrigger EventName="MouseLeftButtonUp"> <local:NavigateAction Page="Pages/Page2.xaml"/> </i:EventTrigger> </i:Interaction.Triggers> </Ellipse> <Ellipse Fill="#FF0BC6CD" Stroke="Black" Width="100"> <i:Interaction.Triggers> <i:EventTrigger EventName="MouseLeftButtonUp"> <local:NavigateAction Page="Pages/Page3.xaml"/> </i:EventTrigger> </i:Interaction.Triggers> </Ellipse> </StackPanel> </Grid> </Window>
Using the behavior on items within a page:
<Page xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:i="clr-namespace:System.Windows.Interactivity;assembly=System.Windows.Interactivity" xmlns:local="clr-namespace:WpfApplication21" x:Class="WpfApplication21.Page1" x:Name="Page" WindowTitle="Page" FlowDirection="LeftToRight" Width="640" Height="480" WindowWidth="640" WindowHeight="480"> <Grid x:Name="LayoutRoot"> <StackPanel> <TextBlock Text="Page 1" TextWrapping="Wrap"> <i:Interaction.Triggers> <i:EventTrigger EventName="MouseLeftButtonUp"> <local:NavigateAction Page="Pages/Page1.xaml"/> </i:EventTrigger> </i:Interaction.Triggers> </TextBlock> <TextBlock Text="Page 2" TextWrapping="Wrap"> <i:Interaction.Triggers> <i:EventTrigger EventName="MouseLeftButtonUp"> <local:NavigateAction Page="Pages/Page2.xaml"/> </i:EventTrigger> </i:Interaction.Triggers> </TextBlock> <TextBlock HorizontalAlignment="Left" Text="Page 3" TextWrapping="Wrap"> <i:Interaction.Triggers> <i:EventTrigger EventName="MouseLeftButtonUp"> <local:NavigateAction Page="Pages/Page3.xaml"/> </i:EventTrigger> </i:Interaction.Triggers> </TextBlock> </StackPanel> </Grid> </Page>
September 8th, 2010 on 8:23 pm
I want to thank you for sharing this technque. It is great piece of code. I am wondering what will be the best approach to utilize this NaigateAction with listbox item selected state.
In my model I use listbox as links menu. Each listbox item has unique UriSource assigned through XML. I also created ‘back’ and ‘forward’ type of navigation to browse through the same content based on the browsing history. I load content into frame and accessing the browsing history with NavigationCommands.BrowseBack and BrowseForward Properties. It is working great. However, I need to indicate a selected state of the listbox item related to the content shown in the frame while browsing with Back/Forward buttons. What is the correct way to bind selected state of the listitem in this case? Thank you in advance.