Friday, November 19, 2010

WPF - Passing Parameters to the Page loaded in the Child Frame

WPF supports frame based navigation. We might need to pass some arguments to the page loaded in the frame. In this post, we will be discussing how we can pass arguments to the page being loaded.

This seems to be very common scenario so it must have some common solution. We know that pages are navigated using Navigate() method of NavigationService of the frame. If we look at various overloads of Navigate(), a few overloads seems to be fulfilling our requirement. These overloads are as follows:

1 - NavigationService.Navigate Method (Object, Object)
2 - NavigationService.Navigate Method (Uri, Object)
3- NavigationService.Navigate Method (Uri, Object, Boolean)

The second argument of all the above three methods seems to be passing state to the page being loaded which seems to be available for the page being loaded.
"an object containing navigation state for processing during navigation"

Now we have no overload of Navigate which supports this. Since the problem is presented now, let us discuss one solution to pass parameters to the page being loaded. Let us look at the the lifetime of NavigationService.

1- Navigating
2- NavigationProgress
3- Navigated
4- LoadCompleted

Of these events, LoadCompleted seems to be the one we need. It also makes the argument, passed in NavigationState parameter of Navigate method, available in ExtraData property of NavigationEventArgs of NavigationService.LoadCompleted event. But the thing is that, NavigationService has not been instantiated and not available in the Page constructor. Additionally NavigationService.LoadCompleted event is raised before Loaded event of Page. So we can not subscribe to LoadCompleted in the Page. This timing issue makes our life difficult. Still we need to pass some arguments to the page, but how???



Let us add a page to our project. The XAML of page is as follows:

<Page x:Class="MultiBinding_DifferentVMs.PageNavigation"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
DataContext ="{Binding RelativeSource={RelativeSource Self}}"
Title="PageNavigation">
<Grid>
<TextBlock Height="43" Margin="12,94,11,0" x:Name="textBlock1" VerticalAlignment="Top"
Text="{Binding MessageFromCallingWindow}"/>
</Grid>
</Page>

So it expects its DataContext to have a property named MessageFromCallingWindow to bind to the Text property of the text block. To keep the example simple, the DataContext is itself. So we need to define a dependency property in the same page. The code behind of the page is as follows:

public partial class PageNavigation : Page
{
public static DependencyProperty MessageFromCallingWindowProperty =
DependencyProperty.Register("MessageFromCallingWindow", typeof(string), typeof(PageNavigation));

public string MessageFromCallingWindow
{
get { return (string)GetValue(MessageFromCallingWindowProperty); }
set { SetValue(MessageFromCallingWindowProperty, value); }
}

public PageNavigation()
{
InitializeComponent();
}
}

Now let us come to the window which would need to navigate to the above page through the frame hosted within. The good thing is that this window can subscribe a handler for the LoadCompleted event of the frame's navigation service.

<Window x:Class="MultiBinding_DifferentVMs.Window_Navigation"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Window_Navigation" Height="394" Width="501">
<Grid>
<Frame Margin="12,41,12,12" Name="frame1" />
<TextBox Height="27" Margin="0,8,168,0" Name="textBox1" VerticalAlignment="Top"
HorizontalAlignment="Right" Width="244" />
<Label Height="25" HorizontalAlignment="Left" Margin="8,10,0,0" Name="label1"
VerticalAlignment="Top" Width="62">Parameter</Label>
<Button Height="26" HorizontalAlignment="Right" Margin="0,9,55,0"
x:Name="btnNavigate" VerticalAlignment="Top" Width="107" Click="btnNavigate_Click">Navigate</Button>
</Grid>
</Window>

The code behind of the window is as follows:

public partial class Window_Navigation : Window
{
public Window_Navigation()
{
InitializeComponent();
}

private void btnNavigate_Click(object sender, RoutedEventArgs e)
{
this.frame1.NavigationService.Navigate(new Uri("PageNavigation.xaml", UriKind.Relative),
"Hi from calling window!");

this.frame1.NavigationService.LoadCompleted +=
new System.Windows.Navigation.LoadCompletedEventHandler(NavigationService_LoadCompleted);
}

void NavigationService_LoadCompleted(object sender, System.Windows.Navigation.NavigationEventArgs e)
{
((PageNavigation)e.Content).MessageFromCallingWindow = (string)e.ExtraData;
}
}

When we run the project, the view is displayed as follows:



Clicking the Navigate button navigates to the Page. As the page is loaded and being rendered, LoadCompleted event of navigation service of frame1 is raised. This copies the data passed in object state to the MessageFromCallingWindow property. As discussed above, this data was made available through ExtraData property of EventArgs.



Note:
If we need to pass some values back to the calling window then we can use similar approach of defining some event on the Page and subscribe the handler in the calling window.

If you are a silverlight developer, then you should also be looking at NavigationContext. This allows to pass values to the page through query parameters. You can have a look at here for this:

http://msdn.microsoft.com/en-us/library/system.windows.navigation.navigationcontext.querystring(VS.95).aspx

No comments: