Как лучше всего запустить анимацию при изменении связанного значения?

Это ситуация, которая возникает часто:

В представлении у вас есть элемент управления, привязанный к свойству ViewModel (поддерживаемый INotifyPropertyChanged). Например:

<TextBlock Text="{Binding Path=Subtotal}"/>

Когда свойство изменяется, вам нужно привлечь внимание пользователя к этому факту с помощью творческой анимации. Как я могу использовать тот факт, что представление уже подключено к уведомлению, и избежать создания большей части дополнительного кода (или, по крайней мере, создать его один раз и использовать повторно). Триггеры данных, вероятно, лучший выбор, но я не знаю, как заставить их срабатывать при любом изменении значения по сравнению с каким-то конкретным значением.

На ум приходят следующие варианты:

  • вызвать дополнительное событие в ViewModel, подписаться в View code-behind.
  • создать триггер данных, привязанный к упомянутому свойству, с помощью преобразователя, который вернет истину, если значение изменяется.
  • создать триггер данных, привязанный к новому логическому свойству ViewModel, которое используется для «сигнала» об изменении.
  • создать поведение, прикрепленное к элементу управления, которое будет подписываться на изменение свойства зависимости элемента управления и запускать анимацию.

Какой из них вам нравится / пользуетесь? Я пропустил какие-то варианты?

P.S. Было бы неплохо (но не критично), если бы решение позволяло сначала запускать анимацию и отражать изменение значения по ее окончании.


person Sergey Aldoukhov    schedule 13.07.2009    source источник


Ответы (2)


Хорошо, это то, к чему я пришел после некоторых экспериментов.

Я создал триггер Expression Blend 3 со свойством зависимости (я назвал его подпиской). Я привязываю подписку к тому же значению, к которому привязан мой TextBlock, и этот триггер прикреплен к ControlStoryboardAction из Expression Blend 3.

Вот триггер:

public class DataTriggerPlus : TriggerBase<DependencyObject>
{
    public static readonly DependencyProperty SubscriptionProperty =
        DependencyProperty.Register("Subscription", 
            typeof(string),
            typeof(DataTriggerPlus),
            new FrameworkPropertyMetadata("",
              new PropertyChangedCallback(OnSubscriptionChanged)));

    public string Subscription
    {
        get { return (string)GetValue(SubscriptionProperty); }
        set { SetValue(SubscriptionProperty, value); }
    }

    private static void OnSubscriptionChanged(DependencyObject d,
      DependencyPropertyChangedEventArgs e)
    {
        ((DataTriggerPlus)d).InvokeActions(null);
    }
}

Вот как он прикреплен к раскадровке:

<TextBlock x:Name="textBlock" Text="{Binding TestProp}" Background="White">
    <i:Interaction.Triggers>
        <local:DataTriggerPlus Subscription="{Binding TestProp}">
            <im:ControlStoryboardAction 
                Storyboard="{StaticResource Storyboard1}"/>
        </local:DataTriggerPlus>
    </i:Interaction.Triggers>
</TextBlock>

Мне очень нравится этот подход, отличная работа дизайнеров Blend 3!

Изменить: отвечая на комментарий Дрю ...

Да, он поставляется с Blend. Вы можете просто включить в свой проект Microsoft.Expression.Interactions.dll и System.Windows.Interactivity.

И да, это многословно (я спросил, придумал ли кто-нибудь хороший способ применения поведения через стили в этом вопросе), но есть и преимущество гибкости. Например, вы можете не только запустить раскадровку, но и переключить состояние или выполнить какое-либо другое действие с того же триггера.

person Sergey Aldoukhov    schedule 14.07.2009
comment
Откуда <i:Interaction.Triggers>? Я предполагаю, что он поставляется с Blend. Доступен ли он нам, пользователям, не использующим Blend? Я хочу добиться того же в своем коде, хотя надеюсь, что существует менее подробное решение, поскольку у меня есть много таких случаев, которые требуют этого. Может быть, это возможно с помощью стиля ... - person Drew Noakes; 09.11.2009

Вы можете создать триггер, который запустит анимацию.

Что-то вроде этого:

<Style>
    <Style.Triggers>
       <Trigger 
            Property="ViewModelProperty"
            Value="True">
            <Trigger.EnterActions>
                 <BeginStoryboard Storyboard="YourStoryBoard" />
            </Trigger.EnterActions>
       </Trigger>
    </Style.Triggers>
</Style>

Что касается проблемы с установкой значения после завершения анимации, это немного неудобно. Насколько мне известно, вам нужно будет использовать завершенное событие в раскадровке, для этого требуется код позади, чего вы хотите избежать с помощью MVVM.

Я пробовал использовать EventTrigger для привязки к завершенным событиям, но это также вызывает некоторые сложности. Дополнительные сведения см. здесь.

person Chris Nicol    schedule 13.07.2009
comment
Опять же, что, если свойство не является логическим или набором вариантов, а является произвольной строкой или числом? - person Sergey Aldoukhov; 14.07.2009
comment
Тогда вы должны использовать ValueConverter. То же решение, только с valueConverter - person Chris Nicol; 14.07.2009
comment
Как преобразователь значений узнает, что значение изменилось, если вы не создали преобразователь значений для каждой отдельной привязки? - person Drew Noakes; 09.11.2009
comment
Если значение изменится, оно снова пройдет через преобразователь. Конвертер не знает и не должен знать ничего, кроме того, что он принимает и что возвращает. - person Chris Nicol; 09.11.2009
comment
Не могли бы вы конкретизировать приведенный выше пример кода, чтобы объяснить свои ответы в этих комментариях, пожалуйста? Я хотел бы увидеть, что именно вы имеете в виду, говоря об использовании преобразователей значений для анимации изменений произвольно типизированных свойств модели представления. Спасибо. - person Mal Ross; 04.06.2010