使用 MVVM 从事件触发动画

发布于 2024-12-10 19:33:32 字数 2479 浏览 0 评论 0原文

我似乎已经达到了某种 MVVM 的突破点。

当底层视图模型对象的“Status”属性更改时,我希望控件的不透明度动画半秒(DoubleAnimation 从 0.5 到 1.0)。我首先使用 DataTrigger 实现了这一点,但由于我还没有找到对任何更改(只是给定值)做出反应的方法,因此在设置之前我必须始终将 VM 对象的“状态”属性翻转为特殊的“待处理”值达到其预期值。 (顺便说一句,有没有办法对任何更改做出反应?)

这很糟糕,所以我开始摆弄 EventTriggers...

这是我到目前为止所尝试过的:

  1. 使用正常的 EventTrigger

这似乎需要 RoutedEvent,但这又要求我的底层视图模型对象继承自 DependencyObject。

  1. 使用 i:Interaction.Triggers

这样我就可以监听正常的 .NET 事件并做出反应,但我还没有找到使用该方法启动 StoryBoard 的方法。

  1. 使用 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:

  1. Using a normal EventTrigger

This seems to require a RoutedEvent but that, in turn, requires that my underlying view model object inherits from DependencyObject.

  1. 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.

  1. Using i:Interaction.Triggers and writing a Behavior

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 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

飘过的浮云 2024-12-17 19:33:32

您可以简单地使用一个标志:在虚拟机上设置一个名为“RecentlyChangedFlag”的标志。每当适当的值发生变化时,将其脉冲设置为true,然后设置为false。您可以这样做:

private bool _changedFlag;
public bool ChangedFlag
{
    get
    {
        if (_changedFlag)
        {
            _changedFlag = false;
            OnPropertyChanged("ChangedFlag");
            return true;
        }
        // (else...)
        return false;
    }
    protected set
    {
        _changedFlag = value;
        OnPropertyChanged("ChangedFlag");
    }
}

即,当您想要指示动画开始时,使用上面的代码设置 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, then false, whenever the appropriate value(s) change. You could do that like this:

private bool _changedFlag;
public bool ChangedFlag
{
    get
    {
        if (_changedFlag)
        {
            _changedFlag = false;
            OnPropertyChanged("ChangedFlag");
            return true;
        }
        // (else...)
        return false;
    }
    protected set
    {
        _changedFlag = value;
        OnPropertyChanged("ChangedFlag");
    }
}

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 is true, as an EnterAction for instance.

Hope that helps.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文