Search code examples
c#wpfresponsive-designvisualstatemanager

Why doesn't VisualStateManager.GoToState work on a control in WPF?


I am working on a WPF application and trying to make a responsive UI using Visual State Manager as MSDN suggests for UWP responsive design and it works on UWP but not on WPF.

Here is the example I am testing on WPF. It is supposed to change the background of the StackPanel to Red on start-up.

The XAML:

<Grid>
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup>
            <VisualState x:Name="DefaultState">
                <Storyboard>

                </Storyboard>
            </VisualState>

            <VisualState x:Name="WideState">
                <Storyboard >
                    <ObjectAnimationUsingKeyFrames Storyboard.TargetName="Menu" Storyboard.TargetProperty="StackPanel.Background">
                        <DiscreteObjectKeyFrame KeyTime="0" Value="Red"/>
                    </ObjectAnimationUsingKeyFrames>
                </Storyboard>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>

    <Grid.ColumnDefinitions>
        <ColumnDefinition Width="*"/>
    </Grid.ColumnDefinitions>

    <Grid.RowDefinitions>
        <RowDefinition Height="25"/>
        <RowDefinition Height="*"/>
        <RowDefinition Height="25"/>
    </Grid.RowDefinitions>

    <Grid x:Name="StackPanelCenetrofPage" Grid.Row="1">
        <Grid.ColumnDefinitions>
            <ColumnDefinition x:Name="MenuColumnWidth" Width="200"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>

        <StackPanel x:Name="Menu"
                    Orientation="Vertical"
                    Background="AliceBlue"
                    HorizontalAlignment="Stretch"
                    Grid.Column="0">
            <Button Content="Hey"/>
        </StackPanel>
        <Frame Grid.Column="1"/>
    </Grid>
</Grid>

And the C# code:

private void Window_SizeChanged(object sender, SizeChangedEventArgs e) 
{
    if (e.PreviousSize.Width < 1024) 
    {
        VisualStateManager.GoToState(this, "WideState", true);
    }
    else 
    {
        VisualStateManager.GoToState(this, "DefaultState", false);
    }
}

Solution

  • According to the documentation:

    Call the GoToState method if you are changing states in a control that uses the VisualStateManager in its ControlTemplate. Call the GoToElementState method to change states on an element outside of a ControlTemplate (for example, if you use a VisualStateManager in a UserControl or in a single element).

    So you should use GoToElementState instead of GoToState. The first parameter of GoToElementState is the control owning the VisualStateManager so you should move the VisualStateManager to the Window to keep using this in the code-behind.

    private void Window_SizeChanged(object sender, SizeChangedEventArgs e) {
        if (e.PreviousSize.Width < 1024) {
            var result = VisualStateManager.GoToElementState(this, "WideState", true); // <- Here
        } else {
            VisualStateManager.GoToElementState(this, "DefaultState", false);
        }
    }
    

    On a side note, you cannot animate the Background like this. You need to define a Value that is a Brush and not just a Color:

    <DiscreteObjectKeyFrame KeyTime="0">
        <DiscreteObjectKeyFrame.Value>
            <SolidColorBrush Color="Red"/>
        </DiscreteObjectKeyFrame.Value>
    </DiscreteObjectKeyFrame>