2009-08-18 6 views
14

J'ai un objet de données - une classe personnalisée appelée Notification - qui expose une propriété IsCritical. L'idée étant que si une notification expire, elle a une période de validité et l'attention de l'utilisateur doit être attirée sur elle.WPF - Conditionnant l'exécution d'une animation à une propriété de l'élément de données lié

Imaginez un scénario avec ces données de test:

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

Le deuxième élément doit apparaître dans la ItemsControl avec un fond pulsant. Voici un extrait de modèle de données simple qui montre les moyens par lesquels je pensais animer l'arrière-plan entre le gris et le jaune.

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

Ce que je ne suis pas sûr est de savoir comment faire cette animation conditionnelle à la valeur de IsCritical. Si la valeur liée est false, la couleur d'arrière-plan par défaut #DDD doit être conservée.

Répondre

11

La dernière partie de ce casse-tête est ... DataTriggers. Tout ce que vous avez à faire est d'ajouter un DataTrigger à votre DataTemplate, de le lier à la propriété IsCritical, et à chaque fois que c'est vrai, dans EnterAction/ExitAction, vous démarrez et arrêtez de mettre en évidence le storyboard. Voici la solution de travail complètement avec des raccourcis codés en dur (vous pouvez certainement faire mieux):

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> 

code derrière:

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

Hope this helps:).

+1

@Anvanka - merci pour cela. Je n'avais pas utilisé DataTrigger EnterActions ou ExitActions auparavant. Merci aussi pour l'exemple détaillé - une excellente réponse et digne de la prime. –

+0

Vous êtes les bienvenus :). Je suis heureux d'avoir pu aider. – Anvaka

0

Vous utilisez des déclencheurs de style dans ce cas. (Je fais cela de la mémoire afin qu'il pourrait y avoir quelques bugs)

<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

semble prometteur, Merci. Laissez-moi essayer et revenir à vous. –

+1

Non, ne fonctionne pas. Obtenez l'erreur: 'Les déclencheurs de Setter de propriété ne peuvent pas être définis car ils n'ont pas d'accesseur de jeu accessible.' –

+0

Eh bien, cela va être un peu plus complexe que je ne peux en faire tout de suite. Je suis sûr qu'il y a un moyen de le faire, mais vous devrez probablement y aller d'une manière complètement différente. Bonne opportunité de lire sur les déclencheurs ... – Will

2

Ce que je voudrais faire est de créer deux DataTemplates et utiliser un DataTemplateSelector. Votre XAML serait quelque chose comme:

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

Et le DataTemplateSelector serait quelque chose de similaire à:

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

De cette façon, WPF tout régler automatiquement qui est essentiel pour le modèle avec la animation, et tout le reste sera l'autre modèle. Ceci est également générique car plus tard, vous pourriez utiliser une propriété différente pour changer les modèles et/ou ajouter d'autres modèles (schéma d'importance faible/normale/élevée).

+0

Ceci est une réponse intéressante, mais ce n'est pas aussi flexible que je le voudrais. Par exemple, que se passe-t-il si plusieurs éléments du modèle de données doivent être animés en fonction de l'état de différentes propriétés? Dans mon cas aussi le modèle de données réel est beaucoup plus compliqué que juste 'donc j'introduirais beaucoup de duplication sur mon XAML via ceci. Cela pourrait convenir à certaines personnes. +1 pour l'explication détaillée! –

2

Il semble être une odité avec ColorAnimation, car il fonctionne bien avec DoubleAnimation. Vous devez spécifier les storyboards explicity propriété « TargetName » pour travailler avec 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 - merci pour votre réponse. Avec votre montage, cela correspond à mes besoins, mais @Anvanka vous a donné une réponse correcte (fondamentalement la même), donc je l'ai donné à lui/elle. +1 tout de même. –

1

Voici une solution qui démarre uniquement l'animation lorsque la mise à jour de la propriété entrante est une certaine valeur. Utile si vous voulez attirer l'attention de l'utilisateur sur quelque chose avec l'animation, mais que l'interface utilisateur doit ensuite revenir à son état par défaut.

En supposant que IsCritical soit lié à un contrôle (ou même à un contrôle invisible), vous ajoutez NotifyOnTargetUpdated à la liaison et liez un événement EventTrigger à l'événement Binding.TargetUpdated. Ensuite, vous étendez le contrôle à ne déclencher l'événement TargetUpdated lorsque la valeur d'entrée est celui qui vous intéresse. Alors ...

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

et dans le fichier 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> 
Questions connexes