Home > Universal Windows Platform > Adaptive Triggers, RelativePanel and DataTemplate in the Universal Windows Platform

Adaptive Triggers, RelativePanel and DataTemplate in the Universal Windows Platform

12/05/2015

When developing for the Universal Windows Platform, we can use Adaptive Triggers and controls like the RelativePanel to dynamically tailor the UI of our apps according to various conditions, in particular the screen resolution. We can for example modify the dimension of an image, the font size of a text, the margin between objects and rearrange them as the screen space changes.

Let’s see a simple example:

<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
    <RelativePanel>
        <Image x:Name="world" Source="/Assets/World.jpg" Height="250" />
        <TextBlock x:Name="welcomeMessage" RelativePanel.Below="world"
                   RelativePanel.AlignHorizontalCenterWith="world"
                   FontSize="28" Text="Hello World!" />
    </RelativePanel>

    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup>
            <VisualState x:Name="narrowView">
                <VisualState.StateTriggers>
                    <AdaptiveTrigger MinWindowWidth="0" />
                </VisualState.StateTriggers>
                <VisualState.Setters>
                    <Setter Target="world.Height" Value="100" />
                    <Setter Target="welcomeMessage.FontSize" Value="14" />
                    <Setter Target="welcomeMessage.(RelativePanel.RightOf)"
                            Value="world" />
                    <Setter 
                        Target="welcomeMessage.(RelativePanel.AlignVerticalCenterWith)"
                        Value="world" />
                    <Setter 
                        Target="welcomeMessage.(RelativePanel.AlignHorizontalCenterWith)"
                        Value="{x:Null}" />
                </VisualState.Setters>
            </VisualState>
            <VisualState x:Name="wideView">
                <VisualState.StateTriggers>
                    <AdaptiveTrigger MinWindowWidth="720" />
                </VisualState.StateTriggers>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
</Grid>

The default UI contains an Image with an Height of 150 pixels (line 3) and a TextBlock with a FontSize of 28 (lines 4-6). These two controls are put inside a RelativePanel (lines 2-7): using the attached properties RelativePanel.Below and RelativePanel.AlignHorizontalCenterWith on the TextBlock (line 4-5), it is placed below the Image and horizontally centered:

The UI when effective pixels are >= 720

The UI when effective pixels are >= 720

Then, in the VisualStateManager section (lines 9-34), we have defined two states based on AdaptiveTrigger.MinWindowWidth condition.

The first one, narrowView, is applied when the screen width is between 0 (line 13) and 719 effective pixels (that is the lower bound limit defined in the next trigger, line 30). In this case, the Image height is set to 150 pixels, the font size of the TextBlock becomes 14 and the RelativePanel attached properties are modified so that the text appears vertically centered on the right of the image:

The UI when effective pixels are < 720

The UI when effective pixels are < 720

The other state, wideView, is used when the effective pixels are grater than or equal to 720. We haven’t defined any Setters, so the original properties are applied. In other words, the UI will be the default one we have defined directly in XAML (see first screenshot).

This approach works great and allows to easily adapt the UI according to the device characteristics. However, there is a particular situation we must keep in mind: we can’t directly use Adaptive Triggers with objects contained in a DataTemplate. Suppose we have the following ListView:

<ListView x:Name="peopleView" SelectionMode="None">
    <ListView.ItemTemplate>
        <DataTemplate>
            <RelativePanel Margin="8">
                <Image x:Name="personImage" Width="120" Height="120" Margin="12"
                       Source="{Binding ImageUrl}" />
                <TextBlock x:Name="personName" RelativePanel.Below="personImage" 
                       RelativePanel.AlignHorizontalCenterWith="personImage"
                       Text="{Binding FullName}"
                       FontSize="16" />
            </RelativePanel>
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView>

If we want to change properties of personImage and personName based of screen size, we can’t just add a VisualStateManger inside the DataTemplate. Instead, we need to create a User Control and put the DataTemplate content inside it:

<UserControl
    x:Class="FirstDemo.PersonItem"
    ...>

    <RelativePanel Margin="8">
        <Image x:Name="personImage" Width="120" Height="120" Margin="12"
                                       Source="{Binding ImageUrl}" />
        <TextBlock x:Name="personName" RelativePanel.Below="personImage" 
                   RelativePanel.AlignHorizontalCenterWith="personImage"
                   Text="{Binding FullName}"
                   FontSize="16" />

        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup>
                <VisualState x:Name="narrowView">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="0" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="personImage.Width" Value="64" />
                        <Setter Target="personImage.Height" Value="64" />
                        <Setter Target="personName.FontSize" Value="12" />
                        <Setter Target="personName.(RelativePanel.RightOf)" 
                                Value="personImage" />
                        <Setter 
                            Target="personName.(RelativePanel.AlignVerticalCenterWith)" 
                            Value="personImage" />
                        <Setter 
                            Target="personName.(RelativePanel.AlignHorizontalCenterWith)" 
                            Value="{x:Null}" />
                    </VisualState.Setters>
                </VisualState>
                <VisualState x:Name="wideView">
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="720" />
                    </VisualState.StateTriggers>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
    </RelativePanel>
</UserControl>

In this way, we can use Adaptive Triggers as we see in our first example to dynamically change the appearance of all the items in the ListView (lines 13-39). Finally, let’s update the ListView definition to use the User Control:

<ListView x:Name="peopleView" SelectionMode="None">
    <ListView.ItemTemplate>
        <DataTemplate>
            <local:PersonItem />
        </DataTemplate>
    </ListView.ItemTemplate>
</ListView> 

And now everything will work as expected.

  1. 18/05/2015 at 08:11

    Hi, is there a way to let adaptive trigger work under DataTemplate in HubSection?

    • 18/05/2015 at 12:05

      Have you tried the User Control approach I suggest in the article?

  2. 29/05/2015 at 02:15

    I tried the user control approach but my adaptive triggers still don’t work😦

    • 29/05/2015 at 09:08

      Could you share your experiments? So we can try to understand where is the problem.

  1. 07/07/2015 at 10:00
Comments are closed.
%d bloggers like this: