2008-09-17 21 views
15

Estoy tratando de desencadenar una animación de progreso cuando el modelo de vista/modelo de presentación está ocupado. Tengo una Propiedad IsBusy, y ViewModel se establece como el DataContext del UserControl. ¿Cuál es la mejor manera de desencadenar un guión gráfico "progressAnimation" cuando la propiedad IsBusy es verdadera? Mezcle solo deje que se agreguen Event-Triggers en un nivel UserControl, y solo puedo crear activadores de propiedades en mis plantillas de datos.Disparadores de datos WPF y Story Boards

La "progressAnimation" se define como un recurso en el control del usuario.

He intentado añadir los DataTriggers como un estilo en el control de usuario, pero cuando intento iniciar la Historieta me sale el siguiente error:

'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 es el nombre del objeto que estoy tratando de animar , por lo tanto, eliminar el nombre del objetivo NO es lo que quiero.

Tenía la esperanza de resolver esto en XAML utilizando técnicas de enlace de datos, en lugar de tener que exponer eventos y comenzar/detener la animación a través del código.

Respuesta

29

Lo que queremos es posible declarar la animación en el propio progressWheel: El 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> 
Código

atrás:

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

El modelo de vista:

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

Espero que esto ayude!

1

Recomendaría utilizar RoutedEvent en lugar de su propiedad IsBusy. Simplemente active el evento OnBusyStarted y OnBusyStopped y use el activador de evento en los elementos apropiados.

+1

Bueno, eso era lo que esperaba evitar ... Pero, digamos que hago eso: ¿algún ejemplo sobre cómo implementar un RoutedEvent en una clase que NO se deriva de UIElement? –

1

Puede suscribirse al evento PropertyChanged de la clase DataObject y realizar un disparo RoutedEvent desde el nivel Usercontrol.

Para RoutedEvent para trabajar tenemos que tener la clase derivada de DependancyObject

+0

Creo que tienes razón ... Exponer un RoutedEvent desde UserControl se ve como la solución más obvia ... Sin embargo, aún no he abandonado la ejecución de guiones gráficos basados ​​en datos ... ¡Pero gracias por tu opinión! –

0

Puede utilizar Trigger.EnterAction para iniciar una animación cuando se cambia una propiedad.

<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

Como dije, esto está en el nivel de control de usuario, y solo acepto EventTriggers (no Property- o DataTriggers). Además, IsBusy no es una propiedad en el UserControl, sino en el objeto configurado como DataContext (ViewModel) –

4

Aunque la respuesta que propone adjuntar la animación directamente al elemento que se va a animar resuelve este problema en casos simples, esto no es realmente factible cuando se tiene una animación compleja que necesita apuntar a varios elementos. (Puede adjuntar una animación a cada elemento, por supuesto, pero se vuelve bastante horrible de manejar.)

Así que hay una forma alternativa de resolver esto que le permite usar un DataTrigger para ejecutar una animación que se dirige a elementos con nombre.

Hay tres lugares donde puede adjuntar activadores en WPF: elementos, estilos y plantillas. Sin embargo, las primeras dos opciones no funcionan aquí. El primero se descarta porque WPF no admite el uso de un DataTrigger directamente en un elemento. (Hasta donde yo sé, no hay una razón particularmente buena para esto. Por lo que recuerdo, cuando pregunté a personas del equipo de WPF sobre esto hace muchos años, dijeron que les hubiera gustado haberlo apoyado pero no lo hicieron. tener tiempo para hacerlo funcionar) Y los estilos están fuera porque, como dice el mensaje de error que ha informado, no puede orientar los elementos con nombre en una animación asociada a un estilo.

Eso deja plantillas. Y puede usar plantillas de control o datos para esto.

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

Con esta construcción, WPF se complace en dejar que la animación haga referencia a elementos con nombre dentro de la plantilla. (He dejado tanto la animación como el contenido de la plantilla vacíos aquí; obviamente, llenarías eso con tu contenido real de animación n.)

La razón por la que esto funciona en una plantilla pero no en un estilo es cuando aplicas un plantilla, los elementos nombrados que define siempre estarán presentes, por lo que es seguro que las animaciones definidas dentro del alcance de esa plantilla hagan referencia a esos elementos. Este no es generalmente el caso con un estilo, porque los estilos se pueden aplicar a múltiples elementos diferentes, cada uno de los cuales puede tener árboles visuales bastante diferentes. (Es un poco frustrante que te impida hacer esto incluso en escenarios donde puedes estar seguro de que los elementos necesarios estarán allí, pero quizás haya algo que hace que sea muy difícil que la animación se vincule a los elementos nombrados a la derecha Creo que hay muchas optimizaciones en WPF para permitir que los elementos de un estilo se reutilicen de manera eficiente, por lo que tal vez uno de ellos sea lo que hace que sea difícil de soportar.)

Cuestiones relacionadas