2008-09-17 15 views
15

Próbuję wyzwolić animację postępu, gdy model ViewModel/Presentation jest zajęty. Mam właściwość IsBusy, a ViewModel jest ustawiony jako DataContext w UserControl. Jaki jest najlepszy sposób wywołania tablicy opowieści "progressAnimation", gdy właściwość IsBusy jest prawdziwa? Blend pozwala tylko dodawać wyzwalacze zdarzeń na poziomie UserControl i mogę tworzyć tylko wyzwalacze właściwości w moich szablonach danych.Wyzwalacze i tablice danych WPF

"PostępowanieAnimacja" jest zdefiniowany jako zasób w formancie użytkownika.

Próbowałem dodanie DataTriggers jako styl na UserControl, ale gdy próbuję uruchomić Storyboard pojawia się następujący błąd:

'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 jest nazwa obiektu Próbuję animowanie , więc usunięcie nazwy celu jest obsesyjnie NIE tym, czego chcę.

Miałem nadzieję rozwiązać to w XAML za pomocą technik wiązania danych, zamiast zamiast tego ujawniać zdarzenia i uruchamiać/zatrzymywać animację za pomocą kodu.

Odpowiedz

29

Co chcesz, jest to możliwe poprzez uznanie animację na progressWheel samego: 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> 

związany kod:

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

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

Mam nadzieję, że to pomoże!

1

Polecam używać RoutedEvent zamiast swojej własności IsBusy. Po prostu uruchom zdarzenie OnBusyStarted i OnBusyStopped i użyj wyzwalacza zdarzeń na odpowiednich elementach.

+1

Cóż, właśnie to miałem nadzieję unikaj ... Ale powiedzmy, że to robię: wszelkie przykłady implementacji RoutedEvent w klasie, która NIE pochodzi od UIElement? –

1

Możesz subskrybować zdarzenie PropertyChanged klasy DataObject i wywołać pożar RoutedEvent z poziomu Usercontrol.

Dla RoutedEvent do pracy musimy mieć klasę pochodzącą z DependancyObject

+0

Myślę, że masz rację ... Odsłaniając RoutedEvent z szwów UserControl jak najbardziej oczywiste rozwiązanie ... Jednak nie zrezygnowałem z wykonywania arbitralnych storyboardów na podstawie danych jeszcze .. Ale dziękuję za wejście! –

0

Można użyć Trigger.EnterAction rozpocząć animację, gdy nieruchomość została zmieniona.

<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

Tak jak powiedziałem, jest to na poziomie kontroli użytkownika i akceptuję tylko EventTriggers (a nie Property- lub DataTriggers). Ponadto IsBusy nie jest właściwością w UserControl, ale na obiekcie ustawionym jako DataContext (ViewModel) –

4

Chociaż odpowiedź, która proponuje dołączenie animacji bezpośrednio do elementu, który ma być animowany, rozwiązuje ten problem w prostych przypadkach, nie jest to możliwe do zastosowania, gdy mamy złożoną animację, która musi być ukierunkowana na wiele elementów. (Możesz dołączyć animację do każdego elementu, oczywiście, ale zarządzanie nim jest naprawdę okropne.)

Jest więc alternatywny sposób rozwiązania tego problemu, który pozwala użyć animacji, która atakuje wybrane elementy.

Istnieją trzy miejsca, w które można dołączyć wyzwalacze w WPF: elementy, style i szablony. Jednak pierwsze dwie opcje nie działają tutaj. Pierwsza jest wykluczona, ponieważ WPF nie obsługuje użycia elementu DataTrigger bezpośrednio. (O ile wiem, kiedy pytałem ludzi w zespole WPF o to wiele lat temu, powiedzieli, że woleliby to poprzeć, ale nie mieć czas, aby to zadziałało.) A style są niedostępne, ponieważ, jak napisano w zgłoszeniu o błędzie, nie można kierować na wskazane elementy w animacji związanej ze stylem.

Zostawia to szablony. Możesz użyć do tego celu szablonów kontrolnych lub danych.

<ContentControl> 
    <ContentControl.Template> 
     <ControlTemplate TargetType="ContentControl"> 
      <ControlTemplate.Resources> 
       <Storyboard x:Key="myAnimation"> 

        <!-- Your animation goes here... --> 

       </Storyboard> 
      </ControlTemplate.Resources> 
      <ControlTemplate.Triggers> 
       <DataTrigger 
        Binding="{Binding MyProperty}" 
        Value="DesiredValue"> 
        <DataTrigger.EnterActions> 
         <BeginStoryboard 
          x:Name="beginAnimation" 
          Storyboard="{StaticResource myAnimation}" /> 
        </DataTrigger.EnterActions> 
        <DataTrigger.ExitActions> 
         <StopStoryboard 
          BeginStoryboardName="beginAnimation" /> 
        </DataTrigger.ExitActions> 
       </DataTrigger> 
      </ControlTemplate.Triggers> 

      <!-- Content to be animated goes here --> 

     </ControlTemplate> 
    </ContentControl.Template> 
<ContentControl> 

Przy tej konstrukcji WPF z przyjemnością pozwala animacji odwoływać się do nazwanych elementów wewnątrz szablonu. (Zostawiłem tu zarówno animację, jak i zawartość szablonu - oczywiście wypełniłeś to faktyczną treścią animacji.)

Powodem, dla którego działa to w szablonie, ale nie w stylu, jest to, że po zastosowaniu szablon, nazwane przez niego elementy będą zawsze obecne, a więc bezpieczne dla animacji zdefiniowanych w zakresie tego szablonu w odniesieniu do tych elementów. Zwykle nie jest tak w przypadku stylu, ponieważ style mogą być stosowane do wielu różnych elementów, z których każdy może mieć zupełnie inne drzewa wizualne. (To trochę frustrujące, że uniemożliwia ci to nawet w scenariuszach, kiedy możesz być pewien, że wymagane elementy będą tam, ale może jest coś, co bardzo utrudnia animację związaną z nazwanymi elementami po prawej stronie Wiem, że istnieje wiele optymalizacji w WPF, aby umożliwić efektywne wykorzystanie elementów stylu, więc być może jedna z nich sprawia, że ​​jest to trudne.)