2009-10-31 45 views
13

有沒有辦法在xaml的某處定義一個動畫(例如,作爲一個資源),然後重複使用它多次?我有許多不同的數據模板的獨立畫筆,它們獨立地需要根據數據觸發器開始同一種動畫。現在,因爲看起來動畫必須定義一個Storyboard.TargetName和Storyboard.TargetProperty。這幾乎違背了可重用性的目的。我想以某種方式聲明「使用此動畫形式的資源,但將其應用於另一個元素」。將動畫和觸發器定義爲可重用資源?

對我來說,這似乎是一個相當基本的,重要的和基本的要求,我很驚訝,它並不是那麼直截了當的表現。我在這裏錯過了什麼嗎?

同樣的事情適用於觸發器。假設我有很多不同的視覺元素,它們都使用顏色動畫表示相同類型的狀態。例如。當「主動」在「錯誤」時淡出爲「紅色」等時淡入綠色。視覺之間的唯一區別是它們的形狀/視覺樹所期望的動畫行爲是相同的,它們都具有位於其視覺樹中某處的元素,一種顏色屬性。我認爲不難想象它會一遍又一遍地重新定義相同的動畫和數據組。每個開發者都討厭這個。我拼命尋找一種更簡單的解決方案,不需要(或至少很少)c#代碼。

我已經想出到目前爲止是這樣的:

定義在資源動畫LIK這(重複此對所有的基本狀態,有一樣激活,活躍,不活躍,錯誤):

<ColorAnimationUsingKeyFrames x:Key="deactivatingColorAnimation" 
        Storyboard.TargetProperty="Material.(MaterialGroup.Children)[0].Brush.(SolidColorBrush.Color)"      
        FillBehavior="HoldEnd" RepeatBehavior="Forever" AutoReverse="True"> 
     <ColorAnimationUsingKeyFrames.KeyFrames> 
     <LinearColorKeyFrame KeyTime="00:00:00" Value="Gray"/> 
     <LinearColorKeyFrame KeyTime="00:00:0.25" Value="Gray"/> 
     <LinearColorKeyFrame KeyTime="00:00:0.5" Value="Gray" /> 
     <LinearColorKeyFrame KeyTime="00:00:0.75" Value="Gray" /> 
    </ColorAnimationUsingKeyFrames.KeyFrames> 
</ColorAnimationUsingKeyFrames> 

觸發器中使用它在故事板(重複的次數不計其數這對每個國家每個X不同的充stateviusal,總是能想出的故事板的新名稱):

<DataTrigger Binding="{Binding SubstrateHolder.State}" Value="Deactivating"> 
     <DataTrigger.EnterActions> 
      <BeginStoryboard x:Name="someStateVisualDeactivatingStoryboard"> 
       <Storyboard Storyboard.TargetName="someStateVisual"> 
        <StaticResource ResourceKey="deactivatingColorAnimation" /> 
       </Storyboard> 
      </BeginStoryboard> 
     </DataTrigger.EnterActions> 
     <DataTrigger.ExitActions> 
      <RemoveStoryboard BeginStoryboardName="someStateVisualDeactivatingStoryboard" /> 
     </DataTrigger.ExitActions> 
</DataTrigger> 

你可以很容易地想象我需要爲所有這些zillion DataTriggers重複複製和粘貼多少膨脹XAML。

將所有這些觸發器定義一次並將其應用於不同的狀態視覺效果將會很酷。在WPF中如何解決這個問題?任何提示?

回答

1

似乎沒有任何好的XAML解決方案給這個普通proplem。我最終編寫了自己的附屬屬性,它定義了給定元素的所有動畫行爲。像這樣:

<DataTemplate> 
    <!-- ... --> 
    <Rectangle Fill="Gray"> 
    <v:AnimationHelper.Animations> 
     <v:StandardColorStateAnimation TargetColorProperty="(Rectangle.Fill).(SolidColorBrush.Color)" TraggetSateProperty={Binding State} /> 
    </v:AnimationHelper.Animations> 
    </Rectangle> 
<DataTemplate> 

其餘的(創建動畫等)是在代碼隱藏中完成的。

+0

雅知道我嘗試了一堆不同的東西來爲此提出一個建議,只是沒有在代碼中做某件事情就無法實現。這與我想你會得到的解決方案一樣好。爲了WPF的可擴展性而歡呼,對吧? :) – 2009-11-11 20:52:57

+0

對此使用silverlight行爲API可能是一個好主意。你同意嗎?它是否與標準的.net 3.5框架兼容? – bitbonk 2009-11-12 09:44:23

+0

不,它不兼容,但它們應該是。老實說,我認爲你已經找到了一個很好的解決方案,它以一種非常「自然」的方式利用了WPF架構,不應該在這裏再次猜測你自己。 – 2009-11-13 05:09:49

3

你可以試試這樣的事嗎?

  • 將所有當前的控件模板用一個不可見的根元素(例如,邊界或StackPanel,其邊界框將覆蓋整個控件。
  • 爲包含所有觸發器和動畫的不可見框創建樣式或控件模板。
  • 讓動畫在隱形框中設置任意顏色屬性的動畫效果。
  • 在所有不同控件的可視化樹中,將要設置動畫效果的屬性綁定到不可見根元素上的Color屬性。
0

我意識到這個問題在本文發佈時有點死了,但我確實找到了一個只需要很少代碼的解決方案。

您可以製作一個UserControl with custom properties(向下滾動到大約圖8),其中包含您的矩形以及動畫和狀態觸發器。這個用戶控件將定義一個公共屬性,例如狀態,當更改時會觸發顏色更改。

唯一需要的代碼隱藏是在代碼中創建變量。

用戶控件可以在不重寫XAML故事板或數據觸發器的情況下重複使用它們。

0

最「XAML方式」來實現這一目標,我能想到的是創建專用MarkupExtension這將拉動從資源字典中的動畫,並設置必要的屬性 - 我認爲那些被限制的Storyboard.Target一個子集, Storyboard.TargetNameStoryboard.TargetProperty。雖然它需要一些代碼隱藏,但這是一次性的努力,而且,MarkupExtensions被設計爲與XAML一起使用。下面是最簡單的版本:

[MarkupExtensionReturnType(typeof(Timeline))] 
public class AnimationResourceExtension : StaticResourceExtension 
{ 
    //This property is for convienience so that we 
    //don't have to remember to set x:Shared="False" 
    //on all animation resources, but have an option 
    //to avoid redundant cloning if it is 
    public bool IsShared { get; set; } = true; 

    public DependencyObject Target { get; set; } 

    public string TargetName { get; set; } 

    public PropertyPath TargetProperty { get; set; } 

    public override object ProvideValue(IServiceProvider serviceProvider) 
    { 
     if (base.ProvideValue(serviceProvider) is Timeline animation) 
     { 
      //If the animation is shared we shall clone it 
      //Checking if it is frozen is optional and we can 
      //either clone it or throw an exception 
      //(or simply proceed knowing one will be thrown anyway) 
      if (IsShared || animation.IsFrozen) 
       animation = animation.Clone(); 
      Storyboard.SetTarget(animation, Target); 
      Storyboard.SetTargetName(animation, TargetName); 
      Storyboard.SetTargetProperty(animation, TargetProperty); 
      return animation; 
     } 
     else 
      throw new XamlException("The referenced resource is not an animation"); 
    } 
} 

用法相當簡單:

<FrameworkElement.Resources> 
    <DoubleAnimation x:Key="MyAnimation" From="0" To="1" Duration="0:0:1" /> 
</FrameworkElement.Resources> 
(...) 
<Storyboard> 
    <utils:AnimationResource ResourceKey="MyAnimation" TargetName="SomeElement" TargetProperty="Opacity" /> 
</Storyboard> 

作爲簡單,因爲它可能是這個解決方案有其侷限性 - 它不支持也不Binding也不DynamicResource擴展上述性能。然而這是可以實現的,但需要一些額外的努力。 Binding支持應該非常簡單 - 正確使用XamlSetMarkupExtensionAttribute (加上一些樣板代碼)的問題。 DynamicResource支持會有點棘手,除了使用XamlSetMarkupExtensionAttribute將需要包裝IServiceProvider返回足夠的IProvideValueTarget實現,但仍然有可能。