-
Prism and Universal Windows apps – Messages
Another common scenario when you work with the MVVM pattern is messages support: all the available toolkits and frameworks support them. Messages are a way to exchange data between two classes (typically, two ViewModels) with a decoupled approach: the two classes won’t have to know each other and you won’t need a common reference. One ViewModel will simply send a message, then other ViewModels can register to receive that message’s type. Messages are, at the end, simple classes, which can hold some data that can be passed from one ViewModel to another.
In Prism, messages are named events: when a ViewModel needs to send some data to another one, it publishes an event; when a ViewModel wants to receive that data, it subscribes for that event. The “postman” that takes care of managing all the infrastructure and of dispatching the events is a class called EventAggregator, which is not included in the base Prism package: you’ll have to install from NuGet a specific package called Prism.PubSubEvents.
After you’ve installed it in both in the Windows and the Windows Phone project, you’ll have to register the EventAggregator object in the App class, in the same way we did in the previous posts for the NavigationService and the SessionStateService classes: this way, we’ll be able to use the EventAggregator class simply by adding an IEventAggregator parameter to the ViewModel’s constructor, thanks to the dependency injection. We register it in the OnInitializeAsync() method of the App class, like in the following sample:
protected override Task OnInitializeAsync(IActivatedEventArgs args) { // Register MvvmAppBase services with the container so that view models can take dependencies on them _container.RegisterInstance<ISessionStateService>(SessionStateService); _container.RegisterInstance<INavigationService>(NavigationService); _container.RegisterInstance<IEventAggregator>(new EventAggregator()); // Register any app specific types with the container // Set a factory for the ViewModelLocator to use the container to construct view models so their // dependencies get injected by the container ViewModelLocationProvider.SetDefaultViewModelFactory((viewModelType) => _container.Resolve(viewModelType)); return Task.FromResult<object>(null); }
To see how to use events in Prism, we’re going to implement a simple application to display a list of persons: the main page will contain a ListView control, that will display a list of Person objects. The page will contains also a button, in the application bar, to go to an insert page, with a simple form to add a new person to the collection. After the user has filled the name and surname of the user and he has pressed the Save button, we’re going to add the new item in the collection in the main page. We’re going to achieve this goal by using events: when the user presses the Save button, we’re going to send a message to the ViewModel of the main page with the just created person; in the ViewModel of the main page, instead, we’re going to subscribe to this event: when it’s triggered, we’re going to retrieve the new person and add it to the collection displayed in the page.
The first step to implement this scenario is to create a class that identifies our event, like in the following sample:
public class AddPersonEvent : PubSubEvent<Person> { }
As you can see, the class is very simple, since it doesn’t contain any property or constructor: the only requirement is to inherit it from the **PubSubEvent
** class, where **T** is the type of the object we want to pass inside the message. In this sample, we’re going to pass a **Person** object. The next step is to define the sender and the receiver of the message: in our case, the sender will be the ViewModel of the add page, while the receiver will be the ViewModel of the main page. Let’s start to see the ViewModel of the main page:
public class MainPageViewModel : ViewModel { private readonly INavigationService _navigationService; private readonly IEventAggregator _eventAggregator; private ObservableCollection<Person> _persons; public ObservableCollection<Person> Persons { get {return _persons;} set { SetProperty(ref _persons, value); } } public MainPageViewModel(INavigationService navigationService, IEventAggregator eventAggregator) { _navigationService = navigationService; _eventAggregator = eventAggregator; _eventAggregator.GetEvent<AddPersonEvent>().Subscribe(person => { if (Persons == null) { Persons = new ObservableCollection<Person>(); } Persons.Add(person); }, ThreadOption.UIThread); GoToAddPageCommand = new DelegateCommand(() => { _navigationService.Navigate("Add", null); }); } public DelegateCommand GoToAddPageCommand { get; private set; } }
You can notice that, other than an INavigationService parameter (which we already met in another post), we have added a reference to the IEventAggregator class, which we’re going to use to send and receive events. In this case, since we’re in the receiver ViewModel, we’re going to subscribe to the event we’ve previously defined: we do it in the ViewModel’s constructor. The first step is to get a reference to the event we want to manage, by using the **GetEvent
** method, where **T** is the event’s type (in our case, it’s the **AddPersonEvent** class we’ve previously created). Then, since in this case we want to receive it, we call the **Subscribe()** message, which accepts the action that we want to execute when the event is triggered. As action’s parameter, we get the content of the message (in our case, it’s a **Person** object): in the sample, we simply add the **Person** object we’ve received to a collection called **Persons**, which is connected to a **ListView** control in the page. Optionally, we can pass a second parameter to the **Subscribe()** method to specify in which thread we want to manage the event: in this case, since we’re updating a control in the View, we manage it in the UI thread (**ThreadOption.UIThread**). Otherwise, we could have used **ThreadOption.BackgroundThread** to manage it in background: this approach is useful if we need to execute CPU consuming operations that don’t need to interact with the View. The ViewModel defines also a DelegateCommand, which simply redirects the user to the Add page that simply contains a couple of TextBox controls and a button to save the data. Here is its definition:
<storeApps:VisualStateAwarePage x:Class="Prism_Messages.Views.AddPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Prism_Messages.Views" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:mvvm="using:Microsoft.Practices.Prism.Mvvm" xmlns:storeApps="using:Microsoft.Practices.Prism.StoreApps" mc:Ignorable="d" mvvm:ViewModelLocator.AutoWireViewModel="True" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid> <StackPanel Margin="12, 0, 0, 12"> <TextBox PlaceholderText="Name" Header="Name" Text="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> <TextBox PlaceholderText="Surname" Header="Surname" Text="{Binding Path=Surname, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> </StackPanel> </Grid> <Page.BottomAppBar> <CommandBar> <CommandBar.PrimaryCommands> <AppBarButton Label="Save" Icon="Save" Command="{Binding Path=SaveCommand}" /> </CommandBar.PrimaryCommands> </CommandBar> </Page.BottomAppBar> </storeApps:VisualStateAwarePage>
Let’s see now the most interesting part, which is the ViewModel of the Add page:
public class AddPageViewModel : ViewModel { private readonly INavigationService _navigationService; private readonly IEventAggregator _eventAggregator; private string _name; public string Name { get { return _name; } set { SetProperty(ref _name, value); } } private string _surname; public string Surname { get { return _surname; } set { SetProperty(ref _surname, value); } } public AddPageViewModel(INavigationService navigationService, IEventAggregator eventAggregator) { _navigationService = navigationService; _eventAggregator = eventAggregator; SaveCommand = new DelegateCommand(() => { Person person = new Person { Name = this.Name, Surname = this.Surname }; _eventAggregator.GetEvent<AddPersonEvent>().Publish(person); _navigationService.GoBack(); }); } public DelegateCommand SaveCommand { get; private set; } }
In this sample we see the other usage of the EventAggregator class, which is publishing an event: we use it in the SaveCommand, which is triggered when the user presses the Save button in the page. The first step, also in this case, is to get a reference to the event, by calling the **GetEvent
()** method. However, in this situation, we’re going to use the **Publish()** method, which sends the message: as parameter, we need to pass the data that is supported by the event (in our case, the **AddPersonEvent** supports a **Person**’s parameter). In the end, we call the **GoBack()** method of the **NavigationService**, to redirect the user back to the main page. If we launch the application, we’ll notice that, after pressing the Save button in the Add page, the user will be redirected to the main page and the just created Person object will be displayed in the list. If we set some breakpoints in the MainPageViewModel and in the AddPageViewModel classes, we’ll notice that the messages are successfully exchanged between the two ViewModels.
Wrapping up
As usual, you can download the sample project used in this post on GitHub at https://github.com/qmatteoq/Prism-UniversalSample
Index of the posts about Prism and Universal Windows apps
- The basic concepts
- Binding and commands
- Advanced commands
- Navigation
- Managing the application’s lifecycle
- Messages
- Layout management
Sample project: https://github.com/qmatteoq/Prism-UniversalSample
in
-
Prism and Universal Windows app – Managing the application’s lifecycle
Unless this is your first experience with Windows Store app development, you should be familiar with the concept of “application lifecycle”. Windows Store apps are created for scenarios where battery life, memory and CPU are not unlimited: consequently, the old desktop approach, where applications are able to keep running in background for an indefinite time, doesn’t fit well this new modern world, where PCs aren’t anymore the only device used to connect to the Internet, work, play, etc.
When a Windows Store isn’t in foreground anymore (because the user has returned to the Start screen, he has launched another application, he has tapped on a toast notification, etc.) it’s suspended after 10 seconds: the process is kept in memory, but all the running operations (network connections, thread, etc.) are stopped. The application will continue to use RAM memory, but it won’t be able to “steal” CPU power, network, etc. to the other applications. This way, every other application will be able to use the same resources of the previously opened ones. However, RAM memory isn’t infinite: consequently, when it’s running low, the operating system is able to terminate the older applications, to free some memory.
However, this termination should be transparent to the user: since he didn’t explicitly close the application (by using the task switcher in Windows Phone or by dragging the application from the top to the bottom in Windows, for example), he expects to find it in the same state he left. This is what we, as developers, call “managing the application’s state”: typically, when the application is suspended, we’re going to save in the local storage (which content is persisted across different usages) all the data that we need to recreate the impression that the app has never been closed (the last opened page, the content of a form, etc.). When the application starts, in case it was terminated due to low memory, we need to load this state and to restore the application in the previous state.
In a typical Universal Windows app, we perform this operation in two steps:
- The first one is to use the OnNavigatedTo() and OnNavigatedFrom() events exposed by the page to save and restore the page’s state.
- The second one is to use the various methods offered by the App class to manage the application’s lifecycle, like OnSuspended() (to save the state) and OnLaunched() (to restore the state, in case we detect that the app was terminated by the operating system).
However, Prism offers a simpler way to manage this scenario. Let’s see the details.
###
Saving simple data
The simplest scenario is when we need to save plain data, like a text in a TextBox or boolean in a CheckBox. These kind of properties can be automatically saved by Prism when the application is suspended and restored when it’s activated simply by marking them with the RestorableState attribute. Let’s say that you have a page with the following XAML:
<StackPanel Margin="12, 0, 12, 0"> <TextBox Text="{Binding Path=Name, Mode=TwoWay" /> <TextBox Text="{Binding Path=Surname, Mode=TwoWay}" Margin="0, 0, 0, 20 "/> </StackPanel>
We have added two TextBox controls, which are in binding (in two way mode) with two properties in the ViewModel, called Name and Surname. Let’s see how they’re defined:
public class MainPageViewModel : ViewModel { private string _name; [RestorableState] public string Name { get {return _name;} set { SetProperty(ref _name, value); } } private string _surname; [RestorableState] public string Surname { get { return _surname; } set { SetProperty(ref _surname, value); } } }
You can notice that we’re dealing with two standard properties that, thanks to SetProperty() method offered by Prism, are able to notify the View every time their value changes. However, you can also notice that we’ve decorated the public properties with the [RestorableState] attribute. This is enough to enable the automatic state management by Prism.
Test the scenario is easy, thanks to the tools provided by Visual Studio: launch the application with the debugger connected and write some texts in the two TextBox controls. When the debugging session is running, you’ll find a dropdown menu in the Debug location toolbar (if you can’t see it, just right click in an empty space in the top area and enable it) called Lifecycle Events. This dropdown provides a list of options to simulate the different application’s state, since some of them aren’t deterministic: termination is one of them, since we don’t know if and when the operating system will terminate our application due to low memory. Choose Suspend and shutdown from the menu: the application will be terminated and the debugger disconnected. Now launch again the application: you’ll notice that, despite the fact that the process has been terminated, the two values you’ve inserted in the TextBox controls will still be there. If you launch the application from scratch, instead, the two controls will be empty: it’s correct, because in this case the user is launching the application for the first time or after he explicitly closed it, so he doesn’t expect to find it in the previous state.
If you want to make sure that it’s not a trick, but Prism is really managing the state for you, just try to remove the [RestorableState] attributes from one of the two properties: if you simulate again the termination, you’ll notice that only the property which is still marked with the attribute will restore its value, while the other TextBox will be empty.
Saving complex data
Another common scenario is when you have to deal with complex data, like classes that are part of your model. Let’s say, for example, that the Name and Surname properties we’ve previously seen compose a class named Person, with the following definition:
public class Person { public string Name { get; set; } public string Surname { get; set; } }
Now let’s change the XAML of our page in the following way:
<storeApps:VisualStateAwarePage x:Class="Prism_StateManagement.Views.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Prism_StateManagement.Views" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:storeApps="using:Microsoft.Practices.Prism.StoreApps" xmlns:mvvm="using:Microsoft.Practices.Prism.Mvvm" mc:Ignorable="d" mvvm:ViewModelLocator.AutoWireViewModel="True" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid> <StackPanel Margin="12, 0, 12, 0"> <TextBox Text="{Binding Path=Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" /> <TextBox Text="{Binding Path=Surname, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Margin="0, 0, 0, 20 "/> <StackPanel Orientation="Horizontal"> <TextBlock Text="{Binding Path=LatestPerson.Name}" Style="{StaticResource HeaderTextBlockStyle}" /> <TextBlock Text="{Binding Path=LatestPerson.Surname}" Style="{StaticResource HeaderTextBlockStyle}" Margin="30, 0, 0, 0" /> </StackPanel> </StackPanel> </Grid> <Page.BottomAppBar> <CommandBar> <CommandBar.PrimaryCommands> <AppBarButton Label="Ok" Icon="Accept" Command="{Binding Path=ShowMessageCommand}" /> </CommandBar.PrimaryCommands> </CommandBar> </Page.BottomAppBar> </storeApps:VisualStateAwarePage>
We’ve added a couple of new TextBlock controls, which are in binding with a property in the ViewModel called LatestPerson: by using the dot as separator, we access to the Name and Surname properties of the class. We’ve also added a button in the application bar, which is connected to a command in the ViewModel called ShowMessageCommand. Let’s see, now, the new definition of the ViewModel:
public class MainPageViewModel : ViewModel { private string _name; [RestorableState] public string Name { get {return _name;} set { SetProperty(ref _name, value); } } private string _surname; [RestorableState] public string Surname { get { return _surname; } set { SetProperty(ref _surname, value); } } private Person _latestPerson; public Person LatestPerson { get {return _latestPerson;} set { SetProperty(ref _latestPerson, value); } } public MainPageViewModel() { ShowMessageCommand = new DelegateCommand(() => { LatestPerson = new Person { Name = Name, Surname = Surname }; }); } public DelegateCommand ShowMessageCommand { get; private set; } }
We’ve added a new Person property, called LatestPerson, which is the one in binding with the new TextBlock controls we’ve added in the page. We’ve also defined a new DelegateCommand, which name is ShowMessageCommand, that is triggered when you press the button in the application bar: the command simply takes the values inserted by the user in the two TextBox controls in the page and use them to create a new Person object, which is displayed on the page simply by assigning it to the LatestPerson property.
Now let’s say that we want to preserve also the value of the LatestPerson property so that, when the user restores the app, even if it was terminated, both the TextBox controls and the new TextBlock ones will hold the previous value. In this case, we can’t simply add the [RestorableState] attribute to the LatestPerson property, since it’s a complex one. We need to use another approach, thanks to another helper offered by Prism: a class called SessionStateManager. Like the NavigationService, this class is registered in the Unity container in the App class, inside the method OnInitializeAsync():
protected override Task OnInitializeAsync(IActivatedEventArgs args) { // Register MvvmAppBase services with the container so that view models can take dependencies on them _container.RegisterInstance<ISessionStateService>(SessionStateService); _container.RegisterInstance<INavigationService>(NavigationService); // Register any app specific types with the container // Set a factory for the ViewModelLocator to use the container to construct view models so their // dependencies get injected by the container ViewModelLocationProvider.SetDefaultViewModelFactory((viewModelType) => _container.Resolve(viewModelType)); return Task.FromResult<object>(null); }
Thanks to the dependency injection approach, you’ll be able to use the SessioneStateService class simply by adding an ISessionStateService parameter in the ViewModel’s constructor, like in the following sample:
public class MainPageViewModel : ViewModel { private readonly ISessionStateService _sessionStateService; public MainPageViewModel(ISessionStateService sessionStateService) { _sessionStateService = sessionStateService; } }
The SessionStateService class offers a property called SessionState, which type is Dictionary<string, object>: you’ll be able to save, inside this collection, any type of complex data that you want to keep in case of termination. Under the hood, the content of the collection will be serialized in the storage. The SessionStateService class is very useful because it takes care of automatically saving and restoring its content when the app is suspended and resume: as developers, we’ll have just to take to save inside the SessionState collection the data we want to save. That’s it: Prism will take care of saving it when the app is suspended and to restore it in case the app is resumed and it was terminated by the operating system.
Since it’s a standard Dictonary collection, working with is really easy. Here is the complete ViewModel:
public class MainPageViewModel : ViewModel { private readonly ISessionStateService _sessionStateService; private string _name; [RestorableState] public string Name { get {return _name;} set { SetProperty(ref _name, value); } } private string _surname; [RestorableState] public string Surname { get { return _surname; } set { SetProperty(ref _surname, value); } } private Person _latestPerson; public Person LatestPerson { get {return _latestPerson;} set { SetProperty(ref _latestPerson, value); } } public MainPageViewModel(ISessionStateService sessionStateService) { _sessionStateService = sessionStateService; ShowMessageCommand = new DelegateCommand(() => { LatestPerson = new Person { Name = Name, Surname = Surname }; if (sessionStateService.SessionState.ContainsKey("Person")) { sessionStateService.SessionState.Remove("Person"); } sessionStateService.SessionState.Add("Person", LatestPerson); }); } public DelegateCommand ShowMessageCommand { get; private set; } public override void OnNavigatedTo(object navigationParameter, NavigationMode navigationMode, Dictionary<string, object> viewModelState) { base.OnNavigatedTo(navigationParameter, navigationMode, viewModelState); if (_sessionStateService.SessionState.ContainsKey("Person")) { LatestPerson = _sessionStateService.SessionState["Person"] as Person; } } }
When the ShowMessageCommand is executed, other than just assigning a value to the LatestPerson property, we save it in the SessionState collection, simply by using the Add() method. Then, in the OnNavigatedTo() method (which we’ve discussed in the previous post and it’s triggered when the user navigates to the current page, also in case of resume), we can check if the SessionState contains the value we’ve previously saved, which is identified by the Person key. If it exists, we retrieve it and we assign it to the LatestPerson property, after performing a cast since the SessionState collection contain generic objects.
If you’ll try to execute the application, however, you’ll get an exception when the app is suspended: this happens because Person is a custom class, it’s not part of the Windows Runtime, so Prism doesn’t know how to handle it when it comes to save the state by serializing it. We can solve this problem by overriding a method in the App class called OnRegisterKnownTypesForSerialization(), in which we have to register, in the SessionStateService, every custom class we’re going to use in the application, like in the following sample:
protected override void OnRegisterKnownTypesForSerialization() { base.OnRegisterKnownTypesForSerialization(); SessionStateService.RegisterKnownType(typeof(Person)); }
We simply call the RegisterKnownType() method, passing as parameter the class’ type (in this case, Person).
That’s all! Now, if you launch the application and, again, by simulating the termination using the Suspend and shutdown option in Visual Studio, you’ll notice that both the simple properties (Name and Surname) and the complex one (LatestPerson) we’ll be correctly restored.
Wrapping up
As usual, you can find the sample project used for this post on GitHub at https://github.com/qmatteoq/Prism-UniversalSample
Index of the posts about Prism and Universal Windows apps
- The basic concepts
- Binding and commands
- Advanced commands
- Navigation
- Managing the application’s lifecycle
- Messages
- Layout management
Sample project: https://github.com/qmatteoq/Prism-UniversalSample
in
-
Prism and Universal Windows app – Navigation
Let’s continue our journey about using Prism in Universal Windows app development, by understanding how to manage a very important feature: navigation. Unless we’re talking about a really basic application, your project will likely contain more than one page. Consequently, we need to be able to navigate from one page to another. The problem is that, typically, the operation is performed using the Frame class, which can be accessed only in code behind, since the pages inherits from base Page class. To achieve the same result in a MVVM application, we need to use a special service that act as a wrapper of the Frame class and that can be accessed also from a ViewModel. Prism already offers this class: it’s called NavigationService and we’ve already seen it in the first post, when we’ve setup the infrastructure required by Prism to properly work. If you remember, the App.xaml.cs file contains the following method:
protected override Task OnLaunchApplicationAsync(LaunchActivatedEventArgs args) { NavigationService.Navigate("Main", null); return Task.FromResult<object>(null); }
This method is invoked when the application is started and it’s used to redirect the user the user to the main page of the application. We’ve already described how to perform a basic navigation using the NavigationService: we call the Navigate() method, passing as parameter a string with the name of the View, without the Page suffix. In the previous sample, passing Main as parameter of the Navigate() method means that we want to redirect the user to a View in our project that is called MainPage.xaml.
Using the NavigationService in a ViewModel
To use the NavigationService in a ViewModel we need to use the dependency injection approach we’ve already seen in the first post. The App.xaml.cs file, in fact, contains also the following method:
protected override Task OnInitializeAsync(IActivatedEventArgs args) { // Register MvvmAppBase services with the container so that view models can take dependencies on them _container.RegisterInstance<ISessionStateService>(SessionStateService); _container.RegisterInstance<INavigationService>(NavigationService); // Register any app specific types with the container // Set a factory for the ViewModelLocator to use the container to construct view models so their // dependencies get injected by the container ViewModelLocationProvider.SetDefaultViewModelFactory((viewModelType) => _container.Resolve(viewModelType)); return Task.FromResult<object>(null); }
As you can see, among other things, the method takes of registering, inside the UnityContainer object, the INavigationService interface, by connecting it to the NavigationService implementation offered by Prism. This way, we’ll be able to use the NavigationService in a ViewModel simply by adding an INavigationService parameter to the ViewModel’s constructor, like in the following sample:
public class MainPageViewModel : ViewModel { private readonly INavigationService _navigationService; public MainPageViewModel(INavigationService navigationService) { _navigationService = navigationService; } }
Now we have a reference to the Prism’s NavigationService, that we can use to perform navigation inside a ViewModel.
Managing the navigation events in a ViewModel
Another common requirement when you develop an Universal Windows app using the MVVM pattern is to find a way to intercept when the user is navigating to or away from the current page. In code behind, it’s easy to do it because we have access to two methods called OnNavigatedTo() and OnNavigatedFrom(): unfortunately, they are available only in the code behind class, since they are inherited from the base Page class.
Prism offers a simple way to manage these two navigation events in a ViewModel: as we’ve already seen in the previous posts, the ViewModels inherits from a base class called ViewModel. Thanks to this class, we are able to subscribe to the OnNavigatedTo() and OnNavigatedFrom() methods inside a ViewModel, like in the following sample:
public class MainPageViewModel : ViewModel { public override void OnNavigatedTo(object navigationParameter, NavigationMode navigationMode, Dictionary<string, object> viewModelState) { base.OnNavigatedTo(navigationParameter, navigationMode, viewModelState); } public override void OnNavigatedFrom(Dictionary<string, object> viewModelState, bool suspending) { base.OnNavigatedFrom(viewModelState, suspending); } }
The OnNavigatedTo() method, especially, is very important, because it’s the best point where to load the data that will be displayed in the page. Some people, in fact, often use the ViewModel constructor to load the data: however, it’s not a good approach, especially in modern development. It often happens, in fact, that the data needs to be loaded using asynchronous methods, which are based on the async and await pattern. If you have some experience with this approach, you’ll know that a class constructor can’t be marked with the async keyword, so you won’t be able to call a method using the await prefix. This means that, for example, the following code won’t compile:
public class MainPageViewModel : ViewModel { private readonly INavigationService _navigationService; private readonly IFeedService _feedService; private ObservableCollection<News> _news; public ObservableCollection<News> News { get { return _news; } set { SetProperty(ref _news, value); } } public MainPageViewModel(INavigationService navigationService, IFeedService feedService) { _navigationService = navigationService; _feedService = feedService; IEnumerable<News> news = await _feedService.GetNews(); News = new ObservableCollection<News>(); foreach (News item in news) { News.Add(item); } } }
I won’t describe in details how is defined the IFeedService class and how exactly works the GetNews() method: you can see all the details in the source code of the project that is published at https://github.com/qmatteoq/Prism-UniversalSample. For the moment, it’s important just to know that it’s an asynchronous method which, by using the SyndicationClient class provided by the Windows Runtime, downloads the RSS feed of this blog and parses it, to return the items as list of objects. Our goal is to display this list using a ListView control in the application: however, as I’ve previously mentioned, the previous code won’t compile, since I’m calling an asynchronous method (GetNews(), which is invoked with the await keyword) inside the ViewModel constructor, which can’t be marked with the async keyword.
The OnNavigatedTo(), instead, since it’s a sort of event handler (it manages the page navigation event), can be marked with the async keyword, so you can call asynchronous method in it without problems. Here is the correct approach to implement the previous sample:
public class MainPageViewModel : ViewModel { private readonly INavigationService _navigationService; private readonly IFeedService _feedService; private ObservableCollection<News> _news; public ObservableCollection<News> News { get { return _news; } set { SetProperty(ref _news, value); } } public MainPageViewModel(INavigationService navigationService, IFeedService feedService) { _navigationService = navigationService; _feedService = feedService; } public override async void OnNavigatedTo(object navigationParameter, NavigationMode navigationMode, Dictionary<string, object> viewModelState) { IEnumerable<News> news = await _feedService.GetNews(); News = new ObservableCollection<News>(); foreach (News item in news) { News.Add(item); } } }
As you can see, the data loading operation is now performed in the OnNavigatedTo() method, which is marked with the async keyword and, consequently, we can call the GetNews() method using the await prefix without any issue. This code will compile and run just fine!
Passing parameters from one page to another
The OnNavigatedTo() can be useful also in another scenario: to pass parameters from one page to the another. Let’s use again the previous sample and let’s say that we have the following XAML page, which is the same we’ve seen in the previous post talking about commands with parameters:
<storeApps:VisualStateAwarePage x:Class="Prism_Navigation.Views.MainPage" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="using:Prism_Navigation" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" xmlns:storeApps="using:Microsoft.Practices.Prism.StoreApps" xmlns:mvvm="using:Microsoft.Practices.Prism.Mvvm" xmlns:interactivity="using:Microsoft.Xaml.Interactivity" xmlns:core="using:Microsoft.Xaml.Interactions.Core" mc:Ignorable="d" mvvm:ViewModelLocator.AutoWireViewModel="True" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"> <Grid> <ListView ItemsSource="{Binding Path=News}" SelectionMode="Single" IsItemClickEnabled="True" Margin="12, 0, 12, 0"> <ListView.ItemTemplate> <DataTemplate> <StackPanel> <TextBlock Text="{Binding Title}" Style="{StaticResource SubheaderTextBlockStyle}" TextWrapping="Wrap" /> </StackPanel> </DataTemplate> </ListView.ItemTemplate> <interactivity:Interaction.Behaviors> <core:EventTriggerBehavior EventName="ItemClick"> <core:InvokeCommandAction Command="{Binding Path=ShowDetailCommand}" /> </core:EventTriggerBehavior> </interactivity:Interaction.Behaviors> </ListView> </Grid> </storeApps:VisualStateAwarePage>
We’re using a ListView control to display the list of news retrieved by the GetNews() method of the FeedService class. By using the behavior we’ve learned to use in the previous post, we’ve connected the ItemClick event to the ShowDetailCommand command in the ViewModel. Here is how the ShowDetailCommand’s definiton looks like:
public class MainPageViewModel : ViewModel { public MainPageViewModel(INavigationService navigationService, IFeedService feedService) { _navigationService = navigationService; _feedService = feedService; ShowDetailCommand = new DelegateCommand<ItemClickEventArgs>((args) => { News selectedNews = args.ClickedItem as News; _navigationService.Navigate("Detail", selectedNews); }); } public DelegateCommand<ItemClickEventArgs> ShowDetailCommand { get; private set; } }
The approach is the same we’ve seen in the previous post: we’ve defined a **DelegateCommand
** and, consequently, we are able to retrieve, in the command definition, the item selected by the user. The difference is that, this time, after casting it as a **News** object, we pass it as second parameter of the **Navigate()** method of the **NavigationService**. This way, other than redirecting the user to a page called **DetailPage.xaml** (since we’re using the string **Detail**), we bring the information about the selected news, so that we can show the details. The OnNavigatedTo() method can be used also to retrieve the parameter that we’ve passed from the NavigationService, thanks to one of the parameters called navigationParameter. The following sample shows the definition of the ViewModel of the detail page, which is the destination page of the navigation:
public class DetailPageViewModel : ViewModel { private News _selectedNews; public News SelectedNews { get { return _selectedNews; } set { SetProperty(ref _selectedNews, value); } } public override void OnNavigatedTo(object navigationParameter, NavigationMode navigationMode, Dictionary<string, object> viewModelState) { if (navigationParameter != null) { SelectedNews = navigationParameter as News; } } }
As you can see, thanks to the navigationParameter, we are able to retrieve the selected item that has been passed by the previous page. In the sample, we simply cast it back to the News type (since it’s a generic object) and we display it to the user with the following XAML:
<Grid> <StackPanel> <TextBlock Text="{Binding Path=SelectedNews.Title}" /> <TextBlock Text="{Binding Path=SelectedNews.Summary}" /> </StackPanel> </Grid>
Managing the back button in Windows Phone 8.1
One of the most important differences in the navigation system between Windows Phone 8.0 and Windows Phone 8.1 is the back button management: by default, in Windows Phone 8.0, the Back button always redirects the user to the previous page of the application. In Windows Phone 8.1, instead, to keep the behavior consistent with Windows 8.1 (which doesn’t offer a hardware button), the Back button redirect the user to the previous application. However, Microsoft don’t suggest to developers to use this approach: users, by pressing the back button, expect to go back to the previous page of the application since Windows Phone 7.0. Consequently, you need to override, in every page or in the App.xaml.cs, the HardwareButtons.BackPressed event and to perform a similar code:
private void HardwareButtons_BackPressed(object sender, BackPressedEventArgs e) { Frame frame = Window.Current.Content as Frame; if (frame == null) { return; } if (frame.CanGoBack) { frame.GoBack(); e.Handled = true; } }
The code takes care of checking if there are pages in the backstack of the application (by checking the value of the CanGoBack property): if this is the case, we call the GoBack() method to perform a navigation to the previous page and we set the Handled property of the event handler as true, so that we prevent the operating system to manage it.
Well, the good news is that the MvvmAppBase class, which is the one that replaces the App one in a Prism application, already takes care of this for us: we won’t have to do nothing or to write additional code to support a proper Back key management in a Windows Phone 8.1 application.
Wrapping up
As usual, you can download the sample code related to this post from GitHub at the following URL: https://github.com/qmatteoq/Prism-UniversalSample
Index of the posts about Prism and Universal Windows apps
- The basic concepts
- Binding and commands
- Advanced commands
- Navigation
- Managing the application’s lifecycle
- Messages
- Layout management
Sample project: https://github.com/qmatteoq/Prism-UniversalSample
in
subscribe via RSS