使用 MVVM 的动态视图动画

发布于 2024-11-06 01:58:33 字数 3238 浏览 3 评论 0原文

我一直在试图弄清楚当 ViewModel 中的属性更新时如何有效地触发 View 中的动画,其中动画取决于所述属性的值。

我使用单个视图和视图模型在一个简单的应用程序中重新创建了我的问题。这里的目标是使用 ColorAnimation 来过渡矩形的颜色变化。作为参考,我一直在使用 Josh Smith 的 MVVM Foundation 包。

示例项目可以在此处下载。

总而言之,我想为颜色过渡设置动画每当 Color 属性更改时视图。

MainWindow.xaml

<Window x:Class="MVVM.ColorAnimation.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ColorAnimation="clr-namespace:MVVM.ColorAnimation" Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <ColorAnimation:MainWindowViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="40" />
        </Grid.RowDefinitions>

        <Rectangle Margin="10">
            <Rectangle.Fill>
                <SolidColorBrush Color="{Binding Color}"/>
            </Rectangle.Fill>
        </Rectangle>

        <StackPanel Orientation="Horizontal" Grid.Row="1">
            <Button Command="{Binding BlueCommand}" Width="100">Blue</Button>
            <Button Command="{Binding GreenCommand}" Width="100">Green</Button>
        </StackPanel>
    </Grid>
</Window>

MainWindowViewModel.cs

namespace MVVM.ColorAnimation
{
    using System.Windows.Input;
    using System.Windows.Media;

    using MVVM;

    public class MainWindowViewModel : ObservableObject
    {
        private ICommand blueCommand;
        private ICommand greenCommand;

        public ICommand BlueCommand
        {
            get
            {
                return this.blueCommand ?? (this.blueCommand = new RelayCommand(this.TurnBlue));
            }
        }

        private void TurnBlue()
        {
            this.Color = Colors.Blue;
        }

        public ICommand GreenCommand
        {
            get
            {
                return this.greenCommand ?? (this.greenCommand = new RelayCommand(this.TurnGreen));
            }
        }

        private void TurnGreen()
        {
            this.Color = Colors.Green;
        }

        private Color color = Colors.Red;

        public Color Color
        {
            get
            {
                return this.color;
            }

            set
            {
                this.color = value;
                RaisePropertyChanged("Color");
            }
        }     
    }
}

是否有从视图触发 ColorAnimation 而不是值之间的即时转换?我目前执行此操作的方式是另一个应用程序,非常混乱,因为我通过 ViewModel 属性设置 ViewModel,然后使用 PropertyObserver 监视值更改,然后创建动画并触发它来自代码隐藏:

this.colorObserver = new PropertyObserver<Player>(value)
    .RegisterHandler(n => n.Color, this.CreateColorAnimation);

在我处理许多颜色和许多潜在动画的情况下,这变得非常混乱,并且弄乱了我手动将 ViewModel 传递到 View 而不是简单地通过绑定两者的事实资源字典。我想我也可以在 DataContextChanged 事件中执行此操作,但是有更好的方法吗?

I've been trying to figure out how to effectively trigger animations in a View when a property in the ViewModel updates, where the animation depends on the value of said property.

I've recreated my problem in a simple application with a single View and ViewModel. The goal here is to transition the color change of a rectangle by using a ColorAnimation. For reference, I've been using the MVVM Foundation package by Josh Smith.

The example project can be downloaded here.

To summarize, I want to animate the color transition in the View whenever the Color property changes.

MainWindow.xaml

<Window x:Class="MVVM.ColorAnimation.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:ColorAnimation="clr-namespace:MVVM.ColorAnimation" Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <ColorAnimation:MainWindowViewModel />
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="40" />
        </Grid.RowDefinitions>

        <Rectangle Margin="10">
            <Rectangle.Fill>
                <SolidColorBrush Color="{Binding Color}"/>
            </Rectangle.Fill>
        </Rectangle>

        <StackPanel Orientation="Horizontal" Grid.Row="1">
            <Button Command="{Binding BlueCommand}" Width="100">Blue</Button>
            <Button Command="{Binding GreenCommand}" Width="100">Green</Button>
        </StackPanel>
    </Grid>
</Window>

MainWindowViewModel.cs

namespace MVVM.ColorAnimation
{
    using System.Windows.Input;
    using System.Windows.Media;

    using MVVM;

    public class MainWindowViewModel : ObservableObject
    {
        private ICommand blueCommand;
        private ICommand greenCommand;

        public ICommand BlueCommand
        {
            get
            {
                return this.blueCommand ?? (this.blueCommand = new RelayCommand(this.TurnBlue));
            }
        }

        private void TurnBlue()
        {
            this.Color = Colors.Blue;
        }

        public ICommand GreenCommand
        {
            get
            {
                return this.greenCommand ?? (this.greenCommand = new RelayCommand(this.TurnGreen));
            }
        }

        private void TurnGreen()
        {
            this.Color = Colors.Green;
        }

        private Color color = Colors.Red;

        public Color Color
        {
            get
            {
                return this.color;
            }

            set
            {
                this.color = value;
                RaisePropertyChanged("Color");
            }
        }     
    }
}

Is there anyway from the View to trigger a ColorAnimation instead of an instant transition between the values? The way I'm currently doing this is another application is quite messy, in that I set the ViewModel through a ViewModel property, and then using a PropertyObserver to monitor value changes, then create the Animation and trigger it from the codebehind:

this.colorObserver = new PropertyObserver<Player>(value)
    .RegisterHandler(n => n.Color, this.CreateColorAnimation);

In a situation where I'm dealing with many colors and many potential animations, this becomes quite a mess, and messes up the fact that I'm manually passing in the ViewModel to the View than simply binding the two through a ResourceDictionary. I suppose I could do this in the DataContextChanged event as well, but is there a better way?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

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

发布评论

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

评论(2

恋你朝朝暮暮 2024-11-13 01:58:33

如果只是为了一些动画,我建议使用视觉状态。然后您可以在视图上使用 GoToAction 行为来触发不同的动画。如果您正在处理很多类似的动画,创建自己的行为将是更好的解决方案。

更新
我创建了一个非常简单的行为来为矩形提供一点彩色动画。这是代码。

   public class ColorAnimationBehavior : TriggerAction<FrameworkElement>
    {
        #region Fill color
        [Description("The background color of the rectangle")]
        public Color FillColor
        {
            get { return (Color)GetValue(FillColorProperty); }
            set { SetValue(FillColorProperty, value); }
        }

        public static readonly DependencyProperty FillColorProperty =
            DependencyProperty.Register("FillColor", typeof(Color), typeof(ColorAnimationBehavior), null);
        #endregion

        protected override void Invoke(object parameter)
        {
            var rect = (Rectangle)AssociatedObject;

            var sb = new Storyboard();
            sb.Children.Add(CreateVisibilityAnimation(rect, new Duration(new TimeSpan(0, 0, 1)), FillColor));

            sb.Begin();
        }

        private static ColorAnimationUsingKeyFrames CreateVisibilityAnimation(DependencyObject element, Duration duration, Color color)
        {
            var animation = new ColorAnimationUsingKeyFrames();

            animation.KeyFrames.Add(new SplineColorKeyFrame { KeyTime = new TimeSpan(duration.TimeSpan.Ticks), Value = color });

            Storyboard.SetTargetProperty(animation, new PropertyPath("(Shape.Fill).(SolidColorBrush.Color)"));
            Storyboard.SetTarget(animation, element);

            return animation;
        }

    }

在 xaml 中,您只需像这样附加此行为,

    <Rectangle x:Name="rectangle" Fill="Black" Margin="203,103,217,227" Stroke="Black">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="MouseLeftButtonDown">
                <local:ColorAnimationBehavior FillColor="Red"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Rectangle>

当您单击矩形时,它应该从黑色变为红色。

If just for a few animations I would recommend using Visual States. Then you can use GoToAction behavior on the view to trigger different animations. If you are dealing with a lot of similar animations, creating your own behavior would be a better solution.

Update
I have created a very simple behaivor to give a Rectangle a little color animation. Here is the code.

   public class ColorAnimationBehavior : TriggerAction<FrameworkElement>
    {
        #region Fill color
        [Description("The background color of the rectangle")]
        public Color FillColor
        {
            get { return (Color)GetValue(FillColorProperty); }
            set { SetValue(FillColorProperty, value); }
        }

        public static readonly DependencyProperty FillColorProperty =
            DependencyProperty.Register("FillColor", typeof(Color), typeof(ColorAnimationBehavior), null);
        #endregion

        protected override void Invoke(object parameter)
        {
            var rect = (Rectangle)AssociatedObject;

            var sb = new Storyboard();
            sb.Children.Add(CreateVisibilityAnimation(rect, new Duration(new TimeSpan(0, 0, 1)), FillColor));

            sb.Begin();
        }

        private static ColorAnimationUsingKeyFrames CreateVisibilityAnimation(DependencyObject element, Duration duration, Color color)
        {
            var animation = new ColorAnimationUsingKeyFrames();

            animation.KeyFrames.Add(new SplineColorKeyFrame { KeyTime = new TimeSpan(duration.TimeSpan.Ticks), Value = color });

            Storyboard.SetTargetProperty(animation, new PropertyPath("(Shape.Fill).(SolidColorBrush.Color)"));
            Storyboard.SetTarget(animation, element);

            return animation;
        }

    }

In xaml, you simply attach this behavior like this,

    <Rectangle x:Name="rectangle" Fill="Black" Margin="203,103,217,227" Stroke="Black">
        <i:Interaction.Triggers>
            <i:EventTrigger EventName="MouseLeftButtonDown">
                <local:ColorAnimationBehavior FillColor="Red"/>
            </i:EventTrigger>
        </i:Interaction.Triggers>
    </Rectangle>

When you click the Rectangle, it should go from Black color to Red.

寻找一个思念的角度 2024-11-13 01:58:33

我使用了 Xin 发布的代码,并做了一些非常小的周(代码如下)。唯一的 3 个实质性差异:

我创建了适用于任何 UIElement 的行为,而不仅仅是矩形

我使用了 PropertyChangedTrigger 而不是 EventTrigger。这让我可以监视 ViewModel 上的颜色属性,而不是监听单击事件。

我将 FillColor 绑定到 ViewModel 的 Color 属性。

要使用它,您需要下载 Blend 4 SDK(它是免费的,只有在您还没有 Expression Blend 时才需要它),并添加对 System.Windows.Interactivity 和 Microsoft.Expression.Interactions 的引用

。行为类的代码:


// complete code for the animation behavior
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace ColorAnimationBehavior
{
    public class ColorAnimationBehavior: TriggerAction<UIElement>
    {
        public Color FillColor
        {
            get { return (Color)GetValue(FillColorProperty); }
            set { SetValue(FillColorProperty, value); }
        }

        public static readonly DependencyProperty FillColorProperty =
            DependencyProperty.Register("FillColor", typeof(Color), typeof(ColorAnimationBehavior), null);

        public Duration Duration
        {
            get { return (Duration)GetValue(DurationProperty); }
            set { SetValue(DurationProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Duration.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DurationProperty =
            DependencyProperty.Register("Duration", typeof(Duration), typeof(ColorAnimationBehavior), null);

        protected override void Invoke(object parameter)
        {
            var storyboard = new Storyboard();
            storyboard.Children.Add(CreateColorAnimation(this.AssociatedObject, this.Duration, this.FillColor));
            storyboard.Begin();
        }

        private static ColorAnimationUsingKeyFrames CreateColorAnimation(UIElement element, Duration duration, Color color)
        {
            var animation = new ColorAnimationUsingKeyFrames();
            animation.KeyFrames.Add(new SplineColorKeyFrame() { KeyTime = duration.TimeSpan, Value = color });
            Storyboard.SetTargetProperty(animation, new PropertyPath("(Shape.Fill).(SolidColorBrush.Color)"));
            Storyboard.SetTarget(animation, element);
            return animation;
        }
    }
}


现在这是将其连接到矩形的 XAML:


<UserControl x:Class="MVVM.ColorAnimation.Silverlight.MainPage"
    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:ColorAnimation="clr-namespace:MVVM.ColorAnimation"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
    xmlns:ca="clr-namespace:ColorAnimationBehavior;assembly=ColorAnimationBehavior"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <UserControl.DataContext>
        <ColorAnimation:MainWindowViewModel />
    </UserControl.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="40" />
        </Grid.RowDefinitions>

        <Rectangle x:Name="rectangle" Margin="10" Stroke="Black" Fill="Red">
            <i:Interaction.Triggers>
                <ei:PropertyChangedTrigger Binding="{Binding Color}">
                    <ca:ColorAnimationBehavior FillColor="{Binding Color}" Duration="0:0:0.5" />
                </ei:PropertyChangedTrigger>
            </i:Interaction.Triggers>
        </Rectangle>
        <StackPanel Orientation="Horizontal" Grid.Row="1">
            <Button Command="{Binding BlueCommand}" Width="100" Content="Blue"/>
            <Button Command="{Binding GreenCommand}" Width="100" Content="Green"/>
        </StackPanel>
    </Grid>
</UserControl>


这确实是 Xin 的想法 - 我只是稍微清理了一下。

I used the code that Xin posted, and made a few very minor tweeks (code is below). The only 3 material differences:

I created the behavior to work on any UIElement, not just a rectangle

I used a PropertyChangedTrigger instead of an EventTrigger. That let's me Monitor the color property on the ViewModel instead of listening for click events.

I bound the FillColor to the Color property of the ViewModel.

To use this, you will need to download the Blend 4 SDK (it's free, and you only need it if you don't already have Expression Blend), and add references to System.Windows.Interactivity, and Microsoft.Expression.Interactions

Here's the code for the behavior class:


// complete code for the animation behavior
using System.Windows;
using System.Windows.Interactivity;
using System.Windows.Media;
using System.Windows.Media.Animation;

namespace ColorAnimationBehavior
{
    public class ColorAnimationBehavior: TriggerAction<UIElement>
    {
        public Color FillColor
        {
            get { return (Color)GetValue(FillColorProperty); }
            set { SetValue(FillColorProperty, value); }
        }

        public static readonly DependencyProperty FillColorProperty =
            DependencyProperty.Register("FillColor", typeof(Color), typeof(ColorAnimationBehavior), null);

        public Duration Duration
        {
            get { return (Duration)GetValue(DurationProperty); }
            set { SetValue(DurationProperty, value); }
        }

        // Using a DependencyProperty as the backing store for Duration.  This enables animation, styling, binding, etc...
        public static readonly DependencyProperty DurationProperty =
            DependencyProperty.Register("Duration", typeof(Duration), typeof(ColorAnimationBehavior), null);

        protected override void Invoke(object parameter)
        {
            var storyboard = new Storyboard();
            storyboard.Children.Add(CreateColorAnimation(this.AssociatedObject, this.Duration, this.FillColor));
            storyboard.Begin();
        }

        private static ColorAnimationUsingKeyFrames CreateColorAnimation(UIElement element, Duration duration, Color color)
        {
            var animation = new ColorAnimationUsingKeyFrames();
            animation.KeyFrames.Add(new SplineColorKeyFrame() { KeyTime = duration.TimeSpan, Value = color });
            Storyboard.SetTargetProperty(animation, new PropertyPath("(Shape.Fill).(SolidColorBrush.Color)"));
            Storyboard.SetTarget(animation, element);
            return animation;
        }
    }
}


Now here's the XAML that hooks it up to your rectangle:


<UserControl x:Class="MVVM.ColorAnimation.Silverlight.MainPage"
    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:ColorAnimation="clr-namespace:MVVM.ColorAnimation"
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:ei="http://schemas.microsoft.com/expression/2010/interactions"
    xmlns:ca="clr-namespace:ColorAnimationBehavior;assembly=ColorAnimationBehavior"
    mc:Ignorable="d"
    d:DesignHeight="300" d:DesignWidth="400">

    <UserControl.DataContext>
        <ColorAnimation:MainWindowViewModel />
    </UserControl.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="*" />
            <RowDefinition Height="40" />
        </Grid.RowDefinitions>

        <Rectangle x:Name="rectangle" Margin="10" Stroke="Black" Fill="Red">
            <i:Interaction.Triggers>
                <ei:PropertyChangedTrigger Binding="{Binding Color}">
                    <ca:ColorAnimationBehavior FillColor="{Binding Color}" Duration="0:0:0.5" />
                </ei:PropertyChangedTrigger>
            </i:Interaction.Triggers>
        </Rectangle>
        <StackPanel Orientation="Horizontal" Grid.Row="1">
            <Button Command="{Binding BlueCommand}" Width="100" Content="Blue"/>
            <Button Command="{Binding GreenCommand}" Width="100" Content="Green"/>
        </StackPanel>
    </Grid>
</UserControl>


It was really Xin's idea -- I just cleaned it up a bit.

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