2009-10-30 20 views
8

Cerco un modo pulito per avviare un'animazione che avrà valori dinamici. Fondamentalmente voglio fare un'animazione in cui un elemento cambia larghezza in base ai dati di un altro elemento. Diciamo che ho un TextBlock che è Text Property is Binding. Quando questa proprietà cambia, voglio che un elemento visivo dica un rettangolo per fare un DoubleAnimation modificando la larghezza dal valore precedente al nuovo valore.WPF MVVM Modifica Proprietà Animazione

sto cercando di stare lontano dal mettere il codice a mio avviso, se possibile. Ho esaminato DataTriggers ma sembrano richiedere che tu sappia quale sarebbe il valore come un Enum. Nel mio caso è solo il cambiamento di valore che deve attivare uno storyboard e l'animazione dovrebbe iniziare dal valore corrente (precedente) e muoversi piacevolmente al nuovo valore.

Tutte le idee. Forse ho appena perso una proprietà.

risposta

13

Ecco la soluzione ho finito con. Per fare l'animazione basata sui dati nel mio ViewModel ho usato un DataTrigger. Di seguito è il mio stile per il controllo.

<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

Si potrebbe esplorare con Attached Properties per collegare la logica necessaria per Storyboard/Animazione che si desidera.

Questo non sarà necessariamente impedirvi di dover scrivere codice, ma sarà tenerlo separato dalla vista e permettono di essere riutilizzato su più punti di vista.

+0

Io non sono davvero sicuro questa è la direzione che ho bisogno. Sono quasi al punto in cui dovrò utilizzare un routedevent per comandare che lo storyboard avvenga e associare la larghezza precedente e la larghezza attuale alle proprietà di e dell'animazione. Questo potrebbe essere l'unico modo per farlo. Ancora non sono sicuro. DataTriggers funzionerebbe solo per le modifiche allo stile di stato e non per le modifiche dinamiche. È qui che tendo a staccarmi un po 'dal modello. – cjibo

0

Dal proprietà modificate da animazione non possono essere impostati al di fuori l'animazione 'contesto', mi si avvicinò con una soluzione di codice perché non ho potuto fare lo stesso in XAML in modo efficace.

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

In realtà si desidera associare il DoubleAnimation.ToProperty alla proprietà ViewModel e animare controllo effettivo. Il problema è che l'animazione deve essere continuata quando è stato modificato ToProperty. La mia soluzione racchiude tutta questa logica in un MarkupExtenstion che racchiude 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; 
    } 
} 

È possibile fare lo stesso con altre classi di animazione per animare altri tipi.

+0

Si verificheranno questi problemi pervasivi con il congelamento degli storyboard quando inseriti in stili/modelli? Se non sembra molto interessante ... – tobriand

+0

Qual è la sintassi per utilizzare questa estensione di associazione in Xaml? –