Navigation using Regions in Windows and Uno Platform Applications

Out of the box, applications built using WinUI / Windows App Sdk or the Uno Platform have access to a wide range of controls, such as Frame, TabBar and NavigationView that can be used for navigation. However, there’s no common pattern/metaphor that makes it navigate within an application. Uno.Extensions includes Navigation which provides a consistent abstraction for navigation either in code (both codebehind and in Model/ViewModel) or XAML. In this post I’m going to start by giving a simple example of how Navigation works and then discuss the concept of Regions.

We’ll kick of this post by creating a new application using the Uno Platform Template Wizard. If you just want to build for Windows, you can just pick Windows from the Platforms tab in the Wizard. For the purpose of this post we’ll go with the Recommended preset, which will create a XAML based Material themed application that uses both MVUX (a Reactive pattern for state management) and region based navigation (i.e. Uno.Extensions with Navigation).

If we run up the generated application, clicking the button will navigate from MainPage to SecondPage.

Unlike an application that you’d get if you used the Blank App template for WinUI/Windows App SDK, if you look in App.xaml.cs you won’t see the creation of a Frame or explicit calls to Navigate to MainPage. If you look inside ShellModel you’ll see there’s a Start method that’s invoked from the constructor, which invokes the NavigateViewModelAsync method, specifying the MainModel.

public async Task Start()
{
    await _navigator.NavigateViewModelAsync<MainModel>(this);
}

I’m not going to go into detail right now about ViewMap and RouteMap but if you look in App.xaml.cs you’ll see there are three ViewMap instances defined that match pages and models. In this case MainModel is associated with MainPage, which is how Navigation knows to navigate to the MainPage from the call to NavigateViewModelAsync.

So the next question is, where’s the Frame that’s being used to navigate between MainPage and SecondPage. One of the built in capabilities of Navigation is that if you attempt to navigate to a Page but there’s no Frame, a new Frame will be created and used to navigate. We’ll come back to this when we look at how NavigationView works but in the context of a new application, a Frame is created and used for navigation.

The follow up question is how does this newly created Frame get added to the application. To answer this, let’s step through the startup sequence for the application.

  • An instance of the App class gets created
  • The overridden App.OnLaunched method is invoked, which kicks of the initialization sequence for the application.
  • An IApplicationBuilder instance is create by calling CreateBuilder and then a sequence of extension methods is invoked to register services etc. Note that these extension methods are very quick to execute because they don’t do much other than to register other callbacks for when Build is invoked.
  • There are a couple of extension methods invoked on MainWindow to enable hot reload (in DEBUG) and to set the Window icon
  • The NavigateAsync method is called on the IApplicationBuilder, specifying the UIElement that should be used as the root element of the application, in this case Shell.
  • Inside the NavigateAsync method, a new instance of Shell will be created and set as the Content for the Window of the application. At this point the Window is activated so that the application appears and shows the progress indicator that’s part of the Shell.
  • Build is invoked on the IApplicationBuilder, which calls Build on internally held IHostBuilder in order to create the IHost instance. Note that this is done after the instance of Shell is created to avoid any delays in displaying the application.
  • After the IHost instance has been created, an instance of ShellModel is created (as this is the first RouteMap defined for the application), which in turn calls NavigateViewModelAsync to navigate to the MainModel.
  • As there’s currently no Frame available, a new Frame is created and added to the Content property of the ContentControl specified by the Shell. In this case the ExtendedSplashScreen is returned – once all the startup logic has completed, the ExtendedSplashScreen will change states from displaying the LoadingContent, to displaying the Content
  • The Navigate method on the Frame is invoked in order to navigate to the MainPage

As you can see from this sequence, there’s quite a few steps that are abstracted away through the use of Navigation, coupled with the Shell / ExtendedSplashScreen control.

Navigating from MainPage to SecondPage can be done a number of different ways. When the application is created from the template, the navigation is invoked by calling the NavigateViewModelAsync method via code in the MainModel (1 in the code below).

public async Task GoToSecond()
{
    var name = await Name;

    // 1 - Navigate based on Model/ViewModel
    await _navigator.NavigateViewModelAsync<SecondModel>(this, data: new Entity(name!));
    // 2 - Navigate to a view
    await _navigator.NavigateViewAsync<SecondPage>(this, data: new Entity(name!));
    // 3 - Navigate based on Data
    await _navigator.NavigateDataAsync(this, new Entity(name!));
}

Other alternative methods are NavigateViewAsync, where you can specify the type of view (in this case SecondPage) that you want to navigate to, and NavigateDataAsync where you specify an data instance and Navigation works out which view to navigate to based on the registered ViewMap instances.

You can also invoke navigation from either the codebehind or in XAML. Here’s an example of both:

Codebehind

private async void NavigateSecondPageClick(object sender, RoutedEventArgs e)
{
    await this.Navigator().NavigateViewModelAsync<SecondModel>(this);
}

XAML

<Button Content="Go to Second Page"
        uen:Navigation.Request="Second" />

As you can see from these code examples the navigation methods exist on an INavigator instance that is either passed into the constructor of a Model/ViewModel, or can be retrieved using the Navigator() extension method. You can think of an INavigator as being the controller for navigation for a particular Region. A Region is a logical abstraction for an element within the application responsible for navigating between views. In this case the Frame that was created during startup is a Region and there is a corresponding FrameNavigator instance that is used to control, and respond to, navigation on the Frame. It’s the FrameNavigator instance that’s injected into the MainModel constructor which is subsequently used to navigate to SecondPage.

In this post we’ve covered how Navigation works during application startup and discussed in brief the concept of a Region. I’ll continue this discussion in the next post where we look in more detail at other types of Regions and how they relate to each other.

1 thought on “Navigation using Regions in Windows and Uno Platform Applications”

Leave a comment