18

以下问题已经困扰我好几天了,但我只能将其提炼成最简单的形式。考虑以下 XAML:

<Window x:Class="VSMTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">

    <Window.Resources>
        <Style TargetType="CheckBox">
            <Setter Property="Margin" Value="3"/>
            <Setter Property="Template">
                <Setter.Value>
                    <ControlTemplate TargetType="CheckBox">
                        <Grid x:Name="Root">
                            <Grid.Background>
                                <SolidColorBrush x:Name="brush" Color="White"/>
                            </Grid.Background>

                            <VisualStateManager.VisualStateGroups>
                                <VisualStateGroup Name="CheckStates">
                                    <VisualStateGroup.Transitions>
                                        <VisualTransition To="Checked" GeneratedDuration="00:00:03">
                                            <Storyboard Name="CheckingStoryboard">
                                                <ColorAnimationUsingKeyFrames Storyboard.TargetName="brush" Storyboard.TargetProperty="Color">
                                                    <DiscreteColorKeyFrame KeyTime="0" Value="LightGreen"/>
                                                </ColorAnimationUsingKeyFrames>
                                            </Storyboard>
                                        </VisualTransition>

                                        <VisualTransition To="Unchecked" GeneratedDuration="00:00:03">
                                            <Storyboard Name="UncheckingStoryboard">
                                                <ColorAnimationUsingKeyFrames Storyboard.TargetName="brush" Storyboard.TargetProperty="Color">
                                                    <DiscreteColorKeyFrame KeyTime="0" Value="LightSalmon"/>
                                                </ColorAnimationUsingKeyFrames>
                                            </Storyboard>
                                        </VisualTransition>
                                    </VisualStateGroup.Transitions>

                                    <VisualState Name="Checked">
                                        <Storyboard Name="CheckedStoryboard" Duration="0">
                                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="brush" Storyboard.TargetProperty="Color">
                                                <DiscreteColorKeyFrame KeyTime="0" Value="Green"/>
                                            </ColorAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>

                                    <VisualState Name="Unchecked">
                                        <Storyboard Name="UncheckedStoryboard" Duration="0">
                                            <ColorAnimationUsingKeyFrames Storyboard.TargetName="brush" Storyboard.TargetProperty="Color">
                                                <DiscreteColorKeyFrame KeyTime="0" Value="Red"/>
                                            </ColorAnimationUsingKeyFrames>
                                        </Storyboard>
                                    </VisualState>
                                </VisualStateGroup>
                            </VisualStateManager.VisualStateGroups>

                            <ContentPresenter/>
                        </Grid>
                    </ControlTemplate>
                </Setter.Value>
            </Setter>
        </Style>
    </Window.Resources>

    <StackPanel>
        <CheckBox x:Name="cb1">Check Box 1</CheckBox>
        <CheckBox x:Name="cb2">Check Box 2</CheckBox>
        <CheckBox x:Name="cb3">Check Box 3</CheckBox>
    </StackPanel>
</Window>

它只是重新模板化CheckBox控件,使其背景取决于其状态:

  • 选中 = 绿色
  • 未选中 = 红色
  • 检查(过渡)=浅绿色
  • 取消选中(过渡)= 浅红色

因此,当您选中其中一个复选框时,您会希望它在短时间内变为浅绿色,然后变为绿色。同样,取消选中时,您希望它会在短时间内变为浅红色,然后变为红色。

它通常就是这样做的。但不总是。

玩这个程序足够长的时间(我可以在大约 30 秒内得到它),你会发现过渡动画有时在视觉状态下胜过它。也就是说,复选框在选中时将继续显示为浅绿色,在未选中时将继续显示为浅红色。这是一个屏幕截图,说明了我的意思,在转换配置为 3 秒后拍摄:

在此处输入图像描述

发生这种情况时,并不是因为控件没有成功转换到目标状态。它声称处于正确的状态。我通过在调试器中检查以下内容来验证这一点(对于上面屏幕截图记录的特定情况):

var vsgs = VisualStateManager.GetVisualStateGroups(VisualTreeHelper.GetChild(this.cb2, 0) as FrameworkElement);
var vsg = vsgs[0];
// this is correctly reported as "Unselected"
var currentState = vsg.CurrentState.Name;

如果我启用动画跟踪,当转换成功完成时,我会得到以下输出:

System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='44177654'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='UncheckedStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 : 
System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='6148812'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='UncheckedStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='8261103'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 : 
System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='36205315'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='UncheckedStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='18626439'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 : 
System.Windows.Media.Animation Start: 3 : Storyboard has been removed; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='44177654'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='UncheckedStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'
System.Windows.Media.Animation Stop: 3 : 
System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='36893403'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='CheckingStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 : 
System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='49590434'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='<null>'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 : 
System.Windows.Media.Animation Start: 3 : Storyboard has been removed; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='36893403'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='CheckingStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'
System.Windows.Media.Animation Stop: 3 : 
System.Windows.Media.Animation Start: 3 : Storyboard has been removed; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='49590434'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='<null>'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'
System.Windows.Media.Animation Stop: 3 : 
System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='16977025'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='CheckedStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 : 
System.Windows.Media.Animation Start: 3 : Storyboard has been removed; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='16977025'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='CheckedStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'
System.Windows.Media.Animation Stop: 3 : 
System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='16977025'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='CheckedStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 : 

当转换未能成功完成时,我得到以下输出:

System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='44177654'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='UncheckedStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 : 
System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='6148812'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='UncheckedStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='8261103'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 : 
System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='36205315'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='UncheckedStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='18626439'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 : 
System.Windows.Media.Animation Start: 3 : Storyboard has been removed; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='44177654'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='UncheckedStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'
System.Windows.Media.Animation Stop: 3 : 
System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='36893403'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='CheckingStoryboard'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 : 
System.Windows.Media.Animation Start: 1 : Storyboard has begun; Storyboard='System.Windows.Media.Animation.Storyboard'; Storyboard.HashCode='49590434'; Storyboard.Type='System.Windows.Media.Animation.Storyboard'; StoryboardName='<null>'; TargetElement='System.Windows.Controls.Grid'; TargetElement.HashCode='41837403'; TargetElement.Type='System.Windows.Controls.Grid'; NameScope='<null>'
System.Windows.Media.Animation Stop: 1 : 

前 12 行与转换成功时完全相同,但最后 10 行完全缺失!

我已经阅读了我能找到的所有 VSM 文档,但无法对这种不稳定的行为做出解释。

我是否可以假设这是 VSM 中的错误?此问题是否有任何已知的解释或解决方法?

4

4 回答 4

16

我已经能够识别并解决以下问题:

首先,我将我的 repro 项目降级到 .NET 3.5 并从CodePlex获取 WPF Toolkit 源代码。我将 WPF Toolkit 项目添加到我的解决方案中,并从 Repro 项目中添加了对它的引用。

接下来,我运行了该应用程序并确保我仍然可以重现该问题。果然,这很容易做到。

然后我破解了VisualStateManager.cs文件并开始在关键位置添加一些诊断程序,这些诊断程序会告诉我哪些代码正在运行,哪些没有运行。通过添加这些诊断并比较从良好转换到不良转换的输出,我很快能够确定当问题出现时以下代码没有运行:

// Hook up generated Storyboard's Completed event handler
dynamicTransition.Completed += delegate
{
    if (transition.Storyboard == null ||
        transition.ExplicitStoryboardCompleted)
    {
        if (ShouldRunStateStoryboard(control, element, state, group))
        {
            group.StartNewThenStopOld(element, state.Storyboard);
        }

        group.RaiseCurrentStateChanged(element, lastState, state,
                                        control);
    }

    transition.DynamicStoryboardCompleted = true;
};

因此,错误的性质从 VSM 中的问题转变为Storyboard.Completed事件中并不总是被提出的问题。这是我以前遇到过的一个问题,对于任何在动画方面做任何不寻常的事情的 WPF 开发人员来说,这似乎是一个非常焦虑的根源。

在整个过程中,我将我的发现发布在WPF Disciples google group上,而正是在这一点上,Pavan Podila回复了这个 gem:

肯特,

过去我遇到过故事板没有触发完成的事件的问题。我意识到,如果您直接替换 Storyboard,而不先停止它,您可能会看到一些乱序的 Completed 事件。在我的情况下,我将更新的 Storyboard 应用到同一个 FrameworkElement,而没有停止早期的 Storyboard,这给了我一些问题。不确定你的情况是否相似,但我想我会分享这个花絮。

帕万

有了这种洞察力,我在VisualStateManager.cs中更改了这一行:

group.StartNewThenStopOld(element, transition.Storyboard, dynamicTransition); 

对此:

var masterStoryboard = new Storyboard();

if (transition.Storyboard != null)
{
    masterStoryboard.Children.Add(transition.Storyboard);
}

masterStoryboard.Children.Add(dynamicTransition);
group.StartNewThenStopOld(element, masterStoryboard);

而且 - 你瞧 - 我以前间歇性失败的复制现在每次都在工作!

因此,这确实可以解决 WPF 动画子系统中的错误或奇怪行为。

于 2011-02-17T19:25:13.940 回答
2

It appears as though setting Duration="0" on the Checked and Unchecked storyboards was the culprit. Removing it fixes the problem. I'm not sure I understand why, unless the storyboard is linked to the corresponding transition in some way.

However, I think I found a cleaner solution for you anyway. If you change your ControlTemplate to this then it accomplishes the same thing without the Transitions...

<ControlTemplate TargetType="CheckBox">
  <Grid x:Name="Root">
      <Grid.Background>
          <SolidColorBrush x:Name="brush" Color="White"/>
      </Grid.Background>

      <VisualStateManager.VisualStateGroups>
          <VisualStateGroup Name="CheckStates">
              <VisualState Name="Checked">                                      
                  <Storyboard x:Name="CheckedStoryboard">
                      <ColorAnimationUsingKeyFrames Storyboard.TargetName="brush" Storyboard.TargetProperty="Color">
                        <DiscreteColorKeyFrame KeyTime="0" Value="LightGreen"/>
                        <DiscreteColorKeyFrame KeyTime="00:00:03" Value="Green"/>
                      </ColorAnimationUsingKeyFrames>
                  </Storyboard>
              </VisualState>

              <VisualState Name="Unchecked">
                  <Storyboard x:Name="UncheckedStoryboard">
                      <ColorAnimationUsingKeyFrames Storyboard.TargetName="brush" Storyboard.TargetProperty="Color">
                        <DiscreteColorKeyFrame KeyTime="0" Value="LightSalmon"/>
                        <DiscreteColorKeyFrame KeyTime="00:00:03" Value="Red"/>
                      </ColorAnimationUsingKeyFrames>
                  </Storyboard>
              </VisualState>
          </VisualStateGroup>
      </VisualStateManager.VisualStateGroups>

      <ContentPresenter/>
  </Grid>
</ControlTemplate>
于 2011-02-15T14:45:39.190 回答
0

这个问题最近在 WPF 4.5 中引起了我的丑陋。就我而言,看起来我的过渡在活动时被垃圾收集了,所以它有时从未触发 Completed 事件,也从未重置其动画。由于我的 Checked VisualState 基本上再次调用所有相同的属性以在其转换端点“修复”它们,因此该状态似乎已部分触发,但我不相信它曾经这样做过。

解决方案:我在 VisualTransitions 中取消了 GeneratedDuration 属性(我的转换运行速度比应有的速度慢,因此我将其关闭以尝试加快速度。)。我认为这个属性可以在给定的时间内“锚定”过渡。当我将该属性添加回转换时,它解决了我的问题,并且我的动画可以可靠地工作。

于 2014-05-13T17:13:22.023 回答
0

不知道这是否与您的问题有关,但我也偶然发现了 AnimationClock.Completed 在用另一个正在运行的动画替换运行动画时无法可靠触发的问题。我认为这是垃圾收集和引用/生根的问题。当 AnimationClock 仍在运行但不再以某种方式引用时,它可能会在任何时间点被垃圾收集。如果在垃圾收集发生之前到达终点,则触发 Completed,否则不触发。这导致了非常不可预测的行为。

我的解决方法是最初将我的时钟添加到某个集合中(以强制它被植根,从而防止垃圾收集)并在完成时将其从集合中删除,然后完成 100% 的时间被触发,并且没有内存泄漏。

就我的两分钱...

于 2011-09-22T19:46:54.467 回答