The VisualStateManger has a collection of VisualStateGroups each one being a set of VisualStates that may apply in a given situation. Each of the groups are independent in the sense that one VisualState from each group might be active at any one time. For simplicity, let's just deal with one VisualStateGroup from which only one VisualState is active.
In this case the basic XAML is:
- <VisualStateManager.VisualStateGroups>
- <VisualStateGroup>
- list of visual states
- </VisualStateGroup>
- </VisualStateManager.VisualStateGroups>
This is how the VisualStateManager worked before Windows 10, but you could only activate a VisualState in code and the changes to the UI were specified as animations. With Windows 10 you can now activate a state using an AdaptiveTrigger object using just XAML and no code. In addition, you no longer have to pretend that your UI is changing via animation. Now you can use a Setter to change the value of any property on any object.
Each of the VisualState objects can have list of StateTriggers and a list of Setters and of course the order doesn't matter but it is slightly more logical to start with the StateTriggers.
The StateTriggers set conditions for the VisualState to be active and the Setters can be used to set properties on any XAML object when the VisualState is activated.
An AdaptiveTrigger takes the simple form:
- <AdaptiveTrigger property=value />
The property is set to the value that you specify and this is usually used in a comparison with some other value to determine when the trigger fires.
At the moment the only triggers are MinWindowWidth and MinWindowHeight but it is possible to add custom triggers. These conditions test the current width and height of the window and trigger if the window is larger than the specified value.
For example:
- <AdaptiveTrigger MinWindowsWidth=400 />
Is active is the window width that is 400 effective pixels or greater.
Notice that what happens is that you set the value of MinWindowsWidth to 400 and this is compared to the actual width of the window whenever it changes. The trigger fires if the actual window width is greater than or equal to MinWindowsWidth.
A Setter takes the form:
- <Setter Target="fully qualified property name"
- Value="value" />
- So for example:
- <Setter Target="Button1.Width"
- Value="200" />
Sets the Width of Button1 to 200 pixels if the state is active.
You can put multiple conditions and multiple setters into a single state.
For example:
- <VisualState x:Name="Big">
- <VisualState.StateTriggers>
- <AdaptiveTrigger MinWindowWidth="600" /> </VisualState.StateTriggers>
- <VisualState.Setters>
- <Setter Target="Button1.Content" Value="Wide" /> </VisualState.Setters>
- </VisualState>
In this case the trigger is if the window is 600 pixels or wider.
If the trigger fires then the Button's content is set to "Wide". What is it set to if the trigger isn't active?
The simple answer is that whatever value the property had before is restored when the trigger isn't active. For example, if the Button's original definition was:
- <Button Name="Button1" Content="Narrow" />
The label on the Button would change to "Wide" as the window was resized to bigger than 600 pixels and to "Narrow" when it became smaller than 599 pixels. That is, when none of the VisualStates are active, the system restores the UI to as it was originally specified in the XAML.
If you would rather have the default state included as a part of the VisualStateManager you can include it as:
- <VisualState x:Name="Small">
- <VisualState.StateTriggers>
- <AdaptiveTrigger MinWindowWidth="0" /> </VisualState.StateTriggers>
- <VisualState.Setters>
- <Setter Target="Button1.Content" Value="Narrow" /> </VisualState.Setters>
- </VisualState>
Putting the whole thing together gives:
- <Page x:Class="App4.MainPage" xmlns="http:
- winfx/2006/xaml/presentation" xmlns:x="http:
- winfx/2006/xaml" xmlns:local="using:App4" xmlns:d="http:
- expression/blend/2008" xmlns:mc="http:
- markup-compatibility/2006" mc:Ignorable="d">
- <Grid Background="{ThemeResource
- ApplicationPageBackgroundThemeBrush}">
- <VisualStateManager.VisualStateGroups>
- <VisualStateGroup>
- <VisualState x:Name="Small">
- <VisualState.StateTriggers>
- <AdaptiveTrigger MinWindowWidth="0" /> </VisualState.StateTriggers>
- <VisualState.Setters>
- <Setter Target="Button1.Content" Value="Narrow" /> </VisualState.Setters>
- </VisualState>
- <VisualState x:Name="Big">
- <VisualState.StateTriggers>
- <AdaptiveTrigger MinWindowWidth="600" /> </VisualState.StateTriggers>
- <VisualState.Setters>
- <Setter Target="Button1.Content" Value="Wide" /> </VisualState.Setters>
- </VisualState>
- </VisualStateGroup>
- </VisualStateManager.VisualStateGroups>
- <Button x:Name="Button1" HorizontalAlignment="Left" VerticalAlignment="Top" Margin="196,172,0,0" /> </Grid>
- </Page>
If you run this application and resize the width of the window you will see the label on the Button change as you cross the 600 pixel wide limit.
Of course, in a real application it is unlikely that you would be setting the content property of the controls. What you would be doing is setting layout properties to cope with the changing size of the window. This isn't an easy thing to do if the changes to the layout are large. It is much more suited to making small tweaks to essentially the same layout.
Examples: