Prism for Silverlight/MEF in Easy Samples. Part 2 - Prism Navigation
Download PrismTutorialSamples_PART2.zip - 285.46 KB
Introduction
This is a continuation of "Prism for Silverlight/MEF in Easy Samples Tutorial Part1" tutorial.
In this part, I cover Prism Region Navigation, which allows changing a view displayed or active within a Prism Region. (For region definition and samples look at the first part of this tutorial).
Region navigation is essential for properly using the "real estate" of the application by changing the views displayed at different locations within the application screen depending on the current state of the application.
Region Navigation functionality within Prism allows loading a view within a region based on the view's class name, passing navigation parameters, making decisions whether the view should be displayed or not, cancelling the navigation if needed and recording the navigation operations.
On top of the prerequisites listed for "Part 1" (C#, MEF and Silverlight), one also needs to understand the MVVM pattern for some of the samples in "Part 2".
Region Navigation Overview and Samples
Simple Region Navigation Sample
This sample's code is located under SimpleRegionNavigation.sln solution. It demonstrates basic navigation functionality. Module1 of the sample application has two views: Module1View1 and Module1View2. Both views are registered with "MyRegion1" region within the Module1Impl.Initialize
method (view discovery is used to associate these views with the region):
public void Initialize() { TheRegionManager.RegisterViewWithRegion ( "MyRegion1", typeof(Module1View1) ); TheRegionManager.RegisterViewWithRegion ( "MyRegion1", typeof(Module1View2) ); }
Both views display a message (a TextBlock
specifying the name of the view) and a button to switch to the other view. The foreground of Module1View1
is red, while the foreground of Module1View2
is green.
There are two ways to navigate to a view within a region:
- By using
RegionManager's
RequestNavigate method. - By using
Region's
RequestNavigate method.
RegionManager.RequestNavigate
function requires region name as its first parameter, while Region's
method does not require it.
To demonstrate both methods, Module1View1 navigates to Module1View2 using RegionManager.RequestNavigate
function:
TheRegionManager.RequestNavigate ( "MyRegion1", new Uri("Module1View2", UriKind.Relative), PostNavigationCallback );
while Module1View2 navigates to Module1View1 using Region.RequestNavigate
:
_region1.RequestNavigate ( new Uri("Module1View1", UriKind.Relative), PostNavigationCallback );
One can see that both methods require a relative Uri argument, whose string matches the name of the view we want to navigate to.
Both methods also need a post-navigation callback as their last argument. This is because some navigation implementations might be asynchronous so, if you want some code for sure to be executed after the end of the navigation you put it within the post-navigation callback. In our case we want to display a modular MessageBox stating whether the navigation was successful or not:
void PostNavigationCallback(NavigationResult navigationResult) { if (navigationResult.Result == true) MessageBox.Show("Navigation Successful"); else MessageBox.Show("Navigation Failed"); }
Many times, however, the post-navigation callback is not needed, and then the following simple lambda can be placed in its stead:
a => { }
Up to now we've covered the code behind for Module1View1
view located within Module1View1.xaml.cs file. Module1View2
functionality, however, is more complex. We can see, that Module1View2
class implements IConfirmNavigationRequest
interface. IConfirmNavigationRequest
extends INavigationAware
interface.
INavigationAware
interface has three methods:
bool IsNavigationTarget(NavigationContext navigationContext); void OnNavigatedFrom(NavigationContext navigationContext); void OnNavigatedTo(NavigationContext navigationContext);
IsNavigationTarget
method allows the navigation target to declare that it does not want to be navigated to, by returning "false". This method is most useful with injected views as will be shown below.
OnNavigatedFrom
method is called before one navigates away from a view. It allows doing any pre-navigation processing within the view that has been navigated from. In case of Module1View2
we show a message box:
public void OnNavigatedFrom(NavigationContext navigationContext) { MessageBox.Show("We are within Module1View2.OnNavigatedFrom"); }
OnNavigatedTo
method is called after one navigates to the view. It allows doing any post-navigation processing within the view that has been navigated to. In case of Module1View2
a message box is shown:
public void OnNavigatedTo(NavigationContext navigationContext) { MessageBox.Show("We are within Module1View2.OnNavigatedTo"); }
The above methods IsNavigationTarget
, OnNavigatedFrom
and OnNavigatedTo
are part of INavigationAware
interface (which IConfirmNavigationRequest
extends) and can be used if the view class implements INavigationAware
only. The method ConfirmNavigationRequest
, however, is only part of IConfirmNavigationRequest
and in order to use it one needs to implement IConfirmNavigationRequest
interface.
ConfirmNavigationRequest
method is called before one navigates from the current view. It gives you a last chance to cancel the navigation. Its second argument is a delegate called "continuationCallback" that takes a Boolean value as its argument. Calling continuationCallback(true)
will allow the navigation to continue, while calling continuationCallback(false)
will cancel it.
Within Module1View2
we show a message box, asking whether the user wants to continue navigation or cancel it and the argument to "continuationCallback" is determined by whether the user pressed OK or Cancel button:
public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback) { MessageBoxResult messageBoxResult = MessageBox.Show("Should Navigate from Current View?", "Navigate", MessageBoxButton.OKCancel); bool shouldNavigateFromCurrentView = messageBoxResult.HasFlag(MessageBoxResult.OK); continuationCallback(shouldNavigateFromCurrentView); }
Even though we use a modular window that blocks the control flow until the user clicks a button, non-modular control can also be used to continue or cancel the navigation - we simply need to pass the "continuationCallback" parameter to it and based on the user action it should either call continuationCallback(true)
or continuationCallback(false)
.
This is how the application looks when it is started:
Once "Navigate to Next View" button is clicked, we get a popup indicating that we are inside Module1View2.OnNavigatedTo
function. After OK is clicked, we are getting the post-navigate callback message indicating that the navigation has been successful. Once OK is clicked, the new view is shown on the screen:
If we click "Navigate to Next View" button now, we'll hit the Module1View2.ConfirmNavigationRequest
method first, which will show us a popup window asking whether to continue navigation or cancel it. If we press ok, navigation to Module1View1 continues successfully, otherwise it fails and we stay at Module1View2. (In case of successful navigation we get two more modular popups - one indicating that we are inside Module1View2.OnNavigatedFrom
function and one informing us that the navigation was successful, while in case of a navigation failure we are informed of it by a message popup)
Exercise: create a similar demo (look at "Prism for Silverlight/MEF in Easy Samples Tutorial Part1" for instructions on how to create projects for Silverlight Prism application and modules and how to load the module into the application using the bootstrapper).
Exercise: create a similar demo with the two views located in different modules instead of them being within the same module.
Simple Navigation via the View Model
In the above example, Module1View2
view implements IConfirmNavigationRequest
interface and because of that its functions IsNavigationTarget
, OnNavigatedFrom
, OnNavigatedTo
and ConfirmNavigationRequest
participate in the navigation process. A better way, however, is to make all the navigation decisions within the view model (if MVVM pattern is being followed). So, Prism provides a way for the view model to implement INavigationAware
or IConfirmNavigationRequest
interfaces (instead of the view).
The rule is the following: if the view implements one of INavigationAware
or IConfirmNavigationRequest
interfaces than the view's corresponding methods will be called during the navigation. If, however, the view does not implement it, but the view's DataContext does, then the corresponding functions of the DataContext will be involved as demonstrated by our sample under NavigationViaViewModel.sln solution.
Module1View1
is almost the same as in the previous sample (aside from the fact that it does not popup message windows).
Module1View2
does not implement IConfirmNavigationRequest
. Instead, within the constructor, it sets its DataContext
property to be a new object of View2Model
type:
// we are setting the DataContext property of // Module1View2 view View2Model view2Model = new View2Model(); DataContext = view2Model;
View2Model
is a non-visual (as a view model should be) class that implements IConfirmNavigationRequest
:
public class View2Model : IConfirmNavigationRequest { // we use this event to communicate to the view // that we are starting to navigate from it public event Func<bool> ShouldNavigateFromCurrentViewEvent; #region INavigationAware Members public bool IsNavigationTarget(NavigationContext navigationContext) { return true; } public void OnNavigatedTo(NavigationContext navigationContext) { } public void OnNavigatedFrom(NavigationContext navigationContext) { } #endregion #region IConfirmNavigationRequest Members public void ConfirmNavigationRequest(NavigationContext navigationContext, Action<bool> continuationCallback) { bool shouldNavigateFromCurrentViewFlag = false; if (ShouldNavigateFromCurrentViewEvent != null) shouldNavigateFromCurrentViewFlag = ShouldNavigateFromCurrentViewEvent(); continuationCallback(shouldNavigateFromCurrentViewFlag); } #endregion }
We use the view model's ShouldNavigateFromCurrentViewEvent
event to inform the view that navigation has been started away from it. The view connects a handler to this event within the view's constructor:
view2Model.ShouldNavigateFromCurrentViewEvent += new Func<bool>(view2Model_ShouldNavigateFromCurrentViewEvent);
Just like in the previous sample, View2Model.view2Model_ShouldNavigateFromCurrentViewEvent
displays a modular popup window asking whether the user wants to continue or cancel the navigation. Then, depending on the user choice it returns a Boolean to the view model and the View2Model.ConfirmNavigationRequest's
"continuationCallback" argument is called with the corresponding value specifying whether to continue or cancel the navigation.
Exercise: create a similar demo.
Navigation Parameters Sample
If a view or a view model implements INavigationAware
or IConfirmNavigationRequest
, interface, the navigation can pass parameters to these functions as part of the navigation Url. As I mentioned above, the Url should contain the name of the view class to which one wants to navigate. This name of the class can be followed by '?' character and key-value parameter pairs where the keys are separated from the values by '=' character and the pairs are separated from each other by '&' character.
NavigationParameters.sln solution contains a sample where the view Module1View1
navigates to itself but with different parameters.
Here is how the sample's window looks:
One can enter any text into the text box above, then press the button "Copy via Navigation" and the text will be copied into the text block below.
The "Copy via Navigation" has the following callback:
void NextViewButton_Click(object sender, RoutedEventArgs e) { if (TheTextToCopyTextBox.Text == null) TheTextToCopyTextBox.Text = ""; // navigate passing the paramter "TheText" TheRegionManager.RequestNavigate ( "MyRegion1", new Uri("Module1View1?TheText=" + TheTextToCopyTextBox.Text, UriKind.Relative), a => { } ); }
Note that the full Url will look like: "Module1View1?TheText=<text-to-copy>".
Module1View1
view implements INavigationAware
interface, so, its own OnNavigatedTo
function will be called at the end of the navigation process. Within this function we get the UriQuery dictionary from the navigationContext argument and from the UriQuery dictionary we get the value corresponging to "TheText" key as below:
public void OnNavigatedTo(NavigationContext navigationContext) { TheCopiedTextTextBlock.Text = navigationContext.UriQuery["TheText"]; }
Of course, in reality, RequestNavigate
and OnNavigatedTo
functions can be in different views and even in different modules, and instead of a trivial task of copying text, other much more complex tasks requiring navigation parameters might be employed.
Exercise: Create a similar demo.
Using IsNavigationTarget Method for Navigating to Injected Views
In case of the view injection there can be multiple views of the same view type within an application. Navigating based on the Url containing the View class name only, will not work in that case, since all those views have the same view class. Prism employs bool INavigationAware.IsNavigationTarget(NavigationContext)
method to specify which view to navigate to in such case.
When navigating to injected views, each of the region's views of the specified type has its INavigationAware.IsNavigationTarget
method called until one of them returns "true" (or until all of them return "false"). The view whose INavigationAware.IsNavigationTarget
method returns "true" will be navigated to.
The corresponding sample is located under InjectedViewNavigation.sln solution. Unlike in previous examples, the region control is represented by a ListBox
in the application's Shell.xaml file:
<ListBox HorizontalAlignment="Center"
VerticalAlignment="Center"
prism:RegionManager.RegionName="MyRegion1">
</ListBox>
so that it displays every view connected to the region and navigating to a view makes this view selected within the list box:
The view is defined by Module1View1
class. It has ViewID
property that uniquely specifies the view object. This class also has a static method CreateViews()
which creates 5 views with different ViewIDs ranging from 1 to 5 and associates the created views with "MyRegion1" region:
public static void CreateViews(IRegionManager regionManager) { IRegion region = regionManager.Regions["MyRegion1"]; for (int i = 1; i <= MAX_VIEW_ID; i++) { Module1View1 view = new Module1View1 { ViewID = i }; view.TheRegionManager = regionManager; region.Add(view); } }
Each view has a button to navigate to the next view. The button click has the following event handler:
void NextViewButton_Click(object sender, RoutedEventArgs e) { NextViewButton.IsEnabled = false; TheRegionManager.RequestNavigate ( "MyRegion1", new Uri("Module1View1?ViewID=" + NextViewID, UriKind.Relative), a => { } ); }
As you can see, we navigate to the "Module1View1" view passing "ViewID" parameter set to point to a value returned by NextViewID
property.
NextViewID
returns the ViewID of the current view plus one unless it is the last view in the set - then it goes back to ViewID=1:
int NextViewID { get { if (ViewID < Module1View1.MAX_VIEW_ID) return ViewID + 1; return 1; } }
IsNavigationTarget
method compares the "ViewID" parameter received as part of the Navigation Url to the ViewID
property of the view. If they are the same, it returns true (this means we navigate to this view), if they are different, it returns false (the view is not navigated to):
public bool IsNavigationTarget(NavigationContext navigationContext) { string viewIDString = navigationContext.UriQuery["ViewID"]; int viewID = Int32.Parse(viewIDString); bool canNavigateToThisView = (viewID == ViewID); return canNavigateToThisView; }
One can see that by pushing the view's "Navigate to Next View" button, we indeed switch to the next view. After changing NextViewID
property to return every second view, we can see that every second view in the list gets selected. If we change the region control within Shell.xaml file of the application project to be a ContentControl
instead of a ListBox
the navigation will be displaying the navigated view instead of selecting it.
Exercise: Create a similar demo.
Undo-Redo Functionality Using Navigation Journal
Navigation functionality also includes undo-redo capabilities as shown in this sample. The sample is located under NavigationJournal.sln solution.
This sample is very similar to the previous one, only each injected view has two more buttons: "Back" button and "Forward" button. "Back" button allows undoing the last operation, while "Forward" - redoing the last undone operation:
"Back" and "Forward" buttons are enabled only when the corresponding operations are possible.
Undo-redo functionality is available from RegionNavigationJournal, which in turn can be accessed via RegionNavigationService. We can get a reference to the navigation service from the navigationContext argument within OnNavigatedTo
method:
public void OnNavigatedTo(NavigationContext navigationContext) { // getting a reference to navigation service: if (_navigationService == null) _navigationService = navigationContext.NavigationService; ... }
Here is how "Back" button hooks to the corresponding Undo functionality:
void BackButton_Click(object sender, RoutedEventArgs e) { _navigationService.Journal.GoBack(); ... }
"Forward" button's handler is calling _navigationService.Journal.GoForward()
method instead.
Button enabling/disabling functionality is based on _navigationService.Journal.CanGoBack
and _navigationService.Journal.CanGoForward
properties for "Back" and "Forward" buttons correspondingly. It seems that it is logical to add button enabling/disabling functionality to the body of OnNavigatedTo
method (which is called on Undo-Redo), but unfortunately it is called before the navigation Journal state is updated, so that the CanGoBack
and CanGoForward
properties do not have the correct values yet. In order to get around this problem, I had to figure out which view is current based on the Uri
property of the current entry within the Journal. We get the ViewID from the Uri and set IsEnabled
properties on the "Back" and "Forward" buttons of the corresponding view:
void UpdateIfCurrentView() { if (_navigationService == null) return; string uriStr = _navigationService.Journal.CurrentEntry.Uri.OriginalString; int lastEqualIdx = uriStr.LastIndexOf('='); string viewIDStr = uriStr.Substring(lastEqualIdx + 1); int viewID = Int32.Parse(viewIDStr); if (viewID != this.ViewID) return; BackButton.IsEnabled = _navigationService.Journal.CanGoBack; ForwardButton.IsEnabled = _navigationService.Journal.CanGoForward; }
Method UpdateIfCurrentView
is set as the static JournalUpdatedEvent
event handler for each of the created views:
public Module1View1()
{
InitializeComponent();
...
JournalUpdatedEvent += UpdateIfCurrentView;
...
}
"Back" and "Forward" button click handlers call JournalUpdatedEvent
after calling _navigationService.Journal.GoBack()
or _navigationService.Journal.GoForward()
methods:
void BackButton_Click(object sender, RoutedEventArgs e) { _navigationService.Journal.GoBack(); DisableButtons(); JournalUpdatedEvent(); } void ForwardButton_Click(object sender, RoutedEventArgs e) { _navigationService.Journal.GoForward(); DisableButtons(); JournalUpdatedEvent(); }
Important Note: the journal undo/redo functionality works only within one region. If there are multiple regions each one of them will have their own undo/redo sequence.
Exercise: create a similar demo.
Conclusion
In part 2 of this tutorial I gave a detailed description of Prism's Navigation functionality in small and simple samples. I'd appreciate very much if you could vote for this article and leave a comment. I would love to hear your suggestions for improving and expanding the content of this tutorial.
History
Feb 15, 2011 - changed the code to use latest Prism dlls (which lead to significant changes in API:INavigationAwareWithVeto
interface was replaced by IConfirmNavigationRequest
, CanNavigateTo()
was replaced by IsNavigationTarget()
and method OnNavigatedFrom
was added to INavigationAware
interface. Hat tip to jh1111 for noticing that my API was out of date.
Post Comment
Looking forward to reading more. Great article.Really looking forward to read more. Awesome.
Muchos Gracias for your blog.Much thanks again. Cool.
Im obliged for the blog post.Really looking forward to read more. Really Cool.
Nicely? to be Remarkable post and will look forward to your future update. Be sure to keep writing more great articles like this one.
Thanks so much for this, keep up the good work
Major thanks for the article post.Thanks Again. Will read on
You made some clear points there. I did a search on the subject and found most guys will approve with your site.
Looking forward to reading more. Great blog post.Thanks Again. Much obliged.
pretty handy material, overall I consider this is really worth a bookmark, thanks
Well I definitely liked reading it. This tip procured by you is very effective for accurate planning.
Thanks so much for the blog post.Really looking forward to read more. Will read on
This very blog is obviously educating and besides diverting. I have found a lot of handy stuff out of this amazing blog. I ad love to go back over and over again. Cheers!
I value the article post.Really thank you! Much obliged.
I think you have noted some very interesting points , thanks for the post.
Really appreciate you sharing this post.Really thank you! Will read on
minute but I have saved it and also added your RSS feeds, so when I have time I
I think other site proprietors should take this website as an model, very clean and excellent user genial style and design, let alone the content. You are an expert in this topic!
Really enjoyed this blog article.Much thanks again. Much obliged.
Received the letter. I agree to exchange the articles.
Say, you got a nice blog post.Really thank you! Fantastic.
Nice blog right here! Also your website loads up very fast! What web host are you the use of? Can I get your affiliate hyperlink for your host? I desire my website loaded up as fast as yours lol
yeah bookmaking this wasn at a speculative determination outstanding post!.
I will immediately snatch your rss feed as I can at in finding your e-mail subscription hyperlink or e-newsletter service. Do you have any? Kindly let me know in order that I could subscribe. Thanks.
Only wanna input that you have a very nice web site , I like the layout it really stands out.
4L26Vn PRADA OUTLET ONLINE ??????30????????????????5??????????????? | ????????
It as not that I want to replicate your web page, but I really like the pattern. Could you tell me which design are you using? Or was it tailor made?
whoah this blog is great i love reading your articles. Keep up the great work! You know, a lot of people are searching around for this info, you can help them greatly.
Im grateful for the article.Really thank you! Fantastic.
Im no expert, but I suppose you just crafted the best point. You undoubtedly understand what youre talking about, and I can really get behind that. Thanks for staying so upfront and so genuine.
Way cool! Some extremely valid points! I appreciate you writing this write-up and also the rest of the website is also very good.
I'аve recently started a blog, the information you offer on this site has helped me tremendously. Thank you for all of your time & work.
I truly appreciate this article post.Thanks Again. Want more.
What as up, just wanted to tell you, I loved this post. It was practical. Keep on posting!
Way cool! Some extremely valid points! I appreciate you writing this post plus the rest of the website is extremely good.
You must participate in a contest for top-of-the-line blogs on the web. I will advocate this website!
This is a really good tip especially to those new to the blogosphere. Short but very precise info Many thanks for sharing this one. A must read post!
Wow! Thank you! I constantly wanted to write on my website something like that. Can I include a fragment of your post to my blog?
pretty beneficial material, overall I think this is really worth a bookmark, thanks
Thanks for this wonderful post! It has been extremely useful. I wish that you will carry on sharing your knowledge with me.
Wow! Thank you! I constantly needed to write on my site something like that. Can I take a portion of your post to my site?
Thank you for sharing this good piece. Very inspiring! (as always, btw)
Thanks for the blog article.Really thank you! Really Cool.
This particular blog is obviously entertaining and also diverting. I have chosen helluva helpful advices out of this amazing blog. I ad love to come back over and over again. Thanks a bunch!
Muchos Gracias for your post.Really looking forward to read more. Much obliged.
This site was how do you say it? Relevant!! Finally I ave found something that helped me. Kudos!
Really enjoyed this blog.Really looking forward to read more. Keep writing.
online dating websites This actually answered my problem, thanks!
You should take part in a contest for probably the greatest blogs on the web. I will advocate this website!
This is my first time go to see at here and i am in fact happy to read all at single place.
wow, awesome blog article.Thanks Again. Really Great.