2009-10-30 135 views
8

我正在尋找一種乾淨的方式來啓動將具有動態值的動畫。基本上我想做一個動畫,一個元素根據另一個元素的數據改變寬度。假設我有一個文本屬性爲綁定的TextBlock。當這個屬性改變時,我想讓一個可視元素爲我們說出一個Rectangle來做一個DoubleAnimation,將寬度從之前的值改變爲新的值。WPF MVVM屬性更改動畫

我想盡可能遠離將代碼放在我的視圖中。我研究過DataTrigger,但他們似乎要求你知道值是什麼,比如Enum。在我的情況下,它只是需要觸發故事板的價值變化,動畫需要從當前(前一個)值開始,並很好地移動到新值。

任何想法。也許我錯過了一個物業。

回答

13

這是我最終解決的解決方案。要在我的ViewModel中基於數據執行動畫,我使用了一個DataTrigger。以下是我的控制風格。

<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

您可以使用Attached Properties探索將必要的邏輯連接到您想要的故事板/動畫。

這不一定會阻止你編寫代碼,但它會保持它與視圖分開,並允許它跨多個視圖重用。

+0

我真的不確定這是我需要的方向。我幾乎到了必須使用routedevent命令發生故事板並將PreviousWidth和CurrentWidth綁定到動畫的to和from屬性的位置。這可能是做到這一點的唯一方法。仍然不確定。 DataTriggers只適用於狀態更改而非動態更改。這是我傾向於脫離一點模式的地方。 – cjibo

0

由於通過動畫修改屬性不能動畫「上下文」之外的設置,我想出了一個代碼解決方案,因爲我不能有效地做同樣的XAML。

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

其實你要綁定的DoubleAnimation.ToPropertyViewModel財產和動畫實際控制權。問題是當ToProperty更改時應該繼續動畫。我的解決方案將所有這些邏輯封裝爲一個MarkupExtenstion,其中包含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; 
    } 
} 

您可以對其他動畫類執行相同的動畫來爲其他類型創建動畫。

+0

這會在放置在樣式/模板中時遇到凍結故事板的普遍問題嗎?如果看起來不是很有趣... – tobriand

+0

在Xaml中使用此綁定擴展的語法是什麼? –