以下问题一直困扰着我好几天,但我只能将它提炼到最简单的形式。请考虑以下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中的错误?这个问题是否有任何已知的解释或解决方法?
答案 0 :(得分: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已回复此宝石:
肯特,
我过去曾经遇到过没有解雇他们的故事板的问题 完成的活动。我意识到了什么 如果你更换故事板就是这样 直接,没有先停止, 你可能会看到一些无序的 完成的活动。就我而言,我是 将更新的故事板应用于同一个 FrameworkElement,无需停止 早期的故事板,那是 给我一些问题。不知道 你的情况类似,但我想 分享这个花絮。
帕
有了这种洞察力,我在 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动画子系统中的错误或奇怪的行为。
答案 1 :(得分:2)
似乎在Checked和Unchecked故事板上设置Duration="0"
是罪魁祸首。删除它可以解决问题。我不确定我理解为什么,除非故事板以某种方式与相应的过渡相关联。
但是,我认为无论如何我找到了一个更清洁的解决方案。如果您将ControlTemplate更改为此,那么它将完成相同的操作而不进行转换...
<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>
答案 2 :(得分:0)
不知道这是否与您的问题有关,但我也偶然发现了AnimationClock.Completed在用另一个替换正在运行的动画时无法可靠触发的问题。我认为这是垃圾收集和引用/生根的问题。当AnimationClock仍在运行但不再以某种方式引用时,它可能在任何时间点被垃圾收集。如果在垃圾收集发生之前到达结束,则触发Completed,否则不触发。这导致了一种非常不可预测的行为。
我的解决方法是最初将我的时钟添加到某个集合(以强制它为root,从而防止垃圾收集)并在完成时将其从集合中删除,然后在100%的时间内完成被触发,并且没有内存泄漏。
只是我的两分钱......
答案 3 :(得分:0)
这个问题最近在WPF 4.5中为我带来了丑陋的头脑。在我的情况下,看起来我的转换是在活动时收集垃圾,因此它有时从未触发Completed事件并且它从未重置其动画。由于我的Checked VisualState基本上再次调用了所有相同的属性来“修复”它们的过渡终点,所以看起来这个状态已经被部分解雇了,但我不相信它曾经做过。
解决方案:我在VisualTransitions中没有使用GeneratedDuration属性(我的转换运行速度比应该的速度慢,所以我把它关掉以试图加速它。)。我认为这个属性可以“锚定”给定时间的过渡。当我将属性添加回过渡时,它修复了我的问题,我的动画可以正常工作。