使用 MVVM 从事件触发动画
我似乎已经达到了某种 MVVM 的突破点。
当底层视图模型对象的“Status”属性更改时,我希望控件的不透明度动画半秒(DoubleAnimation 从 0.5 到 1.0)。我首先使用 DataTrigger 实现了这一点,但由于我还没有找到对任何更改(只是给定值)做出反应的方法,因此在设置之前我必须始终将 VM 对象的“状态”属性翻转为特殊的“待处理”值达到其预期值。 (顺便说一句,有没有办法对任何更改做出反应?)
这很糟糕,所以我开始摆弄 EventTriggers...
这是我到目前为止所尝试过的:
- 使用正常的
EventTrigger
这似乎需要 RoutedEvent,但这又要求我的底层视图模型对象继承自 DependencyObject。
- 使用
i:Interaction.Triggers
这样我就可以监听正常的 .NET 事件并做出反应,但我还没有找到使用该方法启动 StoryBoard
的方法。
- 使用
i:Interaction.Triggers
并编写Behavior
这个实验失败了,因为我发现无法将自定义行为附加到其关联的控件。
这就是 XAML 的样子:
<cc:MyControl>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Updated">
<i:Interaction.Behaviors>
<cv:OpacityBehavior Duration="0:0:0:5" />
</i:Interaction.Behaviors>
</i:EventTrigger>
</i:Interaction.Triggers>
这是自定义行为:
class OpacityBehavior : Behavior<MyControl>
{
public Duration Duration { get; set; }
protected override void OnAttached()
{
base.OnAttached();
var animation = new DoubleAnimation(0.5, 1, Duration, FillBehavior.HoldEnd);
var associatedObject = lookupVisualParent(this);
associatedObject.BeginAnimation(UIElement.OpacityProperty, animation);
}
}
这不起作用,因为 XAML 解析器要求它直接附加到“MyControl”,但我需要将它附加到事件触发器。然后我尝试了这种方法:
class OpacityBehavior : Behavior<DependencyObject>
{
public Duration Duration { get; set; }
protected override void OnAttached()
{
base.OnAttached();
var animation = new DoubleAnimation(0.5, 1, Duration, FillBehavior.HoldEnd);
var associatedObject = lookupVisualParent(this);
associatedObject.BeginAnimation(UIElement.OpacityProperty, animation);
}
private UIElement lookupVisualParent(DependencyObject dObj)
{
if (dObj is UIElement)
return (UIElement) dObj;
if (dObj == null)
return null;
return lookupVisualParent(LogicalTreeHelper.GetParent(dObj));
}
}
由于 lookupVisualParent
不起作用,所以失败了。该行为的逻辑父级始终为null
。
我觉得这应该是一个相当常见的任务?这个问题有好的解决办法吗?我觉得很奇怪,我将编写视图模型类,以便它们从 DependencyObject 派生,以便在事件触发时启动动画。
干杯
I seem to have reached some sort of MVVM breaking point here.
I would like for a control to have its opacity animated for half a second (DoubleAnimation from 0.5 to 1.0) when the underlying view model object have its "Status" property changed. I achieved this at first using a DataTrigger but since I haven't found a way to react to ANY change, just a given value, I had to always flip the VM objects "Status" property to a special "pending" value before setting it to its intended value. (Is there a way to react to ANY change btw?)
This was hacky so I started fiddling with EventTriggers instead...
This is what I've tried so far:
- Using a normal
EventTrigger
This seems to require a RoutedEvent but that, in turn, requires that my underlying view model object inherits from DependencyObject.
- Using
i:Interaction.Triggers
That way I can listen to and react to normal .NET events but I haven't found a way to start a StoryBoard
using that approach.
- Using
i:Interaction.Triggers
and writing aBehavior
This experiment fell short on the fact I found no way to attach my custom behavior to its associated control.
This is what the XAML looked like:
<cc:MyControl>
<i:Interaction.Triggers>
<i:EventTrigger EventName="Updated">
<i:Interaction.Behaviors>
<cv:OpacityBehavior Duration="0:0:0:5" />
</i:Interaction.Behaviors>
</i:EventTrigger>
</i:Interaction.Triggers>
And here's the custom behavior:
class OpacityBehavior : Behavior<MyControl>
{
public Duration Duration { get; set; }
protected override void OnAttached()
{
base.OnAttached();
var animation = new DoubleAnimation(0.5, 1, Duration, FillBehavior.HoldEnd);
var associatedObject = lookupVisualParent(this);
associatedObject.BeginAnimation(UIElement.OpacityProperty, animation);
}
}
That didn't work because the XAML parser required it to be attached directly to "MyControl" but I need to attach it to the event trigger. I then tried this approach:
class OpacityBehavior : Behavior<DependencyObject>
{
public Duration Duration { get; set; }
protected override void OnAttached()
{
base.OnAttached();
var animation = new DoubleAnimation(0.5, 1, Duration, FillBehavior.HoldEnd);
var associatedObject = lookupVisualParent(this);
associatedObject.BeginAnimation(UIElement.OpacityProperty, animation);
}
private UIElement lookupVisualParent(DependencyObject dObj)
{
if (dObj is UIElement)
return (UIElement) dObj;
if (dObj == null)
return null;
return lookupVisualParent(LogicalTreeHelper.GetParent(dObj));
}
}
This failed on the fact that lookupVisualParent
doesn't work. The logical parent of the behavior is always null
.
It strikes me this should be a fairly common task? Is there a good solution to this problem? I find it strange that I will have write my view model classes so that they derive from DependencyObject
in order to start an animation when an event fires.
Cheers
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
您可以简单地使用一个标志:在虚拟机上设置一个名为“RecentlyChangedFlag”的标志。每当适当的值发生变化时,将其脉冲设置为
true
,然后设置为false
。您可以这样做:即,当您想要指示动画开始时,使用上面的代码设置
ChangedFlag = true
。 WPF 查询 true 值后会重置为 false。然后,当
RecentlyChangedFlag
的值为true
时发生动画,例如EnterAction
。希望有帮助。
You could simply use a flag: set a flag on your VM called 'RecentlyChangedFlag'. Pulse it to
true
, thenfalse
, whenever the appropriate value(s) change. You could do that like this:I.e., with the above code set
ChangedFlag = true
when you want to signal the animation to start. It will be reset to false after WPF queries the true value.Then have the animation occur when the value of
RecentlyChangedFlag
istrue
, as anEnterAction
for instance.Hope that helps.