In this post we’re going to cover one of the basics of app navigation which is the use of the UWP SplitView. If you’re coming from iOS and Android development you might be thinking “huh, I don’t even know what that is.” Well the good news is that it’s actually something you’re already familiar with. Whether you’re used to an app that has a master-details layout, or one that uses a burger menu to display a flyout menu, these can both be implemented using the UWP SplitView.
One thing to be aware of is that the UWP SplitView is one of the basic controls that was added early in the UWP lifecycle. Since then there have been other controls added, such as the NavigationView and the MasterDetailsView (Windows Community Toolkit) that provide extended functionality and are worth exploring depending on the requirements of your project.
New Project – Cross-Platform App (Uno Platform)
Let’s get into it – we’re going to start with creating a new project based on the latest Uno Visual Studio Extension (updated at end of July – if you’re using an older version I would recommend updating). I’m also working with the Visual Studio 2019 (16.3 preview 1.0) as this provides the best support for working with WebAssembly.
Once you’ve created your solution, I would highly recommend that you a) update NuGet references and b) go through each platform and make sure you can build and run the application. However, as at the time of writing do NOT update the Microsoft.Extensions.Logging.* packages – there’s a note in the csproj files that states “Note that for WebAssembly version 1.1.1 of the console logger required.” My recommendation is to leave these packages at v1.1.1 across all projects. For the Uno libraries I pick the latest prerelease versions but be warned that this occasionally backfires as you may get an unstable version – this is why it’s important to run each platform before proceeding!!
Note: One issue I’m currently seeing in the Uno template is that it generates assets (i.e. images) with names that include the scale factor eg scale-200. This is fine for UWP but fails to build for Android. I’ve gone through and just removed the scale factor from the filenames. You’ll need to rebuild your UWP project to make sure it picks up the filename changes correctly.
Note 2: When running the WebAssembly (aka WASM) project, make sure you select “Start without Debugging” in Visual Studio.
Note 3: When running the iOS build you may need to set the Deployment Target in the Info.plist. If you’re using the latest preview of Visual Studio it will pick up iOS 12.4 on the build agent which will cause you app to fail to deploy if you haven’t set the Deployment Target.
Adding the UWP SplitView
Now that we have our new application up and running, it’s time to add the SplitView control.
Design in Blend
For this we’re going to switch over to Blend – yeh, the product still exists and you’ll see in a minute why we’re going to be using it. To make the switch, right-click on the MainPage.xaml in the shared project and select Design in Blend.
Switching to Blend will take a few seconds, particularly if this is the first time you’ve ever opened Blend. You’ll also see errors as the iOS and Android projects won’t open – ignore these and don’t worry about the upgrade log that will popup (these are irritating but can be safely ignored as they won’t impact your project).
Despite selecting Design in Blend from the MainPage.xaml, once in Blend, you’ll actually need to use the Solution Explorer window to open MainPage.xaml in the designer. Once you’ve opened MainPage.xaml, Blend should feel fairly familiar, even if this is the first time you’ve used it. This is because a lot of the windows are shared with Visual Studio – the separation between the products is a mix of legacy (don’t want to disrupt the tiny percentage of developers who still use it) and the optimising that design tasks will be done using Blend.
Adding the SplitView
There are a number of ways to add the SplitView control into the MainPage. If you’re familiar with writing XAML it’s probably just easiest to add the SplitView element. However, to build familiarity with Blend, let’s add the SplitView using the designer. In the Assets window, enter “splitview” to locate the SplitView control. Use the mouse to drag the SplitView down to the Objects and Timeline window at the position in the hierarchy where you want it to appear. In this case we’re going to drop it inside the existing Grid – I typically have a Grid as the root of most pages to allow setting of background and other properties using a application-wide style, independently of what content is nested within the Grid.
Adding the SplitView this way will add it as the second child of the Grid, immediately following the existing TextBlock. As we want the SplitView to take up the entire space of the page, we’re going to move the TextBlock in the main content area by dragging the TextBlock into the Grid that is nested within the SplitView. The XAML of the page should now look like the following:
<Page
x:Class="UwpSplitViewSample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:UwpSplitViewSample"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<SplitView>
<SplitView.Pane>
<Grid />
</SplitView.Pane>
<Grid>
<TextBlock
Margin="20,20,20,20"
FontSize="30"
Text="Hello, world !" />
</Grid>
</SplitView>
</Grid>
</Page>
The SplitView is made up of two distinct areas: the main content area and the pane that can be shown/hidden (i.e. the flyout). From this code we can see that the SplitView as two nested XAML elements which align to the SplitView areas. The first is the SplitView.Pane which defines the layout of the content that will appear within the pane/flyout of the SplitView. The second defines the layout of content to appear within the main content area of the SplitView, in this case a Grid with a TextBlock contained within it.
Adjusting Layout with Blend
I’ll repeat this: For those of us familiar with writing XAML, you’ll find that manually crafting XAML is by far the quickest way to add and edit elements. However, Blend provides a number of shortcuts for positioning elements that can come in handy. Some of which we’ll cover here.
Designing the SplitView Pane
For the purpose of this post, all we’re going to do is to add a StackPanel with a TextBlock and Button as children to both the SplitView.Pane and the SplitView. The TextBlock will be used to indicate what part of the SplitView it is (Pane or Content) and the Button will be used to Show and Hide the pane of the SplitView.
Let’s start with the SplitView.Pane which already has a Grid element. In the Objects and Timeline window, right-click on the Grid and select Change Layout Type, followed by StackPanel.
You should see the Grid change to StackPanel and remains selected in the Objects and Timeline window. Next, with the StackPanel still in focus, use the Assets window to locate the TextBlock and double-click on TextBlock to add a TextBlock as a child to the StackPanel. Repeat, this time for a Button.
We’re going to position the StackPanel in the centre of the SplitView pane. To do this, again keep the StackPanel selected in the Objects and Timeline window, we’re going to go to the Properties window and scroll down to the Layout section at set the HorizontalAlignment and VerticalAlignment properties to Center.
Whilst we’re using the Properties window, select the TextBlock and change the Text property to “Pane”, and then select the Button and change the Content property to “Hide Pane” (Note that unlike Xamarin.Forms that has a Text property, to set the text on a Button in UWP XAML you need to set the Content property).
The last thing we’re going to do with the XAML for the SplitView.Pane is to add an event handler to the Click event of the Button. With the Button selected in the Objects and Timeline window, go to the Properties window, and click on the lightning bold icon to switch from properties to events view. Locate the Click event and type the name of the event handler you want to create, in this case PaneButtonClick. When you press Enter the PaneButtonClick method will be created in the MainPage.xaml.cs code behind file and the XAML will be updated with the Click property.
For the moment, that’s where we’ll leave the XAML for the SplitView.Pane and we’ll implement the logic for the Click event handler shortly.
Designing the SplitView Content
The layout of the SplitView content is going to be very similar to that of the pane: A StackPanel with nested TextBlock and Button. However, we’re going to go about it slightly differently, considering the fact that we already have a TextBlock nested in a Grid in the content area. We’re going to start by wrapping the TextBlock in a StackPanel. To do this, right-click somewhere in the TextBlock on the designer and select Group Into, followed by StackPanel (this also works if you right-click on the TextBlock in the Objects and Timeline window).
After doing this the hierarchy of elements in the SplitView content will be Grid, StackPanel, TextBlock. However, if you look at the XAML you’ll notice that Blend has added some additional attributes, setting the Margin on the StackPanel and the Height and Width on the TextBlock. To remove these, we can just right-click the StackPanel and select Layout, followed by Reset All. Repeat this on the TextBlock.
From here we can repeat the remaining steps that we did for the SplitView pane:
- Add a Button to the StackPanel
- Set HorizontalAlignment and VerticalAlignment on the StackPanel to Center
- Set Text on the TextBlock to “Content Area”
- Set Content on the Button to “Show Pane”
- Add an event handler called ShowPaneButtonClick to the Button Click event
The final XAML in MainPage.xaml should look similar to:
<Page x:Class="UwpSplitViewSample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:UwpSplitViewSample"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<SplitView>
<SplitView.Pane>
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock Text="Pane"
TextWrapping="Wrap" />
<Button Click="PaneButtonClick"
Content="Hide Pane" />
</StackPanel>
</SplitView.Pane>
<Grid>
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Vertical">
<TextBlock Text="Content Area" />
<Button Click="ShowPaneButtonClick"
Content="Show Pane" />
</StackPanel>
</Grid>
</SplitView>
</Grid>
</Page>
DisplayMode Property on the UWP SplitView
The UWP SplitView has two properties that are worth exploring in a bit of detail and we’ll use these to control the behaviour, or should I say the layout, of the SplitView based on screen width. Let’s start with the easy one, which is the IsPaneOpen property. As you can imagine from the name, this property indicates whether the pane of the SplitView is currently open (i.e. displayed in full, rather than hidden or in compact mode), or not.
The second property worth looking at is the DisplayMode of the SplitView, and has the following four possible values:
- Overlay – When the pane is visible it appears as an overlay across the content area. It doesn’t affect the size of the content area when it is shown/hidden.
- Inline – When the pane is displayed it reduces the size of the content area by the width of the pane. As the pane is shown/hidden the width of the content area shrinks and grows accordingly.
- CompactOverlay – Same as Overlay, except when the pane isn’t open, it still takes up space on the page equal to the CompactPaneLength property. Typically the compact view would show a list of icon buttons as a summary of the options available in the pane when it’s open.
- CompactInline – Same as Inline, except when the pane isn’t open it still takes up space on the page equal to the CompactPaneLength property.
We’ll leave the use of the CompactOverlay and CompactInline options for the moment and focus on the other two options. The Overlay option is great for when the size of the application is narrow, for example on a mobile phone. The Inline option is better suited for when the application has sufficient width to allow the pane to be shown and there still to remain sufficient content area to be useful.
Using VisualState to Set DisplayMode
With this in mind, we’re going to use visual states to set the DisplayMode property based on the width of the application. For the purpose of this post we’re going to use a split point of 900, meaning that if the width of the application is greater than 900 the DisplayMode will be set to Inline. If it’s below 900 it’ll be set to Overlay.
To begin with, we’re going to set the default value of the DisplayMode to Compact by adding DisplayMode=”Compact” to the SplitView element. Next in Blend we’re going to add two visual states via the States window. In the States window, click the Add state group button and give the state group a name, SizeStateGroup.
Next, click the Add state button twice to add two visual states and name them NarrowState and WideState. The actual names of the visual state group and the states are only for your benefit at this point, so you can name them according to what makes sense for you.
Click on the WideState and you should see a red dot appear along side the state name. This indicates that Blend is in visual state editing mode. You should also see a red border appear around the main design area. Note that when you’re in visual state editing mode in Blend, any change you make via the tool windows (eg changing a property) will be tracked against the selected visual state.
In visual state editing mode for the WideState, use the Properties window to change the DisplayMode to Inline. Next click the lightning icon button, Edit Adaptive Triggers, alongside the WideState visual state.
In the Collection Editor: StateTriggers, set the MinWindowHeight to 0 and the MinWindowWidth to 900. This is adding a trigger which will return true when the width of the application exceeds 900. At this point the VisualStateManager will automatically switch to the WideState, without you having to explicitly run any code. Repeat the process of setting an adaptive trigger but this time for the NarrowState and set both MinWindowHeight and MinWindowWidth to 0.
The final XAML:
<Page x:Class="UwpSplitViewSample.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="using:UwpSplitViewSample"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<SplitView x:Name="splitView"
DisplayMode="Overlay">
<SplitView.Pane>
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center">
<TextBlock Text="Pane"
TextWrapping="Wrap" />
<Button Click="PaneButtonClick"
Content="Hide Pane" />
</StackPanel>
</SplitView.Pane>
<Grid>
<StackPanel HorizontalAlignment="Center"
VerticalAlignment="Center"
Orientation="Vertical">
<TextBlock Text="Content Area" />
<Button Click="ShowPaneButtonClick"
Content="Show Pane" />
</StackPanel>
</Grid>
</SplitView>
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="SizeStateGroup">
<VisualState x:Name="WideState">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowHeight="0"
MinWindowWidth="900" />
</VisualState.StateTriggers>
<VisualState.Setters>
<Setter Target="splitView.(SplitView.DisplayMode)" Value="Inline" />
</VisualState.Setters>
</VisualState>
<VisualState x:Name="NarrowState">
<VisualState.StateTriggers>
<AdaptiveTrigger MinWindowHeight="0"
MinWindowWidth="0" />
</VisualState.StateTriggers>
</VisualState>
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Grid>
</Page>
Note: In UWP the order of the VisualState elements doesn’t matter as all the AdaptiveTriggers are evaluated and the best match is determined. For example if the application width is 1000, the conditions for the triggers for both NarrowState and WideState are met. However, the WideState is a better match since the width is > 900. For Uno (i.e. iOS, Android and WebAssembly), the order matters – the first trigger to match, wins. In the above XAML you’ll see the WideState before the NarrowState to make sure that if the application width is > 900, the WideState visual state is active.
Opening and Closing the UWP SplitView Pane
If you run the application at this point and adjust the width of the application the DisplayMode property will switch between Inline and Overlay. However, you won’t notice any change because we currently don’t have a way to open the pane. To do this we’ll add some code to the Click event handlers we created earlier, as follows:
private void PaneButtonClick(object sender, RoutedEventArgs e)
{
splitView.IsPaneOpen = false;
}
private void ShowPaneButtonClick(object sender, RoutedEventArgs e)
{
splitView.IsPaneOpen = true;
}
UWP SplitView In Action
With all the XAML and code in place, let’s see it in action on all various platforms.
In this post we’ve covered off how easily you can build a responsive interface using Blend and the UWP SplitView. The fact that it then just works on iOS, Android and WebAssembly is a big bonus thanks to the Uno platform.
One of the best tutorials for uno.platform and winui! Thanks
Since I am on Mac most of the time then creating the project is a pane, sorry PAIN.
Also there are so many errors when starting from the uno.platform templates. Always tons of warning, errors, crashes until you get to a stable project. You then change something and it starts crashing, don’t compile, more warnings, …
A basic project like this from GitHub would be nice.
Would you share your project?