In the a previous post, I've told that you should use controls that are responsive to ease your work of creating an UI that adapts to the screen size. One of the controls that can help you a lot in this task is the Navigation View. In fact, if you do nothing and create something like:
<Grid>
<NavigationView/>
</Grid>
You will have a full featured UI with a navigation pane, a content pane and a settings button:
And this UI adapts itself to the screen size. If you resize your screen to decrease its height, the navigation pane will collapse and, if you decrease a little more, it will collapse, remaining only the Hamburger and Back buttons:
This is a great bonus for you: you don't need to worry about adapting to the device - the control does that automatically for you. In this article, I will show you how to use the Navigation View to show your content and navigate between pages.
Adding Navigation Items
To add navigation items, you should use its MenuItems property. There you can add simple items (NavigationItem), separators (NavigationItemSeparator) and headers (MenuItemHeader). For example, when you use this code:
<Grid>
<NavigationView>
<NavigationView.MenuItems>
<NavigationViewItemHeader Content="Main"/>
<NavigationViewItem Content="Customers"/>
<NavigationViewItem Content="Orders"/>
<NavigationViewItemSeparator/>
<NavigationViewItemHeader Content="Reports"/>
<NavigationViewItem Content="Customers"/>
<NavigationViewItem Content="Orders"/>
<NavigationViewItem Content="Sales"/>
<NavigationViewItemSeparator/>
<NavigationViewItemHeader Content="Charts"/>
<NavigationViewItem Content="Sales by Customer"/>
<NavigationViewItem Content="Sales by Product"/>
<NavigationViewItem Content="Sales by Date"/>
</NavigationView.MenuItems>
</NavigationView>
</Grid>
You will get this UI:
Although it seems good when the navigation pane is open, when it's closed, it doesn't work so well:
You need to add an icon to the items. This is done with the Icon property. You can set it directly using an enumeration for the symbol (see all enumerations here) or you can set the icon from an image. If you want to set a symbol form the Segoe MDL2 Assets font, you can use a FontIcon. This code shows how this is done:
<NavigationView>
<NavigationView.MenuItems>
<NavigationViewItemHeader Content="Main" />
<NavigationViewItem Content="Customers" Icon="People"/>
<NavigationViewItem Content="Orders" Icon="Shop"/>
<NavigationViewItemSeparator/>
<NavigationViewItemHeader Content="Reports"/>
<NavigationViewItem Content="Customers" >
<NavigationViewItem.Icon>
<FontIcon Glyph="" FontFamily="Segoe UI Symbol"/>
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="Orders">
<NavigationViewItem.Icon>
<FontIcon Glyph="" FontFamily="Segoe UI Symbol"/>
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="Sales">
<NavigationViewItem.Icon>
<PathIcon HorizontalAlignment="Center" VerticalAlignment="Center"
Data="M 0,0 L20,0 40,70 140,70 140,74 36,74 20,4z
M25,12 L150,12 140,50 37,55z
M70,82 A10,10 360 1 1 70,81.99z
M120,82 A10,10 360 1 1 120,81.99z" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItemSeparator/>
<NavigationViewItemHeader Content="Charts"/>
<NavigationViewItem Content="Sales by Customer">
<NavigationViewItem.Icon>
<FontIcon Glyph=""/>
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="Sales by Product">
<NavigationViewItem.Icon>
<PathIcon Data="M0,0 L1.25,0 1.25,18 18,18 18,19.25 0,19.25z
M0,18 L18,0 19.25,0 19.25,1.25z" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="Sales by Date" >
<NavigationViewItem.Icon>
<FontIcon Glyph=""/>
</NavigationViewItem.Icon>
</NavigationViewItem>
</NavigationView.MenuItems>
</NavigationView>
In the code above, there are some different ways to set the icon to the item:
- The Customers and Orders items use the named icons from the MDL2 Assets
- The Customers and Orders reports use font glyphs from the Segoe UI Symbol font. The Glyph property is the unicode number of the glyph
- The Sales report doesn't have a FontIcon, but a PathIcon, that has a Data property that creates the icon as a geometry
- The Sales by Customers chart uses a glyph from the MDL2 Assets, but uses the unicode number of the gliph. In this case, you don't need the FontFamily property
This code shows something like this:
Navigating between pages
When you are using this control, two events are key for the navigation: ItemInvoked and SelectionChanged. ItemInvoked is called when an item is selected by a user interaction (tap, pointer, mouse), and it can be called even if the item is already selected. SelectionChanged is only called if the current item has effectively changed and can be triggered programatically (in this case, ItemInvoked is not called). In the handler, you have to find out which is the item selected and take the according action, usually changing the content of the control. This code shows how to handle this:
private NavigationViewItem _lastItem;
private void NavigationView_OnItemInvoked(
Windows.UI.Xaml.Controls.NavigationView sender,
NavigationViewItemInvokedEventArgs args)
{
var item = args.InvokedItemContainer as NavigationViewItem;
if (item == null)
{
NavView.Content = null;
return;
}
if (item == _lastItem)
return;
var textSelected = item.Content?.ToString() ?? "";
NavView.Header = textSelected;
var grid = new Grid();
var text = new TextBlock
{
Text = textSelected,
FontFamily = new FontFamily("Arial"),
FontSize = 24,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center
};
grid.Children.Add(text);
NavView.Content = grid;
_lastItem = item;
}
We are checking the selected item, if it is a different one, we create a grid with a textblock that shows the text of the selected option and set it as the content for the NavigationView. This works fine, but there is more that can be done here: we can also handle the back button and act if it's clicked (until now it's been disabled. If you don't want to show it, just set the IsBackButtonVisible property to False).
Managing content and navigation
We could manage the content and navigation by ourselves. In this case, we would need a navigation mechanism, that handles the back navigation and replaces the content in the NavigationView. But we can do it by adding a single control: the Frame. It can control the back navigation and even replace the content with a transition. This can be done with something like this:
<NavigationView x:Name="NavView"
ItemInvoked="NavigationView_OnItemInvoked"
BackRequested="NavView_OnBackRequested"
IsBackEnabled="{Binding ElementName=ContentFrame, Path=CanGoBack}">
....
<Frame x:Name="ContentFrame"
NavigationFailed="ContentFrame_OnNavigationFailed"/>
</NavigationView>
The IsBackEnabled property of the NavigationView is bound to the CanGoBack property of the frame. In the previous version of the code, I've used the title of the navigation item, but I think this option is less than optimal, because if we change the title, the code breaks. In this case, I think it's better to use the Tag property, a property that can store any object. In our case, we will store an unique string that will point to the view, so we can navigate to it. Our code will be something like this:
<NavigationView.MenuItems>
<NavigationViewItemHeader Content="Main" />
<NavigationViewItem Content="Customers" Icon="People" Tag="CustView"/>
<NavigationViewItem Content="Orders" Icon="Shop" Tag="OrderView"/>
<NavigationViewItemSeparator/>
<NavigationViewItemHeader Content="Reports"/>
<NavigationViewItem Content="Customers" Tag="CustRepoView">
<NavigationViewItem.Icon>
<FontIcon Glyph="" FontFamily="Segoe UI Symbol"/>
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="Orders" Tag="OrderRepoView">
<NavigationViewItem.Icon>
<FontIcon Glyph="" FontFamily="Segoe UI Symbol"/>
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="Sales" Tag="SalesRepoView">
<NavigationViewItem.Icon>
<PathIcon HorizontalAlignment="Center" VerticalAlignment="Center"
Data="M 0,0 L20,0 40,70 140,70 140,74 36,74 20,4z
M25,12 L150,12 140,50 37,55z
M70,82 A10,10 360 1 1 70,81.99z
M120,82 A10,10 360 1 1 120,81.99z" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItemSeparator/>
<NavigationViewItemHeader Content="Charts"/>
<NavigationViewItem Content="Sales by Customer" Tag="SalesCustChartView">
<NavigationViewItem.Icon>
<FontIcon Glyph=""/>
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="Sales by Product" Tag="SalesProdChartView">
<NavigationViewItem.Icon>
<PathIcon Data="M0,0 L1.25,0 1.25,18 18,18 18,19.25 0,19.25z
M0,18 L18,0 19.25,0 19.25,1.25z" />
</NavigationViewItem.Icon>
</NavigationViewItem>
<NavigationViewItem Content="Sales by Date" Tag="SalesDateChartView">
<NavigationViewItem.Icon>
<FontIcon Glyph=""/>
</NavigationViewItem.Icon>
</NavigationViewItem>
</NavigationView.MenuItems>
Now, we must create the views for each item. Create a new folder named Views and, in this folder, create new pages, one for each item: CustView, OrderView, CustRepoView,OrderRepoView, SalesRepoView, SalesCustChartView, SalesProdChartView and SalesDateChartView.
With the views in place, we can create the ItemInvoked handler:
private NavigationViewItem _lastItem;
private void NavigationView_OnItemInvoked(
Windows.UI.Xaml.Controls.NavigationView sender,
NavigationViewItemInvokedEventArgs args)
{
var item = args.InvokedItemContainer as NavigationViewItem;
if (item == null || item == _lastItem)
return;
var clickedView = item.Tag?.ToString();
if (!NavigateToView(clickedView)) return;
_lastItem = item;
}
The NavigateToView method is:
private bool NavigateToView(string clickedView)
{
var view = Assembly.GetExecutingAssembly()
.GetType($"NavigationView.Views.{clickedView}");
if (string.IsNullOrWhiteSpace(clickedView) || view == null)
{
return false;
}
ContentFrame.Navigate(view, null, new EntranceNavigationTransitionInfo());
return true;
}
This method uses reflection to get the type corresponding to the view name that's in the Tag property and navigates to it. If you run the program, you will see something like this when you click an item:
This code has one problem: if you click the Settings item, you will get an error. That's because the Settings item doesn't have a valid Tag. So, we must create the SettingsView page in the Views folder and change the ItemInvoked handler:
private void NavigationView_OnItemInvoked(
Windows.UI.Xaml.Controls.NavigationView sender,
NavigationViewItemInvokedEventArgs args)
{
var item = args.InvokedItemContainer as NavigationViewItem;
if (item == null || item == _lastItem)
return;
var clickedView = item.Tag?.ToString() ?? "SettingsView";
if (!NavigateToView(clickedView)) return;
_lastItem = item;
}
If the tag is null, we will navigate to the SettingsView. With this change, the code works fine and you can click on the Settings item. There is only one thing that can be made, here: manage when the back button is clicked. We do this in the BackRequested handler:
private void NavView_OnBackRequested(
Windows.UI.Xaml.Controls.NavigationView sender,
NavigationViewBackRequestedEventArgs args)
{
if (ContentFrame.CanGoBack)
ContentFrame.GoBack();
}
With this code, the back button is also handled with very little code.
Conclusions
As you can see, the NavigationView allows you to create easily a responsive UI, with lots of features and many different ways to handle navigation. There are some features that I didn't mention in the article, like the fact that you can stick to a view and make it not respond to size changes, you can change the stops where the view changes, you can add a header or footer to the page or you can even put the items at the top, just by changing a property - If you set the PaneDisplayMode to Top, you will get this UI:
This is a very powerful control and a nice improvement for your UI.
The full source code for this project is at https://github.com/bsonnino/NavigationView
Great! Thank you
Awesome tutorial. Many many many many thanks 🙂
Nice write-up. Question: When I go back and forth between pages, and then I test the Suspension lifecycle in VS, the Application.Current.Suspending += App_Suspending and Application.Current.Resuming += App_Resuming fire off many, many times in the debug writeline console. So does every time I go back to the previous page, it is loading a new instance?
Great tutorial. Thank you 3 thousands. However it appears that the back button is incomplete.
Can you tell me more? The back button is handled by the component, you can handle the back navigation, but there’s no need to do it.
Thank you – this really helped me.