2009-10-30 14 views
8

Estoy buscando una manera limpia de comenzar una animación que tendrá valores dinámicos. Básicamente, quiero hacer una animación en la que un elemento cambie de ancho en función de los datos de otro elemento. Supongamos que tengo un TextBlock que es Text Property is Binding. Cuando esta propiedad cambia, quiero que un elemento visual diga un Rectángulo para nuestro bien para hacer una Doble Animación cambiando el ancho del valor anterior al nuevo.WPF MVVM Property Change Animation

Estoy tratando de evitar poner código en mi vista si es posible. He analizado DataTriggers pero parecen requerir que sepas cuál sería el valor, como un Enum. En mi caso, es solo el cambio de valor el que necesita desencadenar un guión gráfico y la animación debería comenzar en el valor actual (anterior) y moverse de forma agradable al nuevo valor.

Cualquier idea. Tal vez me perdí una propiedad.

Respuesta

13

Aquí está la solución con la que terminé. Para hacer la animación basada en los datos de mi ViewModel utilicé un DataTrigger. A continuación está mi estilo para el control.

<Style TargetType="Grid" x:Key="DetailRotation" > 
    <Style.Triggers> 
     <DataTrigger Binding="{Binding Path=AnimationState}" Value="New"> 
      <DataTrigger.EnterActions> 
       <StopStoryboard BeginStoryboardName="EndAnimation" /> 
       <BeginStoryboard Name="NewAnimation"> 
        <Storyboard> 
         <ThicknessAnimation Storyboard.TargetProperty="Margin" From="0,30,0,0" To="0,0,0,0" Duration="0:0:1" /> 
         <DoubleAnimation Storyboard.TargetProperty="Opacity" From="0" To="1" Duration="0:0:1" /> 
        </Storyboard> 
       </BeginStoryboard> 
      </DataTrigger.EnterActions> 
      <DataTrigger.ExitActions> 

      </DataTrigger.ExitActions> 

     </DataTrigger> 
     <DataTrigger Binding="{Binding Path=AnimationState}" Value="End"> 
      <DataTrigger.EnterActions> 
       <StopStoryboard BeginStoryboardName="NewAnimation" /> 
       <BeginStoryboard Name="EndAnimation"> 
        <Storyboard> 
         <ThicknessAnimation Storyboard.TargetProperty="Margin" From="0,0,0,0" To="0,-20,0,0" Duration="0:0:1"/> 
         <DoubleAnimation Storyboard.TargetProperty="Opacity" From="1" To="0" Duration="0:0:1" /> 
        </Storyboard> 
       </BeginStoryboard> 
      </DataTrigger.EnterActions> 
     </DataTrigger> 

    </Style.Triggers> 
</Style> 
1

Puede explorar utilizando Attached Properties para conectar la lógica necesaria al Guión gráfico/Animación que desee.

Esto no necesariamente le impedirá tener que escribir código, pero lo mantendrá separado de la vista y permitirá que se vuelva a utilizar en varias vistas.

+0

Realmente no estoy seguro de que esta sea la dirección que necesito. Estoy casi al punto en el que voy a tener que usar un evento de ruta para ordenar que se lleve a cabo el guión gráfico y vincular el Anverso y el Ancho actual a las propiedades de la animación. Esta podría ser la única forma de hacer esto. Todavía no estoy seguro. DataTriggers solo funcionaría para cambios de estilo de estado, no cambios dinámicos. Aquí es donde tiendo a romper un poco el patrón. – cjibo

0

Desde propiedades modificadas por la animación no se pueden establecer fuera de la animación de 'contexto', se me ocurrió una solución de código ya que no podía hacer lo mismo en XAML con eficacia.

private void UserControl_IsVisibleChanged(object sender, 
    DependencyPropertyChangedEventArgs e) 
{ 
    if (this.Visibility == Visibility.Visible) 
    { 
     DoubleAnimation fadeIn = new DoubleAnimation(); 
     fadeIn.From = 1d; 
     fadeIn.To = 1d; 
     fadeIn.Duration = new Duration(new TimeSpan(0, 0, 0)); 

     DoubleAnimation fade = new DoubleAnimation(); 
     fade.From = 1d; 
     fade.To = 0d; 
     fade.BeginTime = TimeSpan.FromSeconds(5); 
     fade.Duration = new Duration(new TimeSpan(0, 0, 1)); 

     NameScope.SetNameScope(this, new NameScope()); 
     this.RegisterName(this.Name, this); 

     Storyboard.SetTargetName(fadeIn, this.Name); 
     Storyboard.SetTargetProperty(fadeIn, new PropertyPath 
      (UIElement.OpacityProperty)); 

     Storyboard.SetTargetName(fade, this.Name); 
     Storyboard.SetTargetProperty(fade, new PropertyPath 
      (UIElement.OpacityProperty)); 

     Storyboard sb = new Storyboard(); 
     sb.Children.Add(fadeIn); 
     sb.Children.Add(fade); 

     sb.Completed += new EventHandler(sb_Completed); 
     sb.Begin(this); 
    } 
} 

void sb_Completed(object sender, EventArgs e) 
{ 
    this.Visibility = Visibility.Hidden; 
} 
0

En realidad se desea enlazar el DoubleAnimation.ToPropertyViewModel a la propiedad y la animación de control real. El problema es que la animación debe continuar cuando ToProperty ha cambiado. Mi solución encapsula toda esta lógica en un MarkupExtenstion que envuelve un Binding.

public class AnimateBindingExtension : MarkupExtension { 
    static DependencyPropertyDescriptor dpd = 
     DependencyPropertyDescriptor.FromProperty(DoubleAnimation.ToProperty, 
      typeof(DoubleAnimation)); 

    public AnimateBindingExtension(PropertyPath path) { 
     Path = path; 
    } 

    public bool ValidatesOnExceptions { get; set; } 
    public IValueConverter Converter { get; set; } 
    public object ConverterParamter { get; set; } 
    public string ElementName { get; set; } 
    public RelativeSource RelativeSource { get; set; } 
    public object Source { get; set; } 
    public bool ValidatesOnDataErrors { get; set; } 
    [ConstructorArgument("path")] 
    public PropertyPath Path { get; set; } 
    public object TargetNullValue { get; set; } 

    public override object ProvideValue(IServiceProvider serviceProvider) { 
     var valueProvider = serviceProvider.GetService(typeof(IProvideValueTarget)) as IProvideValueTarget; 

     if (valueProvider == null) { 
      throw new Exception("could not get IProviderValueTarget service."); 
     } 

     var bindingTarget = valueProvider.TargetObject as FrameworkElement; 
     var bindingProperty = valueProvider.TargetProperty as DependencyProperty; 

     if (bindingProperty == null || bindingTarget == null) { 
      throw new Exception(); 
     } 

     var binding = new Binding { 
      Path = Path, 
      Converter = Converter, 
      ConverterParameter = ConverterParamter, 
      ValidatesOnDataErrors = ValidatesOnDataErrors, 
      ValidatesOnExceptions = ValidatesOnExceptions, 
      TargetNullValue = TargetNullValue 
     }; 

     if (ElementName != null) binding.ElementName = ElementName; 
     else if (RelativeSource != null) binding.RelativeSource = RelativeSource; 
     else if (Source != null) binding.Source = Source; 

     // you can add a Duration property to this class and use it here 
     var anim = new DoubleAnimation { 
      Duration = new Duration(TimeSpan.FromSeconds(0.1)), 
      AccelerationRatio = 0.2, 
      DecelerationRatio = 0.8 
     }; 
     // this can be a new subclass of DoubleAnimation that 
     // overrides ToProperty metadata and add a property 
     // change callback 
     dpd.AddValueChanged(anim, (s, e) => bindingTarget.BeginAnimation(bindingProperty, anim)); 

     BindingOperations.SetBinding(anim, DoubleAnimation.ToProperty, binding); 
     // this is because we need to catch the DataContext so add animation object 
     // to the visual tree by adding it to target object's resources. 
     bindingTarget.Resources[bindingProperty.Name] = anim; 
     // animation will set the value 
     return DependencyProperty.UnsetValue; 
    } 
} 

Puede hacer lo mismo con otras clases de animación para animar otros tipos.

+0

¿Se encontrará esto con los problemas generalizados de los guiones gráficos cuando se colocan en estilos/plantillas? Si no se ve muy interesante ... – tobriand

+0

¿Cuál es la sintaxis para usar esta extensión de enlace en el Xaml? –