WPF - 使动画的执行以绑定数据项的属性为条件

时间:2009-08-18 14:44:06

标签: wpf xaml animation datatemplate coloranimation

我有一个数据对象 - 一个名为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的默认背景颜色。

5 个答案:

答案 0 :(得分:12)

这个难题的最后一部分是...... 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));
      }
    }
  }
}

希望这会有所帮助:)。

答案 1 :(得分: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将使用动画自动设置对模板至关重要的任何内容,其他所有内容都将是另一个模板。这也是通用的,稍后您可以使用不同的属性来切换模板和/或添加更多模板(低/正常/高重要性方案)。

答案 2 :(得分:2)

它似乎与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>

答案 3 :(得分:1)

这是一个只在传入属性更新为特定值时才启动动画的解决方案。如果您想将用户的注意力吸引到动画中的某些内容,则非常有用,但之后UI应该返回到默认状态。

假设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>

答案 4 :(得分:0)

在这种情况下,您使用样式触发器。 (我是从记忆中做到的,所以可能会有一些错误)

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