2009-08-18 13 views
14

Mam obiekt danych - klasa niestandardowa o nazwie Notification - która udostępnia właściwość IsCritical. Chodzi o to, że jeśli powiadomienie wygaśnie, ma ono okres ważności i należy zwrócić na niego uwagę użytkownika.WPF - Wykonywanie warunku animacji zależne od właściwości powiązanego elementu danych

Wyobraźmy sobie scenariusz z tymi danymi testu:

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

Drugi element powinien pojawić się w ItemsControl na tle pulsującej. Oto prosty fragment szablonu danych, który pokazuje środki, dzięki którym myślałem o animacji tła między szarością a kolorem żółtym.

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

Co jestem pewien sposób, aby ta animacja uzależnione od wartości IsCritical. Jeśli powiązana wartość to false, należy zachować domyślny kolor tła #DDD.

Odpowiedz

11

Końcowa część tej układanki jest ... DataTriggers. Wszystko, co musisz zrobić, to dodać DataTrigger do DataTemplate, powiązać go z właściwością IsCritical, a gdy tylko będzie to prawda, w jego EnterAction/ExitAction uruchomisz i zatrzymasz podświetlanie storyboardu. Tutaj jest całkowicie rozwiązanie działa z niektórymi zakodowane skrótów (na pewno można zrobić lepiej):

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> 

kod za:

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

Nadzieja to pomaga:).

+1

@Anvanka - dzięki za to. Nie korzystałem wcześniej z DataTrigger EnterActions lub ExitActions. Dziękuję również za dokładny przykład - wspaniała odpowiedź i godny nagrody. –

+0

Serdecznie zapraszamy :). Cieszę się że mogłem pomóc. – Anvaka

0

W tym przypadku używasz wyzwalaczy stylu. (Robię to z pamięci, więc nie mogą być pewne błędy)

<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

wygląda obiecująco, dzięki. Pozwól mi to wypróbować i wrócić do ciebie. –

+1

Nie, nie działa. Pobierz błąd: 'Wyzwalacze 'Ustawień' nie można ustawić, ponieważ nie ma dostępnego zestawu akcesoriów." –

+0

Cóż, to będzie trochę bardziej skomplikowane, niż mogę teraz wyliczyć. Jestem pewien, że jest sposób, aby to zrobić, ale prawdopodobnie będziesz musiał to zmienić w zupełnie inny sposób. Dobra okazja, żeby przeczytać o wyzwalaczach ... – Will

2

Co chciałbym zrobić, to utworzyć dwa DataTemplates i używać DataTemplateSelector. Twój XAML byłoby coś jak:

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

A DataTemplateSelector byłoby coś podobnego do:

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

ten sposób WPF automatycznie ustawia wszystko, co ma kluczowe znaczenie dla szablonu z animacja, a wszystko inne będzie drugim szablonem. Ma to również charakter ogólny, ponieważ później można użyć innej właściwości do przełączania szablonów i/lub dodawania kolejnych szablonów (schemat niskiego/normalnego/wysokiego znaczenia).

+0

To interesująca odpowiedź, ale nie jest tak elastyczna, jak by chciała. Na przykład, jeśli w szablonie danych jest wiele elementów, które wymagają animacji w zależności od stanu różnych właściwości? W moim przypadku również rzeczywisty szablon danych jest znacznie bardziej skomplikowany niż tylko "", więc wprowadziłbym wiele duplikacji do mojego XAML przez to. Może jednak pasować do niektórych ludzi. +1 dla szczegółowego wyjaśnienia! –

2

Wydaje się, że jest to odcień z ColorAnimation, ponieważ działa dobrze z funkcją DoubleAnimation. Trzeba jawnie określać storyboardy „targetName” własność pracować 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 - dzięki za odpowiedź. Z twoją edycją pasuje do moich potrzeb, ale @Anvanka podarował ci poprawną odpowiedź (w zasadzie to samo), więc dałem mu ją. +1 to samo. –

1

Oto rozwiązanie, które uruchamia animację tylko wtedy, gdy aktualizacja właściwości przychodzących ma określoną wartość. Przydatne, jeśli chcesz zwrócić uwagę użytkownika na coś z animacją, ale potem interfejs użytkownika powinien powrócić do stanu domyślnego.

Zakładając, że IsCritical jest powiązany z formantem (lub nawet niewidoczną formantem), dodajesz parametr NotifyOnTargetUpdated do powiązania i wiąże obiekt EventTrigger ze zdarzeniem Binding.TargetUpdated. Następnie rozszerzyć kontrolę tylko do ognia zdarzenie TargetUpdated gdy wartość przychodząca jest jeden jesteś zainteresowany. Więc ...

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

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