Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does my AdaptiveTrigger fire when I change the theme settings?

I have a user interface that adapts to different device form factors using AdaptiveTrigger. In particular, I have a SplitView-based shell menu with two state triggers for when the minimum window width is 1000 epx and 600 epx respectively. The 600 epx state trigger can fire on certain mobile devices in landscape mode depending on their form factor and scale factor, and this is intended behavior.

However, I've noticed that on the desktop, the 600 epx state trigger fires in the background (changing ShellSplitView.DisplayMode from Overlay to CompactOverlay) as soon as I change a theme setting such as toggling between dark and light mode, changing the accent color, or toggling high contrast mode; and on both the mobile emulator and a device, it fires when I go to the Settings app, change a theme setting, and return to my app. Most peculiarly, this only happens in the default state and only after I've caused any of the state triggers to fire, by resizing the window on the desktop or by rotating the mobile emulator or device. Resizing/rotating again after returning to the app makes the problem go away. I have not previously seen this in any other apps, including Microsoft's apps or any third-party UWP apps.

I've confirmed that this is not caused by some other code interfering with the state triggers, by successfully reproducing the problem in a blank UWP project with a single page containing nothing but a SplitView with any number of visual states (and some text content):

<Page
    x:Class="App1.MainPage"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:App1"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    mc:Ignorable="d">
    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup>
                <VisualState>
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="1000"/>
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="ShellSplitView.DisplayMode" Value="Inline"/>
                        <Setter Target="ShellSplitView.IsPaneOpen" Value="True"/>
                    </VisualState.Setters>
                </VisualState>
                <VisualState>
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="600"/>
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="ShellSplitView.DisplayMode" Value="CompactOverlay"/>
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>

        <SplitView
            x:Name="ShellSplitView"
            CompactPaneLength="48"
            DisplayMode="Overlay"
            IsPaneOpen="False"
            OpenPaneLength="256">
            <SplitView.Content>
                <TextBlock Text="Content"/>
            </SplitView.Content>
            <SplitView.Pane>
                <TextBlock Text="Pane"/>
            </SplitView.Pane>
        </SplitView>
    </Grid>
</Page>

You can attempt to reproduce the problem yourself by creating a blank UWP project, replacing the contents of MainPage.xaml with the above XAML, and running the project.

Considering all I did was change the theme settings, I don't understand why any of the state triggers would fire. In fact, according to this guide on MSDN, the default values reflected in the SplitView element's attributes should be reapplied automatically:

When you use state triggers, you don't need to define an empty DefaultState. The default settings are reapplied automatically when the conditions of the state trigger are no longer met.

And according to the Windows.UI.Xaml.AdaptiveTrigger page itself:

By default, the StackPanel orientation is Vertical. When the window width is >= 720 effective pixels, the VisualState change is triggered, and the StackPanel orientation is changed to Horizontal.

... which, together, reinforce my point — the window width does not change when I change the theme settings, so I don't see why the state trigger would kick in when it shouldn't, and why only after I've triggered a state change by resizing the window or rotating the device.

So why is this happening, and how do I fix it?

like image 773
BoltClock Avatar asked May 21 '16 04:05

BoltClock


1 Answers

This issue appears to have been fixed in the Windows 10 Anniversary Update for all device families. The state triggers will no longer activate when they're not supposed to, and the default values as specified outside of the state triggers are now correctly reapplied.

  • You do not need this workaround if your app will only be running on the 1607 branch (build 14393) or newer. Note that this refers to the minimum version as specified in your project properties, and not the target version.

  • You need this workaround if your app will be running on the 1511 branch (build 10586) or 1507 branch (build 10240), even though it will behave correctly on devices with the newer builds.


Unfortunately, I haven't been able to work around this problem without just adding a default state to the VisualStateGroup anyway, using a state trigger with a minimum dimension of 0 epx (you do not need any setters — you just need a state trigger):

<VisualStateManager.VisualStateGroups>
    <VisualStateGroup>
        <VisualState>
            <VisualState.StateTriggers>
                <AdaptiveTrigger MinWindowWidth="1000"/>
            </VisualState.StateTriggers>
            <VisualState.Setters>
                <Setter Target="ShellSplitView.DisplayMode" Value="Inline"/>
                <Setter Target="ShellSplitView.IsPaneOpen" Value="True"/>
            </VisualState.Setters>
        </VisualState>
        <VisualState>
            <VisualState.StateTriggers>
                <AdaptiveTrigger MinWindowWidth="600"/>
            </VisualState.StateTriggers>
            <VisualState.Setters>
                <Setter Target="ShellSplitView.DisplayMode" Value="CompactOverlay"/>
            </VisualState.Setters>
        </VisualState>
        <VisualState>
            <VisualState.StateTriggers>
                <AdaptiveTrigger MinWindowWidth="0"/>
            </VisualState.StateTriggers>
        </VisualState>
    </VisualStateGroup>
</VisualStateManager.VisualStateGroups>

As to why this happens, my guess is that the VisualStateManager is trying to go to a state when the system theme is updated, but as there is none defined for the initial (or "Default" or "Normal") state, it has to choose the next best alternative. What I still don't understand, is why the VisualStateManager has to change visual states when the system theme is updated in the first place... don't ThemeResources refresh by themselves? Why would they depend on the VisualStateManager?

It's interesting to note that most third-party tutorials on adaptive triggers in UWP already do this. It's not clear if the authors were aware of this problem, or if they added the default state simply because they were used to doing so in the Silverlight/WPF days. Based on the documentation above and the otherwise correct UWP behavior, though, this is probably an edge case.

like image 147
BoltClock Avatar answered Sep 30 '22 16:09

BoltClock