2009-08-18 6 views
14

Ho un oggetto dati - una classe personalizzata denominata Notification - che espone una proprietà IsCritical. L'idea è che se una notifica scadrà, avrà un periodo di validità e l'attenzione dell'utente dovrebbe essere attirata verso di essa.WPF - Esecuzione di un'animazione condizionale su una proprietà dell'elemento di dati associato

immaginare uno scenario con questi dati di test:

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

Il secondo elemento deve apparire nella ItemsControl con uno sfondo pulsante. Ecco un semplice esempio di modello di dati che mostra i mezzi con cui stavo pensando di animare lo sfondo tra grigio e giallo.

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

Quello che io sono sicuro circa è come fare questa animazione subordinato il valore di IsCritical. Se il valore associato è false, è necessario mantenere il colore di sfondo predefinito di #DDD.

risposta

11

La parte finale di questo puzzle è ... DataTriggers. Tutto ciò che devi fare è aggiungere un DataTrigger al tuo DataTemplate, associarlo alla proprietà IsCritical e ogni volta che è vero, in EnterAction/ExitAction si avvia e si interrompe l'evidenziazione dello storyboard. Qui è completamente soluzione di lavoro con alcune scorciatoie hard-coded (si può sicuramente fare di meglio):

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> 

Codice dietro:

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)); 
     } 
    } 
    } 
} 

Spero che questo aiuti:).

+1

@Anvanka - grazie per questo. Non avevo usato DataTrigger EnterActions o ExitActions prima. Grazie anche per l'esempio dettagliato - una grande risposta e degna della generosità. –

+0

Siete i benvenuti :). Sono contento di poterti aiutare. – Anvaka

0

In questo caso si utilizzano i trigger di stile. (Sto facendo questo dalla memoria così ci potrebbe essere qualche bug)

<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

sembra essere molto promettente, Grazie. Fammi provare e tornare da te. –

+1

No, non funziona. Ottieni l'errore: 'Property Setter 'Trigger' non può essere impostato perché non ha un access access set accessibile. –

+0

Beh, questo sarà un po 'più complesso di quanto possa funzionare in questo momento. Sono sicuro che c'è un modo per farlo, ma probabilmente dovrai affrontarlo in un modo completamente diverso. Buona occasione per leggere i trigger ... – Will

2

Cosa vorrei fare è creare due DataTemplates e utilizzare un DataTemplateSelector. Il tuo XAML sarebbe qualcosa di simile:

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

E il DataTemplateSelector sarebbe qualcosa di simile a:

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

In questo modo, WPF imposterà automaticamente tutto ciò che è fondamentale per il modello con il animazione, e tutto il resto sarà l'altro modello. Questo è anche generico in quanto, in seguito, è possibile utilizzare una proprietà diversa per cambiare i modelli e/o aggiungere altri modelli (schema A basso/normale/alta importanza).

+0

Questa è una risposta interessante, ma non è flessibile come vorrei. Ad esempio, cosa succede se ci sono più elementi all'interno del modello di dati che devono essere animati a seconda dello stato delle diverse proprietà? Anche nel mio caso il modello di dati effettivo è molto più complicato di "' quindi introdurrò un sacco di duplicazioni al mio XAML tramite questo. Potrebbe adattarsi ad alcune persone però. +1 per la spiegazione dettagliata! –

2

Sembra che sia un odore con ColorAnimation, poiché funziona perfettamente con DoubleAnimation. È necessario specificare esplicitamente gli storyboard proprietà "TargetName" per lavorare con 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 - grazie per la tua risposta. Con la tua modifica, si adatta alle mie esigenze, ma @Anvanka ti ha colpito con una risposta corretta (sostanzialmente la stessa), quindi l'ho dato a lui/lei. +1 lo stesso. –

1

Ecco una soluzione che avvia l'animazione solo quando l'aggiornamento della proprietà in ingresso è un determinato valore. Utile se vuoi attirare l'attenzione dell'utente su qualcosa con l'animazione, ma in seguito l'interfaccia utente dovrebbe tornare al suo stato predefinito.

Assumendo che IsCritical sia associato a un controllo (o anche a un controllo invisibile), si aggiunge NotifyOnTargetAggiornato all'aggregazione e si associa un EventTrigger all'evento Binding.TargetUpdated. Poi si estende il controllo di al fuoco solo l'evento TargetUpdated quando il valore in arrivo è quello che interessa. Quindi ...

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

e nel file 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> 
Problemi correlati