2008-09-17 19 views
15

J'essaie de déclencher une animation de progression lorsque le modèle ViewModel/Presentation est occupé. J'ai une propriété IsBusy et le ViewModel est défini en tant que DataContext de UserControl. Quel est le meilleur moyen de déclencher un story board "progressAnimation" lorsque la propriété IsBusy est vraie? Mélangez seulement laissez med ajouter des déclencheurs d'événement sur un niveau UserControl, et je peux seulement créer des déclencheurs de propriété dans mes modèles de données.Les déclencheurs de données WPF et les story boards

La "progressAnimation" est définie comme une ressource dans le contrôle utilisateur.

J'ai essayé d'ajouter les DataTriggers comme un style sur le UserControl, mais quand je tente de démarrer le storyboard je reçois l'erreur suivante:

'System.Windows.Style' value cannot be assigned to property 'Style' 
of object'Colorful.Control.SearchPanel'. A Storyboard tree in a Style 
cannot specify a TargetName. Remove TargetName 'progressWheel'. 

ProgressWheel est le nom de l'objet que je suis en train d'animer , donc enlever le nom de la cible est obvisouly PAS ce que je veux. J'espérais résoudre cela en XAML en utilisant des techniques de liaison de données, au lieu de devoir exposer des événements et démarrer/arrêter l'animation par le code.

Répondre

29

Ce que vous voulez est possible en déclarant l'animation sur le progressWheel lui-même: Le XAML:

<UserControl x:Class="TriggerSpike.UserControl1" 
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 
Height="300" Width="300"> 
<UserControl.Resources> 
    <DoubleAnimation x:Key="SearchAnimation" Storyboard.TargetProperty="Opacity" To="1" Duration="0:0:4"/> 
    <DoubleAnimation x:Key="StopSearchAnimation" Storyboard.TargetProperty="Opacity" To="0" Duration="0:0:4"/> 
</UserControl.Resources> 
<StackPanel> 
    <TextBlock Name="progressWheel" TextAlignment="Center" Opacity="0"> 
     <TextBlock.Style> 
      <Style> 
       <Style.Triggers> 
        <DataTrigger Binding="{Binding IsBusy}" Value="True"> 
         <DataTrigger.EnterActions> 
          <BeginStoryboard> 
           <Storyboard> 
            <StaticResource ResourceKey="SearchAnimation"/> 
           </Storyboard> 
          </BeginStoryboard> 
         </DataTrigger.EnterActions> 
         <DataTrigger.ExitActions> 
          <BeginStoryboard> 
           <Storyboard> 
            <StaticResource ResourceKey="StopSearchAnimation"/> 
           </Storyboard> 
          </BeginStoryboard> 
         </DataTrigger.ExitActions> 
        </DataTrigger> 
       </Style.Triggers> 
      </Style> 
     </TextBlock.Style> 
     Searching 
    </TextBlock> 
    <Label Content="Here your search query"/> 
    <TextBox Text="{Binding SearchClause}"/> 
    <Button Click="Button_Click">Search!</Button> 
    <TextBlock Text="{Binding Result}"/> 
</StackPanel> 

code derrière:

using System.Windows; 
using System.Windows.Controls; 

namespace TriggerSpike 
{ 
    public partial class UserControl1 : UserControl 
    { 
     private MyViewModel myModel; 

     public UserControl1() 
     { 
      myModel=new MyViewModel(); 
      DataContext = myModel; 
      InitializeComponent(); 
     } 

     private void Button_Click(object sender, RoutedEventArgs e) 
     { 
      myModel.Search(myModel.SearchClause); 
     } 
    } 
} 

Le viewmodel:

using System.ComponentModel; 
using System.Threading; 
using System.Windows; 

namespace TriggerSpike 
{ 
    class MyViewModel:DependencyObject 
    { 

     public string SearchClause{ get;set;} 

     public bool IsBusy 
     { 
      get { return (bool)GetValue(IsBusyProperty); } 
      set { SetValue(IsBusyProperty, value); } 
     } 

     public static readonly DependencyProperty IsBusyProperty = 
      DependencyProperty.Register("IsBusy", typeof(bool), typeof(MyViewModel), new UIPropertyMetadata(false)); 



     public string Result 
     { 
      get { return (string)GetValue(ResultProperty); } 
      set { SetValue(ResultProperty, value); } 
     } 

     public static readonly DependencyProperty ResultProperty = 
      DependencyProperty.Register("Result", typeof(string), typeof(MyViewModel), new UIPropertyMetadata(string.Empty)); 

     public void Search(string search_clause) 
     { 
      Result = string.Empty; 
      SearchClause = search_clause; 
      var worker = new BackgroundWorker(); 
      worker.DoWork += worker_DoWork; 
      worker.RunWorkerCompleted += worker_RunWorkerCompleted; 
      IsBusy = true; 
      worker.RunWorkerAsync(); 
     } 

     void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) 
     { 
      IsBusy=false; 
      Result = "Sorry, no results found for: " + SearchClause; 
     } 

     void worker_DoWork(object sender, DoWorkEventArgs e) 
     { 
      Thread.Sleep(5000); 
     } 
    } 
} 

Espérons que cela aide!

1

Je recommanderais d'utiliser RoutedEvent à la place de votre propriété IsBusy. Il suffit de déclencher l'événement OnBusyStarted et OnBusyStopped et d'utiliser le déclencheur d'événement sur les éléments appropriés.

+1

Eh bien, c'est ce que j'espérais éviter ... Mais, disons que je fais cela: des exemples sur la façon d'implémenter un RoutedEvent dans une classe qui ne dérive pas de UIElement? –

1

Vous pouvez vous abonner à l'événement PropertyChanged de la classe DataObject et effectuer un incendie RoutedEvent à partir du niveau Usercontrol.

Pour RoutedEvent au travail que nous devons avoir la classe dérivée de DependancyObject

+0

Je pense que vous avez raison ... L'exposition d'un RoutedEvent des coutures UserControl comme la solution la plus évidente ... Cependant, je ne l'ai pas renoncé à l'exécution des storyboards arbitraires basées sur des données tout de .. Mais merci pour l'entrée! –

0

Vous pouvez utiliser Trigger.EnterAction pour démarrer une animation quand une propriété est modifiée.

<Trigger Property="IsBusy" Value="true"> 
    <Trigger.EnterActions> 
     <BeginStoryboard x:Name="BeginBusy" Storyboard="{StaticResource MyStoryboard}" /> 
    </Trigger.EnterActions> 
    <Trigger.ExitActions> 
     <StopStoryboard BeginStoryboardName="BeginBusy" /> 
    </Trigger.ExitActions> 
</Trigger> 
+0

Comme je l'ai dit, c'est au niveau du contrôle utilisateur, et j'accepte uniquement les EventTriggers (pas Property- ou DataTriggers). En outre, IsBusy n'est pas une propriété sur UserControl, mais sur l'ensemble d'objets en tant que DataContext (le ViewModel) –

4

Bien que la réponse qui propose d'attacher l'animation directement à l'élément à animer résout ce problème dans des cas simples, cela n'est pas vraiment réalisable lorsque vous avez une animation complexe qui doit cibler plusieurs éléments. (Vous pouvez attacher une animation à chaque élément bien sûr, mais cela devient assez horrible à gérer.)

Il existe donc une autre façon de résoudre ce problème qui vous permet d'utiliser un DataTrigger pour exécuter une animation qui cible des éléments nommés.

Il existe trois emplacements auxquels vous pouvez attacher des déclencheurs dans WPF: les éléments, les styles et les modèles. Cependant, les deux premières options ne fonctionnent pas ici. Le premier est exclu car WPF ne prend pas en charge l'utilisation d'un DataTrigger directement sur un élément. Autant que je m'en souvienne, lorsque j'ai interrogé des membres de l'équipe de WPF il y a de nombreuses années, ils ont dit qu'ils auraient aimé l'appuyer, mais ils ne l'ont pas fait. avoir le temps de le faire fonctionner.Les styles sont désactivés car, comme l'indique le message d'erreur que vous avez signalé, vous ne pouvez pas cibler les éléments nommés dans une animation associée à un style.

Cela laisse des modèles. Et vous pouvez utiliser des modèles de contrôle ou de données pour cela. Avec cette construction, WPF est heureux de laisser l'animation se référer aux éléments nommés à l'intérieur du gabarit. (J'ai laissé l'animation et le contenu du modèle vides ici - évidemment, vous le remplissez avec votre contenu d'animation réel.)

La raison pour laquelle cela fonctionne dans un modèle mais pas un style est que lorsque vous appliquez un modèle, les éléments nommés qu'il définit seront toujours présents, et il est donc sûr que les animations définies dans la portée de ce modèle se réfèrent à ces éléments. Ce n'est généralement pas le cas avec un style, car les styles peuvent être appliqués à plusieurs éléments différents, dont chacun peut avoir des arbres visuels très différents. (C'est un peu frustrant que cela vous empêche de faire cela même dans des scénarios où vous pouvez être certain que les éléments requis seront là, mais peut-être qu'il y a quelque chose qui rend très difficile l'animation des éléments nommés à droite le temps. Je sais qu'il ya pas mal d'optimisations dans WPF pour permettre aux éléments d'un style de réutiliser efficacement, donc peut-être l'un d'entre eux est ce qui rend ce difficile à supporter.)