2009-08-18 19 views
14

我有一個數據對象 - 一個名爲Notification的自定義類,它公開了IsCritical屬性。這個想法是,如果通知過期,它有一段有效期,用戶應該注意它。WPF - 使動畫的執行以綁定數據項的屬性爲條件

想象的情景與此測試數據:

_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的默認背景色。

回答

11

這個難題的最後一部分是... 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

@Anvanka - 謝謝你。我之前沒有使用過DataTrigger EnterActions或ExitActions。還要感謝這個詳細的例子 - 一個很好的答案,值得獎勵。 – 2009-09-02 17:20:12

+0

不客氣:)。我很高興能夠提供幫助。 – Anvaka 2009-09-02 20:43:59

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

看起來很有希望,謝謝。讓我試試看,回到你身邊。 – 2009-08-18 15:12:18

+1

不,不起作用。獲取錯誤:'屬性設置'觸發器'不能被設置,因爲它沒有可訪問的設置存取器。' – 2009-08-18 15:18:11

+0

好吧,這會比我現在可以制定的複雜一點。我確信有一種方法可以做到這一點,但是你可能必須以完全不同的方式去做。很好的機會閱讀觸發器... – Will 2009-08-18 15:35:47

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將自動設置任何是與模板的關鍵動畫和其他一切將成爲另一個模板。這也是通用的,稍後您可以使用不同的屬性來切換模板和/或添加更多模板(低/正常/高重要性方案)。

+0

這是一個有趣的答案,但並不像我想的那麼靈活。例如,如果數據模板中有多個元素需要根據不同屬性的狀態進行動畫,會出現什麼情況?在我的情況下,實際的數據模板比僅僅是'要複雜得多,所以我會通過這個向我的XAML介紹很多重複。可能適合一些人。 +1的詳細解釋! – 2009-08-29 18:14:22

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

@TFD - 感謝您的回答。隨着你的編輯,它適合我的需求,但@Anvanka給你一個正確的答案(基本相同),所以我給了他/她。 +1都一樣。 – 2009-09-02 17:21:37

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>