[WPF][C#] VisualStateManager.GoToState在非UserControl中起不了作用的替代方案

  • 6738
  • 0
  • C#
  • 2013-07-14

隨著VisualStateManager和State的出現,讓我們在實作使用者介面的狀態轉換上省了很大的工夫,不過VisualStateManager.GoToState這個Method在Silverlight和WPF的UserControl中都可以運作得很正常(因為Silverlight的MainWindow也繼承了UserControl)。
但是,如果在WPF的Window控制項裡面要利用這個方法來切換State的話,那可是你呼叫它一百次它都不會理你的,你不會看到畫面有任何反應,也不會有任何Exception被丟出來,不過,如果透過GoToStateAction的話倒是可以運作得很開心。
難道說在Window控制項中就不能透過程式進行State間的轉換了嗎!?

 

隨著VisualStateManager和State的出現,讓我們在實作使用者介面的狀態轉換上省了很大的工夫,不過VisualStateManager.GoToState這個Method在Silverlight和WPF的UserControl中都可以運作得很正常(因為Silverlight的MainWindow也繼承了UserControl)。

但是,如果在WPF的Window控制項裡面要利用這個方法來切換State的話,那可是你呼叫它一百次它都不會理你的,你不會看到畫面有任何反應,也不會有任何Exception被丟出來,不過,如果透過GoToStateAction的話倒是可以運作得很開心。

難道說在Window控制項中就不能透過程式進行State間的轉換了嗎!?

讓我們來做個簡單的小實驗,建立一個WPF專案,並且建立一個UserControl:

TwoStateControl.xaml

<UserControl
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" xmlns:ee="http://schemas.microsoft.com/expression/2010/effects"
    mc:Ignorable="d"
    x:Class="Wpf_GoToStateSample.TwoStateControl"
    x:Name="UserControl"
    d:DesignWidth="640" d:DesignHeight="480">
 
    <Grid x:Name="LayoutRoot">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="VisualStateGroup" ei:ExtendedVisualStateManager.UseFluidLayout="True">
                <VisualStateGroup.Transitions>
                    <VisualTransition GeneratedDuration="0:0:1">
                        <ei:ExtendedVisualStateManager.TransitionEffect>
                            <ee:SlideInTransitionEffect/>
                        </ei:ExtendedVisualStateManager.TransitionEffect>
                    </VisualTransition>
                </VisualStateGroup.Transitions>
                <VisualState x:Name="State1">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="rectangle">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
                <VisualState x:Name="State2">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="ellipse">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Grid.RowDefinitions>
            <RowDefinition Height="0.917*"/>
            <RowDefinition Height="40"/>
        </Grid.RowDefinitions>
        <VisualStateManager.CustomVisualStateManager>
            <ei:ExtendedVisualStateManager/>
        </VisualStateManager.CustomVisualStateManager>
        <Rectangle x:Name="rectangle" Fill="Blue" Height="100" Stroke="Black" Width="100" Visibility="Collapsed"/>
        <Ellipse x:Name="ellipse" Fill="Red" Stroke="Black" Width="100" Height="100" Visibility="Collapsed"/>
        <Button Content="Change UserControl State" Grid.Row="1" d:LayoutOverrides="Height" Click="Button_Click" />
    </Grid>
</UserControl>

 

TwoStateControl.xaml.cs

using System.Windows;
using System.Windows.Controls;
 
namespace Wpf_GoToStateSample
{
    public partial class TwoStateControl : UserControl
    {
        public TwoStateControl()
        {
            this.InitializeComponent();
        }
 
        private void Button_Click( object sender , RoutedEventArgs e )
        {
            if( this.rectangle.Visibility == Visibility.Visible )
            {
                //這邊用VisualStateManager.GoToState可以跑得很開心
                VisualStateManager.GoToState( this , "State2" , true );
            }
            else
            {
                //這邊用VisualStateManager.GoToState可以跑得很開心
                VisualStateManager.GoToState( this , "State1" , true );
            }
        }
    }
}

 

接著把這個UserControl加到MainWindow中跑看看,如果沒意外的話,應該很開心的可以如下圖一樣的切換State。

image <-
->
image

 

再來我們要在MainWindow中依樣畫葫蘆,在MainWindow中也加入兩個State,並且讓它切換:

MainWindow.xaml

<Window
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions" 
    xmlns:ee="http://schemas.microsoft.com/expression/2010/effects" 
    xmlns:local="clr-namespace:Wpf_GoToStateSample" 
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
    mc:Ignorable="d"
    x:Class="Wpf_GoToStateSample.MainWindow"
    x:Name="Window"
    Title="MainWindow"
    Width="640" Height="480">
 
    <Grid x:Name="LayoutRoot">
        <VisualStateManager.VisualStateGroups>
            <VisualStateGroup x:Name="VisualStateGroup" ei:ExtendedVisualStateManager.UseFluidLayout="True">
                <VisualStateGroup.Transitions>
                    <VisualTransition GeneratedDuration="0:0:1">
                        <ei:ExtendedVisualStateManager.TransitionEffect>
                            <ee:FadeTransitionEffect/>
                        </ei:ExtendedVisualStateManager.TransitionEffect>
                    </VisualTransition>
                </VisualStateGroup.Transitions>
                <VisualState x:Name="State1"/>
                <VisualState x:Name="State2">
                    <Storyboard>
                        <ObjectAnimationUsingKeyFrames Storyboard.TargetProperty="(UIElement.Visibility)" Storyboard.TargetName="twoStateControl">
                            <DiscreteObjectKeyFrame KeyTime="0" Value="{x:Static Visibility.Visible}"/>
                        </ObjectAnimationUsingKeyFrames>
                    </Storyboard>
                </VisualState>
            </VisualStateGroup>
        </VisualStateManager.VisualStateGroups>
        <Grid.RowDefinitions>
            <RowDefinition/>
            <RowDefinition Height="40"/>
        </Grid.RowDefinitions>
        <VisualStateManager.CustomVisualStateManager>
            <ei:ExtendedVisualStateManager/>
        </VisualStateManager.CustomVisualStateManager>
        <Button Content="Change Window State" Grid.Row="1" Click="Button_Click" />
        <local:TwoStateControl x:Name="twoStateControl" d:LayoutOverrides="Height" Visibility="Collapsed" />
    </Grid>
</Window>

 

MainWindow.xaml.cs

using System.Windows;
 
namespace Wpf_GoToStateSample
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            this.InitializeComponent();
        }
 
        private void Button_Click( object sender , RoutedEventArgs e )
        {
            if( this.twoStateControl.Visibility == Visibility.Visible )
            {
                //這個寫法完全起不了作用
                VisualStateManager.GoToState( this , "State1" , true );
            }
            else
            {
                //這個寫法完全起不了作用
                VisualStateManager.GoToState( this , "State2" , true );
            }
 
        }
 
    }
}

 

很開心的執行看看的話,就會發現真的如同之前說的,完全起不了作用啊,一整個Orz....

不過!!!天無絕人之路!!還有兩招可以讓一切變得不一樣:

第一個方法是將原來的


VisualStateManager.GoToState( this , "State1" , true );

改成


VisualStateManager.GoToElementState( this.LayoutRoot , "State1" , true );

 

或是透過Microsoft.Expression.Interactivity.Core.ExtendedVisualStateManagerGoToElementState來達到一樣的目的~


Microsoft.Expression.Interactivity.Core.ExtendedVisualStateManager.GoToElementState( this.LayoutRoot , "State1" , true );

 

噹噹!!State又可以正常切換了!!

image <-
->
image

 

最後一樣奉上原始碼,請自行服用!!