2009-10-30 13 views
8

Ich bin auf der Suche nach einem sauberen Weg, um eine Animation zu starten, die dynamische Werte haben wird. Grundsätzlich möchte ich eine Animation machen, bei der ein Element die Breite basierend auf den Daten eines anderen Elements ändert. Angenommen, ich habe einen TextBlock, dessen Texteigenschaft bindend ist. Wenn sich diese Eigenschaft ändert, möchte ich ein visuelles Element sagen, ein Rectangle, um eine DoubleAnimation zu machen, die die Breite vom vorherigen Wert zum neuen Wert ändert.WPF MVVM Eigenschaft Animation ändern

Ich versuche, Code zu vermeiden, wenn möglich. Ich habe DataTriggers betrachtet, aber sie scheinen zu verlangen, dass Sie wissen, wie der Wert sein würde, wie ein Enum. In meinem Fall ist es nur die Wertänderung, die ein Storyboard auslösen muss, und die Animation muss mit dem aktuellen (vorherigen) Wert beginnen und sich gut auf den neuen Wert bewegen.

Irgendwelche Ideen. Vielleicht habe ich gerade eine Immobilie vermisst.

Antwort

13

Hier ist die Lösung, mit der ich endete. Um die Animation basierend auf Daten in meinem ViewModel durchzuführen, habe ich einen DataTrigger verwendet. Unten ist mein Style für die Steuerung.

<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

Sie könnten mit Attached Properties erkunden, um die notwendige Logik an das Storyboard/Animation, die Sie wünschen, anzuschließen.

Das wird Sie nicht unbedingt davon abhalten, Code zu schreiben, aber es wird es von der Ansicht getrennt halten und erlauben, dass es über mehrere Ansichten wiederverwendet werden kann.

+0

Ich bin mir wirklich nicht sicher, das ist die Richtung, die ich brauche. Ich bin fast an dem Punkt, wo ich ein routedevent verwenden muss, um das Storyboard zu steuern, und binde PreviousWidth und CurrentWidth an die to- und from-Eigenschaften der Animation. Dies könnte der einzige Weg sein, dies zu tun. Immernoch nicht sicher. DataTriggers funktionieren nur für Änderungen im Statusformat, nicht für dynamische Änderungen. Hier neige ich dazu, mich ein wenig vom Muster zu lösen. – cjibo

0

Da durch Animation veränderten Eigenschaften können nicht außerhalb der Animation ‚Kontext‘ gesetzt werden, kam ich mit einem Code-Lösung auf, da ich nicht das gleiche in XAML effektiv tun könnte.

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

Eigentlich wollen Sie die DoubleAnimation.ToProperty an die ViewModel Eigenschaft binden und die tatsächliche Kontrolle animieren. Das Problem ist, dass die Animation fortgesetzt werden sollte, wenn ToProperty geändert wird. Meine Lösung kapselt all diese Logik in eine , die eine Binding umschließt.

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

Sie können das gleiche mit anderen Animationsklassen tun, um andere Typen zu animieren.

+0

Wird dies zu tiefgreifenden Problemen führen, wenn Storyboards eingefroren werden, wenn sie in Styles/Templates platziert werden? Wenn das nicht sehr interessant aussieht ... – tobriand

+0

Wie lautet die Syntax für die Verwendung dieser Bindungserweiterung im Xaml? –