为什么 VisualStateManager.GoToState 不适用于 WPF 中的控件?

问题描述

我正在开发 WPF 应用程序并尝试使用 Visual State Manager 制作响应式 UI,正如 MSDN 建议的 UWP 响应式设计,它适用于 UWP,但不适用于 WPF。

这是我在 WPF 上测试的示例。它应该在启动时将 StackPanel 的背景更改为 Red

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>

和 C# 代码

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

解决方法

根据documentation

如果要更改在其 ControlTemplate 中使用 VisualStateManager 的控件中的状态,请调用 GoToState 方法。调用 GoToElementState 方法以更改 ControlTemplate 外部元素的状态(例如,如果您在 UserControl 或单个元素中使用 VisualStateManager)。

因此您应该使用 GoToElementState 而不是 GoToStateGoToElementState 的第一个参数是拥有 VisualStateManager 的控件,因此您应该将 VisualStateManager 移动到 Window 以继续在代码隐藏中使用 this

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);
    }
}

顺便说一句,您不能像这样为 Background 设置动画。您需要定义一个 Value,它是一个 Brush 而不仅仅是一个 Color

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