So far we've setup a few pages with some design-time data to help us layout the app, but running it still yields a blank screen with no interaction possible. We'll remedy this by loading the sample data at runtime and adding a simple navigation implementation to allow us to go back and forth between the pages.
To keep things simple, we'll briefly break from the MVVM pattern, and navigate directly of the page frame via the code behind, and in future posts see how we can roll this back in. For now, he first thing we need is to have something on the screen to trigger the navigation, so we need to begin by populating the page with some data when the screen loads.
OnNavigatedTo
Everytime a XAML page loads, a method called OnNavigatedTo executes, and by overriding this method in the code-behind for the page, we can execute custom code to prepare the UI.
In our case, we want to add items to the ViewModel associated with the page. Remember that this ViewModel is already wired up to the DataContext property of the page, so we can simply cast it to the appropriate type and update it when the page loads.
For simplicity, we'll just load it with a list of fake items, as shown here in the code-behind for the MainPage:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
var vm = DataContext as MainPageViewModel;
if (!vm.TestItems.Any())
{
vm.TestItems = new System.Collections.ObjectModel.ObservableCollection<TestItem>(GetFakeRuntimeItems());
}
}
private List<TestItem> GetFakeRuntimeItems()
{
var items = new List<TestItem>();
for (var i = 1; i <= 5; i++)
{
var color = string.Join("", Enumerable.Repeat(i.ToString(), 6));
var testItem = new TestItem() { Id = i, Title = "Runtime Item " + i, Subtitle = "Subtitle " + i, HexColor = string.Concat("#", color) };
items.Add(testItem);
}
return items;
}
Running the app now reveals that same list of sample items:
However, clicking or tapping the items doesn't cause any change in behavior. We need to actually trigger the navigation, which we want to happen whenever an item in the list is clicked.
Enabling ItemClick on ListView
Fortunately, the ListView already has the event we need, and we need only subscribe to it. However, before we can use it, we have to enable it. You see, by default, the IsItemClickEnabled property of the ListView (and all other ListViewBase controls) is set to false.
So in addition to creating and setting the ItemClick handler you also want to set the IsItemClickEnabled property to true:
<ListView x:Name="listView"
HorizontalAlignment="Left"
ItemTemplate="{StaticResource TestItemTemplate}"
ItemsSource="{Binding TestItems}"
Margin="19,12,19,0"
VerticalAlignment="Top"
IsItemClickEnabled="True" ItemClick="listView_ItemClick" />
We can now use the handler to perform the actual navigation to the SecondPage, which we do by calling Navigate on the page's frame:
private void listView_ItemClick(object sender, ItemClickEventArgs e)
{
var selectedItem = e.ClickedItem as TestItem;
if (selectedItem == null) return;
this.Frame.Navigate(typeof(SecondPage), selectedItem.Id);
}
However, in doing so we probably want to be able to pass the context of the clicked item, so the desired page knows what it should be showing. We do this by using a navigation parameter.
Navigation Parameters
When an item is clicked, the clicked item is passed to the eventhandler argument as an object, so we want to make sure that we cast it so we can extract the appropriate information needed for navigation, in this case the ID. We can use this value as a parameter to tell the SecondPage which item we actually want to see.
You might be wondering why we passed the ID of the item instead of the complete object itself. It is actually possible to send the complete object, and doing so would certainly simplify loading the SecondPage by using the passed object directly. In fact doing this in an app will not cause any immediate exceptions...
However, the reason this isn't suggested or recommended is that during suspension of your app (as we'll see in our next post), the entire state of the application needs to be preserved so that it can be restored upon resume. This includes the navigation history and all of its parameters, so that the back stack can be restored as well, returning the user to the exact same point they last left the application.
Navigation state is preserved by the app frame by calling GetNavigationState(), however, as you can see in the documentation:
The serialization format used by these methods is for internal use only. Your app should not form any dependencies on it. Additionally, this format supports serialization only for basic types like string, char, numeric and GUID types.
Unless you intend roll your own system for storing and restoring state, you need to make sure the parameters used in navigation are one of the supported simple types, which is why we've used the ID to indicate the desired item.
On the SecondPage, we want to retrieve the item that matches the ID. Again for simplicity, we'll simply instantiate a duplicate list of the same items from the MainPage to ensure that the possible items match, and retrieve the desired item by it's ID value. Just like with the MainPage, we'll fire this in the OnNavigatedTo method:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
var vm = DataContext as SecondPageViewModel;
vm.SelectedItem = GetFakeRuntimeItems().FirstOrDefault(i => i.Id == (int)e.Parameter);
}
private List<TestItem> GetFakeRuntimeItems()
{
var items = new List<TestItem>();
for (var i = 1; i <= 5; i++)
{
var color = string.Join("", Enumerable.Repeat(i.ToString(), 6));
var testItem = new TestItem() { Id = i, Title = "Runtime Item " + i, Subtitle = "Subtitle " + i, HexColor = string.Concat("#", color) };
items.Add(testItem);
}
return items;
}
Obviously a real-world app would not duplicate the data but probably pull it from some locally-cached source, but this simple example should help you get the idea of how the navigation parameter drives the navigation. Running the app now allows us to click the desired item in the list and see its details on the second page:
Of course, once we get there we're stuck! We need a way to go back!
Back Button on Windows 10
If we were on a device like a phone, we could certainly leverage the existing back button required by the hardware. However, most desktop devices do not feature such a requirement. Fortunately you can use the SystemNavigationManager in Windows 10 to enable a global, app-specific back button to facilitate such navigation, ensuring a consistent experience regardless of the device.
Here's what it looks like in our sample app, shown on the SecondPage app bar, allowing us to go back to the previous page.
The button is inherently responsive, so if we activate tablet mode, it will switch to the bottom of the page, consistent with the OS behavior.
In addition to the AppViewBackButtonVisibility property, the SystemNavigationManager also exposes a BackRequested event that fires when the user actually requests to go back. The best part of this event is that it is setup so that it fires regardless of whether it is triggered by the button on the app bar or a hardware back button such as on a phone. This means the same code can handle either of those events (or any other ways a user might request to go back in the future!), all you have to do is handle the event.
Responsive Code
Finally, since devices like phones do have the necessary hardware to allow a user to go back, we want to make sure to only enable the app bar back button on devices that don't have such a feature. This is where we can leverage the ability of Windows 10 to detect such features at runtime via the ApiInformation class. We can use the IsTypePresent method to determine whether or not the device running your application supports, defines, and implements a specified type, in this case the HardwareButtons which contain the BackPressed event.
If the device does support and implement this type, we can hide the app bar back button, otherwise we obviously want to show it. Here is the complete code for the SecondPage, again fired OnNavigatedTo that gives the back navigation support we want:
protected override void OnNavigatedTo(NavigationEventArgs e)
{
base.OnNavigatedTo(e);
var vm = DataContext as SecondPageViewModel;
vm.SelectedItem = GetFakeRuntimeItems().FirstOrDefault(i => i.Id == (int)e.Parameter);
var currentView = SystemNavigationManager.GetForCurrentView();
if (!ApiInformation.IsTypePresent("Windows.Phone.UI.Input.HardwareButtons"))
{
currentView.AppViewBackButtonVisibility = this.Frame.CanGoBack ? AppViewBackButtonVisibility.Visible : AppViewBackButtonVisibility.Collapsed;
}
currentView.BackRequested += SystemNavigationManager_BackRequested;
}
private void SystemNavigationManager_BackRequested(object sender, BackRequestedEventArgs e)
{
if (this.Frame.CanGoBack)
{
this.Frame.GoBack();
e.Handled = true;
}
}
Note that we first have to get the reference to the SystemNaviagtionManager for the current page by calling GetForcurrentView.
The app now shows the expected back button on the screen for the second page. Also as expected, it is hidden from the SecondPage on devices with a dedicated back button, such as the phone:
We could use the same strategy to show an on-canvas back button (like we used to do for Windows 8.1), but we'll leave that as an exercise for the reader :)
Wrapping Up and Next Steps
At last, we have a simple but functional app, complete with both sample and runtime data, that allows us to navigate between the defined pages. However, something interesting happens if, while we are on the SecondPage, we suspend and shutdown the app.
Recall that an app is expected to store the state and resume the user experience where it left off when it was suspended. Our app, however, kicks you back to the beginning on launch, discarding where we were and which item we selected!
In our next post we'll see how we can leverage the SuspensionManager class from Windows 8.1 and modify it to work with Windows 10 to preserve and restore state automatically. We'll also see how we can refactor both the navigation and page-load code so that it is fully driven by and runs in our ViewModels, putting us back on the MVVM pattern.
Until then, be sure to grab the code by filling out this form here:
Get the CODE!
and as always, I hope this was helpful, and thanks for reading!