2009-08-18 5 views
14

Ich habe ein Datenobjekt - eine benutzerdefinierte Klasse namens Notification - die eine IsCritical-Eigenschaft freilegt. Die Idee ist, dass wenn eine Benachrichtigung abläuft, sie eine Gültigkeitsdauer hat und die Aufmerksamkeit des Benutzers darauf gerichtet sein sollte.WPF - Die Ausführung einer Animation von einer Eigenschaft des gebundenen Datenelements abhängig machen

ein Szenario mit diesen Testdaten Stellen Sie sich vor:

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

Das zweite Element in den ItemsControl mit einem pulsierenden Hintergrund erscheinen soll. Hier ist ein einfacher Auszug aus einer Datenvorlage, der die Mittel zeigt, mit denen ich daran gedacht habe, den Hintergrund zwischen Grau und Gelb zu animieren.

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

Was ich bin nicht sicher, wie diese Animation auf den Wert von IsCritical abhängig zu machen. Wenn der gebundene Wert false ist, sollte die Standardhintergrundfarbe #DDD beibehalten werden.

Antwort

11

Der letzte Teil des Puzzles arbeiten ... DataTriggers. Sie müssen lediglich einen DataTrigger zu Ihrer DataTemplate hinzufügen, ihn an die IsCritical-Eigenschaft binden, und wenn dies der Fall ist, starten und stoppen Sie in der EnterAction/ExitAction das Storyboard. Hier ist vollständig funktionierende Lösung mit einigen hartcodierte Verknüpfungen (Sie auf jeden Fall besser machen können):

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

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

hoffe, das hilft:).

+1

@Anvanka - danke dafür. Ich hatte DataTrigger EnterActions oder ExitActions noch nicht verwendet. Danke auch für das ausführliche Beispiel - eine gute Antwort und würdig der Prämie. –

+0

Gern geschehen :). Es freut mich, dass ich helfen konnte. – Anvaka

0

In diesem Fall verwenden Sie Style-Trigger. (Ich tue dies aus dem Gedächtnis so könnte es einige 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

sieht vielversprechend aus, Vielen Dank. Lass es mich ausprobieren und komme zu dir zurück. –

+1

Nein, funktioniert nicht. Erhalten Sie den Fehler: 'Property Setter 'Trigger' kann nicht festgelegt werden, da es keinen Zugriff auf zugreifbar Set verfügt. –

+0

Nun, das wird ein bisschen komplexer sein, als ich gerade jetzt ausarbeiten kann. Ich bin mir sicher, dass es einen Weg gibt, dies zu tun, aber Sie müssen wahrscheinlich einen völlig anderen Weg gehen. Gute Gelegenheit, sich über Trigger zu informieren ... – Will

2

Was ich möchte, ist zwei Datatemplates tun erstellen und einen Datatemplateselector verwenden.

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

Und das Datatemplateselector etwas ähnliches sein würde: Ihr XAML wäre so etwas wie sein

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

diese Weise WPF wird automatisch alles, was auf die Vorlage kritisch mit der Animation und alles andere wird die andere Vorlage sein. Dies ist auch generisch, da Sie später eine andere Eigenschaft verwenden können, um die Vorlagen zu wechseln und/oder weitere Vorlagen hinzuzufügen (Schema "Niedrig/Normal/Hoch").

+0

Dies ist eine interessante Antwort, aber es ist nicht so flexibel wie ich es möchte. Was passiert zum Beispiel, wenn innerhalb der Datenvorlage mehrere Elemente vorhanden sind, die abhängig vom Status verschiedener Eigenschaften animiert werden müssen? Auch in meinem Fall ist die eigentliche Datenvorlage viel komplizierter als nur '' also würde ich dadurch eine Menge Duplizierung in meinen XAML einführen. Könnte jedoch einigen Leuten passen. +1 für die detaillierte Erklärung! –

2

Es scheint eine Odity mit ColorAnimation zu sein, da es mit DoubleAnimation gut funktioniert. Sie müssen explizit die Storyboards „Targetname“ Eigenschaft angeben mit Color ist

<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 - danke für deine Antwort. Mit deiner Bearbeitung passt es meinen Bedürfnissen, aber @Anvanka hat dich mit einer richtigen Antwort (im Grunde das gleiche) überfallen, also habe ich es ihm gegeben. +1 alle gleich. –

1

Hier ist eine Lösung, die nur die Animation startet, wenn die eingehende Eigenschaftenaktualisierung einen bestimmten Wert hat. Nützlich, wenn Sie die Aufmerksamkeit des Benutzers auf etwas mit der Animation lenken möchten, aber danach sollte die Benutzeroberfläche in ihren Standardzustand zurückkehren.

Angenommen, IsCritical ist an ein Steuerelement (oder sogar ein unsichtbares Steuerelement) gebunden, fügen Sie NotifyOnTargetUpdated der Bindung hinzu und binden einen EventTrigger an das Binding.TargetUpdated-Ereignis. Dann erweitern Sie das Steuerelement nur das Target Ereignis ausgelöst, wenn der ankommende Wert ist, die Sie interessieren. So ...

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

und in der XAML-Datei ..

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