Анимации динамического просмотра с использованием MVVM

Я пытался выяснить, как эффективно запускать анимацию в представлении, когда свойство в модели представления обновляется, когда анимация зависит от значения указанного свойства.

Я воссоздал свою проблему в простом приложении с одним View и ViewModel. Цель здесь - передать изменение цвета прямоугольника с помощью ColorAnimation. Для справки я использовал пакет 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, чем просто привязываю их через ResourceDictionary. Полагаю, я мог бы сделать это и в событии DataContextChanged, но есть ли способ лучше?


person Will Eddins    schedule 08.05.2011    source источник
comment
Ссылка на проект больше не действительна.   -  person itsho    schedule 12.11.2013


Ответы (2)


Если бы только для нескольких анимаций, я бы рекомендовал использовать Visual States. Затем вы можете использовать поведение 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>

Когда вы щелкаете прямоугольник, цвет его должен измениться с черного на красный.

person Justin XL    schedule 08.05.2011
comment
У вас есть простой пример, желательно с точки зрения приведенного выше? Я пытался найти вещи в указанном вами направлении, но еще не понял этого. Как есть, я бы предположил, что объект Color может быть любого цвета с помощью отдельной модели просмотра. - person Will Eddins; 11.05.2011
comment
Я не совсем уверен, что я имел в виду именно это, и не могу найти способ применить это к моему конкретному случаю. В этом случае цветная анимация вообще не обращает внимания на модель просмотра и зависит от события. Обычно событием является изменение свойства Color в модели просмотра. - person Will Eddins; 15.05.2011
comment
могу я взглянуть на это маленькое приложение, которое вы создали? - person Justin XL; 15.05.2011

Я использовал код, который опубликовал Xin, и внес несколько очень незначительных изменений (код ниже). Всего 3 материальных отличия:

Я создал поведение для работы с любым UIElement, а не только с прямоугольником

Я использовал PropertyChangedTrigger вместо EventTrigger. Это позволяет мне отслеживать свойство цвета во ViewModel вместо того, чтобы отслеживать события щелчка.

Я привязал FillColor к свойству Color ViewModel.

Чтобы использовать это, вам нужно будет загрузить 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>


На самом деле это была идея Синя - я просто немного ее подчистил.

person JMarsch    schedule 10.06.2011