WPF. Выполнение выполнения анимации в зависимости от свойства связанного элемента данных.

У меня есть объект данных - пользовательский класс под названием Notification, который предоставляет свойство IsCritical. Идея состоит в том, что, если уведомление истечет, оно имеет период действия и внимание пользователя должно быть обращено на него.

Представьте сценарий с этими тестовыми данными:

_source = new[] {
    new Notification { Text = "Just thought you should know" },
    new Notification { Text = "Quick, run!", IsCritical = true },
  };

Второй элемент должен появиться в ItemsControl с импульсным фоном. Здесь простейший пример шаблона данных, который показывает средства, с помощью которых я думал об анимации фона между серым и желтым.

<DataTemplate DataType="Notification">
  <Border CornerRadius="5" Background="#DDD">
    <Border.Triggers>
      <EventTrigger RoutedEvent="Border.Loaded">
        <BeginStoryboard>
          <Storyboard>
            <ColorAnimation 
              Storyboard.TargetProperty="Background.Color"
              From="#DDD" To="#FF0" Duration="0:0:0.7" 
              AutoReverse="True" RepeatBehavior="Forever" />
          </Storyboard>
        </BeginStoryboard>
      </EventTrigger>
    </Border.Triggers>
    <ContentPresenter Content="{TemplateBinding Content}" />
  </Border>
</DataTemplate>

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

Ответы

Ответ 1

Последняя часть этой головоломки... DataTriggers. Все, что вам нужно сделать, это добавить один DataTrigger в свой DataTemplate, привязать его к свойству IsCritical, и всякий раз, когда это правда, в нем EnterAction/ExitAction вы начинаете и останавливаете выделение раскадровки. Вот полностью работающее решение с некоторыми жестко закодированными ярлыками (вы можете определенно сделать лучше):

Xaml

<Window x:Class="WpfTest.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Notification Sample" Height="300" Width="300">
  <Window.Resources>
    <DataTemplate x:Key="NotificationTemplate">
      <Border Name="brd" Background="Transparent">
        <TextBlock Text="{Binding Text}"/>
      </Border>
      <DataTemplate.Triggers>
        <DataTrigger Binding="{Binding IsCritical}" Value="True">
          <DataTrigger.EnterActions>
            <BeginStoryboard Name="highlight">
              <Storyboard>
                <ColorAnimation 
                  Storyboard.TargetProperty="(Panel.Background).(SolidColorBrush.Color)"
                  Storyboard.TargetName="brd"
                  From="#DDD" To="#FF0" Duration="0:0:0.5" 
                  AutoReverse="True" RepeatBehavior="Forever" />
              </Storyboard>
            </BeginStoryboard>
          </DataTrigger.EnterActions>
          <DataTrigger.ExitActions>
            <StopStoryboard BeginStoryboardName="highlight"/>
          </DataTrigger.ExitActions>
        </DataTrigger>
      </DataTemplate.Triggers>
    </DataTemplate>
  </Window.Resources>
  <Grid>
    <Grid.RowDefinitions>
      <RowDefinition Height="*"/>
      <RowDefinition Height="Auto"/>
    </Grid.RowDefinitions>
    <ItemsControl ItemsSource="{Binding Notifications}"
                  ItemTemplate="{StaticResource NotificationTemplate}"/>
    <Button Grid.Row="1"
            Click="ToggleImportance_Click"
            Content="Toggle importance"/>
  </Grid>
</Window>

Код за:

using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;

namespace WpfTest
{
  public partial class Window1 : Window
  {
    public Window1()
    {
      InitializeComponent();
      DataContext = new NotificationViewModel();
    }

    private void ToggleImportance_Click(object sender, RoutedEventArgs e)
    {
      ((NotificationViewModel)DataContext).ToggleImportance();
    }
  }

  public class NotificationViewModel
  {
    public IList<Notification> Notifications
    {
      get;
      private set;
    }

    public NotificationViewModel()
    {
      Notifications = new List<Notification>
                        {
                          new Notification
                            {
                              Text = "Just thought you should know"
                            },
                          new Notification
                            {
                              Text = "Quick, run!",
                              IsCritical = true
                            },
                        };
    }

    public void ToggleImportance()
    {
      if (Notifications[0].IsCritical)
      {
        Notifications[0].IsCritical = false;
        Notifications[1].IsCritical = true;
      }
      else
      {
        Notifications[0].IsCritical = true;
        Notifications[1].IsCritical = false;
      }
    }
  }

  public class Notification : INotifyPropertyChanged
  {
    private bool _isCritical;

    public string Text { get; set; }

    public bool IsCritical
    {
      get { return _isCritical; }
      set
      {
        _isCritical = value;
        InvokePropertyChanged("IsCritical");
      }
    }

    public event PropertyChangedEventHandler PropertyChanged;

    private void InvokePropertyChanged(string name)
    {
      var handler = PropertyChanged;
      if (handler != null)
      {
        handler(this, new PropertyChangedEventArgs(name));
      }
    }
  }
}

Надеюсь, это поможет:).

Ответ 2

Что бы я сделал, это создать два DataTemplates и использовать DataTemplateSelector. Ваш XAML будет примерно таким:

<ItemsControl
ItemsSource="{Binding ElementName=Window, Path=Messages}">
<ItemsControl.Resources>
    <DataTemplate
        x:Key="CriticalTemplate">
        <Border
            CornerRadius="5"
            Background="#DDD">
            <Border.Triggers>
                <EventTrigger
                    RoutedEvent="Border.Loaded">
                    <BeginStoryboard>
                        <Storyboard>
                            <ColorAnimation
                                Storyboard.TargetProperty="Background.Color"
                                From="#DDD"
                                To="#FF0"
                                Duration="0:0:0.7"
                                AutoReverse="True"
                                RepeatBehavior="Forever" />
                        </Storyboard>
                    </BeginStoryboard>
                </EventTrigger>
            </Border.Triggers>
            <TextBlock
                Text="{Binding Path=Text}" />
        </Border>
    </DataTemplate>
    <DataTemplate
        x:Key="NonCriticalTemplate">
        <Border
            CornerRadius="5"
            Background="#DDD">
            <TextBlock
                Text="{Binding Path=Text}" />
        </Border>
    </DataTemplate>
</ItemsControl.Resources>
<ItemsControl.ItemsPanel>
    <ItemsPanelTemplate>
        <StackPanel />
    </ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplateSelector>
    <this:CriticalItemSelector
        Critical="{StaticResource CriticalTemplate}"
        NonCritical="{StaticResource NonCriticalTemplate}" />
</ItemsControl.ItemTemplateSelector>

И DataTemplateSelector будет похож на:

class CriticalItemSelector : DataTemplateSelector
{
    public DataTemplate Critical
    {
        get;
        set;
    }

    public DataTemplate NonCritical
    {
        get;
        set;
    }

    public override DataTemplate SelectTemplate(object item, 
            DependencyObject container)
    {
        Message message = item as Message;
        if(item != null)
        {
            if(message.IsCritical)
            {
                return Critical;
            }
            else
            {
                return NonCritical;
            }
        }
        else
        {
            return null;
        }
    }
}

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

Ответ 3

Кажется, что это имеет место с ColorAnimation, так как отлично работает с DoubleAnimation. Вам нужно объяснить, что раскадровка свойства TargetName для работы с ColorAnimation

    <Window.Resources>

    <DataTemplate x:Key="NotificationTemplate">

        <DataTemplate.Triggers>
            <DataTrigger Binding="{Binding Path=IsCritical}" Value="true">
                <DataTrigger.EnterActions>
                    <BeginStoryboard>
                        <Storyboard>
                            <ColorAnimation 
                                Storyboard.TargetProperty="Background.Color"
                                Storyboard.TargetName="border"
                                From="#DDD" To="#FF0" Duration="0:0:0.7" 
                                AutoReverse="True" RepeatBehavior="Forever" />
                        </Storyboard>
                    </BeginStoryboard>
                </DataTrigger.EnterActions>
            </DataTrigger>
        </DataTemplate.Triggers>

        <Border x:Name="border" CornerRadius="5" Background="#DDD" >
            <TextBlock Text="{Binding Text}" />
        </Border>

    </DataTemplate>

</Window.Resources>

<Grid>
    <ItemsControl x:Name="NotificationItems" ItemsSource="{Binding}" ItemTemplate="{StaticResource NotificationTemplate}" />
</Grid>

Ответ 4

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

Предполагая, что IsCritical привязан к элементу управления (или даже невидимому элементу управления), вы добавляете NotifyOnTargetUpdated к привязке и привязываете EventTrigger к событию Binding.TargetUpdated. Затем вы расширяете элемент управления только для запуска события TargetUpdated, когда входящее значение является тем, которое вас интересует. Итак...

public class CustomTextBlock : TextBlock
    {
        public CustomTextBlock()
        {
            base.TargetUpdated += new EventHandler<DataTransferEventArgs>(CustomTextBlock_TargetUpdated);
        }

        private void CustomTextBlock_TargetUpdated(object sender, DataTransferEventArgs e)
        {
            // don't fire the TargetUpdated event if the incoming value is false
            if (this.Text == "False") e.Handled = true;
        }
    }

и в файле XAML.

<DataTemplate>
..
<Controls:CustomTextBlock x:Name="txtCustom" Text="{Binding Path=IsCritical, NotifyOnTargetUpdated=True}"/>
..
<DataTemplate.Triggers>
<EventTrigger SourceName="txtCustom" RoutedEvent="Binding.TargetUpdated">
  <BeginStoryboard>
    <Storyboard>..</Storyboard>
  </BeginStoryboard>
</EventTrigger>
</DataTemplate.Triggers>
</DataTemplate>

Ответ 5

В этом случае используются триггеры стиля. (Я делаю это из памяти, поэтому могут быть некоторые ошибки)

  <Style TargetType="Border">
    <Style.Triggers>
      <DataTrigger Binding="{Binding IsCritical}" Value="true">
        <Setter Property="Triggers">
         <Setter.Value>
            <EventTrigger RoutedEvent="Border.Loaded">
              <BeginStoryboard>
                <Storyboard>
                  <ColorAnimation 
                    Storyboard.TargetProperty="Background.Color"
                    From="#DDD" To="#FF0" Duration="0:0:0.7" 
                    AutoReverse="True" RepeatBehavior="Forever" />
                </Storyboard>
              </BeginStoryboard>
            </EventTrigger>
         </Setter.Value>
        </Setter>
      </DataTrigger>  
    </Style.Triggers>
  </Style>