VisualStateManager pitfalls

Visual Studio Development WinUI XAML

7 years ago

VisualStateManager is extremely useful when building XAML layouts. With UWP's AdaptiveTriggers to switch between states it provides a great way to build design that looks great on any screen size. There are however two small gotchas that can cause unnecessarily long periods of head scratching.

Gotcha 1: VisualStateManger placement

Consider the following snippet:

<UserControl ...>
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup>
            <VisualState>
                <VisualState.StateTriggers>
                    <AdaptiveTrigger MinWindowWidth="700" />
                </VisualState.StateTriggers>
                <VisualState.Setters>
                    <Setter Target="TargetText.Text" Value="This is never applied :-(" />
                    <Setter Target="Button.Background" Value="Red" />
                </VisualState.Setters>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
    <Button x:Name="Button">
        <TextBlock x:Name="TargetText" Text="VisualStateManager in root" />
    </Button>
</UserControl>

When you try to use this code, you will notice, that the state is never activated (the text of the TextBlock doesn't change when the Window width is greater than 700). The reason for this is that VisualStateManager cannot be attached to the root (Page , UserControl , ...) but has to be attached to a control or panel. Wrapping VisualStateManager inside the main Content UI element is the most common solution:

<UserControl ...>
    <Button>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup>
                <VisualState>
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="700" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="TargetText.Text" Value="Adaptive trigger applied!" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <TextBlock x:Name="TargetText" Text="VisualStateManager inside root Grid" />
    </Button>
</UserControl>

Gotcha 2: Invalid setters

Suppose we have the following:

<UserControl ...>
    <Button>
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup>
                <VisualState>
                    <VisualState.StateTriggers>
                        <AdaptiveTrigger MinWindowWidth="700" />
                    </VisualState.StateTriggers>
                    <VisualState.Setters>
                        <Setter Target="TargetText.Text" Value="This is never applied because the other Setter has invalid syntax" />
                        <Setter Target="TargetText.Foregroun" Value="Red" />
                    </VisualState.Setters>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <TextBlock x:Name="TargetText" Text="VisualStateManager with invalid setter" Foreground="Black" />
    </Button>
</UserControl>

At first glance it seems that everything is ok. At the second glance you can notice an innocent typo in the setter target for TargetText.Foreground. You might think that this will surely be discovered at runtime as an exception. But surprisingly that is not the case. When the Target or Value of any Setter is invalid, the app will still run without exceptions, but the VisualState containing this Setter will never be applied. In this concrete example, neither the Foreground nor the Text will change when the window is wider than 700. Whenever you encounter a VisualState , which does not take effect, be sure to check twice whether all the Setters are actually valid. You may also try to comment the whole Setters block and then try uncommenting one-by-one to find the culprit.

Sample source code

Source code for this blog post is available on my GitHub.

Summary

VisualStateManager is very useful and makes creating complex adaptive layouts much easier. We must however remain cautious to use it as intended and avoid misspellings at least until the Visual Studio XAML editor get proper IntelliSense support for Setter 's Target and Value .