Adaptive user interfaces and the Universal Windows Platform

Mittwoch, 8. Juni 2016

Adaptive user interfaces and the Universal Windows Platform

UWP apps are supposed to work across many different device types like phones, tablets or laptops. Their user interfaces need to adapt to the screen sizes and input methods these devices use. This post will examine techniques to create adaptive user interfaces in UWP apps.

Adaptive UI using the VisualStateManager

Window size

Microsoft recommends screen breakpoints for phones, tables and desktops at 320, 720 and 1024 effective pixels. We define these breakpoints in a general XAML style file, just in case they are changed at some point in the future:

<x:Double x:Key="SmallMinWidth">0</x:Double>
<x:Double x:Key="MediumMinWidth">720</x:Double>
<x:Double x:Key="LargeMinWidth">1024</x:Double>

Now we can use the VisualStateManager to perform changes on the user interface depending on the current size of the app window. The actual changes to the user interfaces can be declared below the <VisualState.Setters> node.

<Grid x:Name="MainGrid">
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="ViewStates">
            <VisualState x:Name="SmallView">
                <VisualState.StateTriggers>
                    <AdaptiveTrigger MinWindowWidth="{StaticResource SmallMinWidth}" />
                </VisualState.StateTriggers>
                <VisualState.Setters>
                    <Setter Target="ItemsListView.ItemTemplate" Value="{StaticResource SmallItemTemplate}" />
                </VisualState.Setters>
            </VisualState>
            <VisualState x:Name="MediumView">
                <VisualState.StateTriggers>
                    <AdaptiveTrigger MinWindowWidth="{StaticResource MediumMinWidth}" />
                </VisualState.StateTriggers>
                <VisualState.Setters>
                    <Setter Target="ItemsListView.ItemTemplate" Value="{StaticResource MediumItemTemplate}" />
                </VisualState.Setters>
            </VisualState>
            <VisualState x:Name="LargeView">
                <VisualState.StateTriggers>
                    <AdaptiveTrigger MinWindowWidth="{StaticResource LargeMinWidth}" />
                </VisualState.StateTriggers>
                <VisualState.Setters>
                    <Setter Target="ItemsListView.ItemTemplate" Value="{StaticResource LargeItemTemplate}" />
                </VisualState.Setters>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
</Grid>

In the example above, depending on the window size the item template of a ListView is changed.

Adapting to input method changes

Even though Microsoft provides information about the current input method in the ViewManagement.UserInteractionMode property there is no builtin visual state trigger that exposes this information for the VisualStateManager. Luckily it is very simple to write custom visual state triggers. A visual state trigger named ContinuumTrigger for exactly this purpose has already been written.

<VisualStateGroup x:Name="InputStates">
    <VisualState x:Name="Touch">
        <VisualState.StateTriggers>
            <triggers:ContinuumTrigger UIMode="Touch" />
        </VisualState.StateTriggers>
        <VisualState.Setters>
            <Setter Target="ItemsListView.SelectionMode" Value="None"></Setter>
        </VisualState.Setters>
    </VisualState>
    <VisualState x:Name="Mouse">
        <VisualState.StateTriggers>
            <triggers:ContinuumTrigger UIMode="Mouse" />
        </VisualState.StateTriggers>
        <VisualState.Setters>
            <Setter Target="ItemsListView.SelectionMode" Value="Single"></Setter>
        </VisualState.Setters>
    </VisualState>
</VisualStateGroup>

The example above switches the selection mode of a listview to None if the system it is running on uses touch mode and to Single if the system uses mouse input.

Programatically changing the UI

Apps with more complex user interfaces often depend on more state than just the window size to decide how the user interface should look like. In this case it can be easier to change the user interface in code. We can attach to the CurrentStateChanged event of our "ViewStates" VisualStateGroup to get notified if the window size is changed and the screen breakpoints are crossed.

Loaded += (s, e) =>
{
    var groups = VisualStateManager.GetVisualStateGroups(ShellGrid as FrameworkElement);
    
    foreach (var group in groups)
    {
        if (group.Name == "ViewStates")
        {
            group.CurrentStateChanged += (sender, args) =>
            {
                UpdateUI();
            };

            UpdateUI();
            break;
        }
    }
};

Typical user interface changes

Show or hide user interface elements

A large part of adaptive ui is simply hiding elements if there is not enough screen real estate. This can be done easily using the Visibility property of any interface element:

<VisualState x:Name="SmallView">
    <VisualState.StateTriggers>
        <AdaptiveTrigger MinWindowWidth="{StaticResource SmallMinWidth}" />
    </VisualState.StateTriggers>
    <VisualState.Setters>
        <Setter Target="DescriptionPanel.Visibility" Value="Collapsed"></Setter>
    </VisualState.Setters>
</VisualState>

Change the template of a control

Changing a template can make sense for any control and is very effective for ListView controls too. Setting the ItemTemplate of a ListView will change the template of all ListViewItems:

<VisualState x:Name="LargeView">
    <VisualState.StateTriggers>
        <AdaptiveTrigger MinWindowWidth="{StaticResource LargeMinWidth}" />
    </VisualState.StateTriggers>
    <VisualState.Setters>
        <Setter Target="ItemsListView.ItemTemplate" Value="{StaticResource LargeItemTemplate}" />
    </VisualState.Setters>
</VisualState>

Display user interface elements side by side or below each other

Responsive websites often use the technique to show elements side by side by default and switch to showing them below each other if there is not enough horizontal space. In UWP apps this can be easily acomplished using the VisualStateManager and a RelativePanel:

...
<VisualStateManager.VisualStateGroups>
    <VisualStateGroup x:Name="ViewStates">
        <VisualState x:Name="SmallView">
            <VisualState.StateTriggers>
                <AdaptiveTrigger MinWindowWidth="{StaticResource SmallMinWidth}" />
            </VisualState.StateTriggers>
            <VisualState.Setters>
                <Setter Target="RightOrBelowPanel.(RelativePanel.Below)" Value="AlwaysVisiblePanel" />
                <Setter Target="RightOrBelowPanel.(RelativePanel.RightOf)" Value="" />
            </VisualState.Setters>
            <VisualState x:Name="MediumView">
                <VisualState.StateTriggers>
                    <AdaptiveTrigger MinWindowWidth="{StaticResource MediumMinWidth}" />
                </VisualState.StateTriggers>
                <VisualState.Setters />
            </VisualState>            
        </VisualState>
        ...
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>
...
<RelativePanel>
    <StackPanel Name="AlwaysVisiblePanel" MaxWidth="320" Margin="16">
        <TextBlock>Always Visible</TextBlock>
        <TextBlock>Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh.</TextBlock>
    </StackPanel>
    
    <StackPanel Name="RightOrBelowPanel" RelativePanel.RightOf="AlwaysVisiblePanel" Margin="16">
        <TextBlock>Right Or Below</TextBlock>
        <TextBlock>
        Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh
        euismod tincidunt ut laoreet dolore magna aliquam erat volutpat.
        </TextBlock>
    </StackPanel>
</RelativePanel>
...

The above snippet will put the RightOrBelowPanel StackPanel below the AlwaysVisiblePanel if the available horizontal width is below 720 effective pixels.

Microsoft makes it very simple to create adaptive user interfaces with UWP. The described techniques enable sophisticated user interfaces that work on desktops, tables and phones. A simple demo application that illustrates the described techniques can be downloaded from GitHub.


Blog / Patrick Dehne

Patrick Dehne

Letzte Eintr├Ąge

08.06.2016
Adaptive user interfaces and the Universal Windows Platform

Beliebteste Eintr├Ąge

08.06.2016
Adaptive user interfaces and the Universal Windows Platform