2009-10-31 24 views
13

Czy istnieje sposób na zdefiniowanie animacji gdzieś w Xaml (np. Jako zasobu), a następnie wielokrotne użycie jej wielokrotnie? Mam wiele niezależnych pędzli w różnych datata-płytach, które niezależnie muszą uruchomić ten sam rodzaj animacji w oparciu o datatrigger. Teraz wydaje się, że animacja musi zdefiniować Storyboard.TargetName i Storyboard.TargetProperty. To prawie pokonuje cel ponownego użycia. Chciałbym jakoś zadeklarować "użyj tej animacji z zasobu, ale tym razem zastosuj ją do innego elementu".definiować animacje i wyzwalacze jako zasoby wielokrotnego użytku?

Dla mnie jest to raczej podstawowe, ważne i zasadnicze żądanie i jestem zaskoczony, że nie jest to łatwe do osiągnięcia. Czy coś mi umyka?

To samo dotyczy wyzwalaczy. Załóżmy, że mam wiele różnych elementów wizualnych, które reprezentują ten sam typ stanu za pomocą animacji kolorów. Na przykład. przejście do koloru zielonego, gdy "aktywny" zaniknie do "czerwonego", gdy "błąd" itp. Jedyną różnicą między obrazami jest ich kształt/drzewo wizualne, a pożądane zachowanie animacji jest takie samo, wszystkie mają element gdzieś w ich wizualnym drzewie, które ma właściwość koloru typu. Myślę, że nie jest trudno wyobrazić sobie, jak żmudne jest ponowne definiowanie tych samych animacji i zestawów datatriggerów w kółko. Każdy programista tego nie znosi. Rozpaczliwie szukam łatwiejszego rozwiązania, które nie wymaga żadnego (lub przynajmniej bardzo małego) kodu C#.

Co mam wymyślić do tej pory to:

Zdefiniuj animacje w zasobie lik ten (powtórz to dla wszystkich podstawowych stanów, które istnieją, jak aktywującego, aktywny, nieaktywny, błąd):

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

sposobie wykorzystywania w serii ujęć w wyzwalacze (powtórz to zillions razy dla każdego państwa X każdej differnt stateviusal, zawsze wymyślić nową nazwę dla serii ujęć):

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

Możesz łatwo sobie wyobrazić, ile razy XAML muszę w kółko kopiować i wklejać dla wszystkich tych Zillion DataTrigger.

Byłoby fajnie zdefiniować wszystkie te wyzwalacze jeden raz i zastosować je do różnych wizualizacji stanu. Jak coś takiego rozwiązano w WPF? Jakaś wskazówka?

Odpowiedz

1

Nie wydaje się być dobrym rozwiązaniem tylko XAML do tego ogólnego proplem. Skończyłem na pisaniu własnych właściwości, które definiują zachowanie animacji dla danego elementu. coś takiego:

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

Reszta (tworzenie animacji itp.) odbywa się w kodzie źródłowym.

+0

Wiem, że próbowałem wielu różnych rzeczy, aby wymyślić propozycję dla tego i po prostu nie było to możliwe bez robienia czegoś w kodzie. Jest to równie dobre rozwiązanie, jak sądzę, że dostaniesz. Hurray dla rozszerzalności WPF, prawda? :) –

+0

Dobrym pomysłem może być wykorzystanie API zachowania silverlight. Czy sie zgadzasz? Czy jest kompatybilny ze standardową strukturą .net 3.5? – bitbonk

+0

Nie jest to niezgodne, chociaż podobno. Szczerze myślę, że znalazłeś świetne rozwiązanie, które wykorzystuje architekturę WPF w bardzo "naturalny" sposób i nie powinno się tu w ogóle odgadywać. –

3

Czy możesz spróbować czegoś takiego?

  • Zawiń wszystkie obecne szablony kontrolne niewidocznym elementem głównym, np. Obramowanie lub StackPanel, którego ramka ograniczająca obejmie całą kontrolę.
  • Utwórz szablon stylu lub kontrolki dla tego niewidocznego okna zawierającego wszystkie wyzwalacze i animacje.
  • Animacje animuj dowolną właściwość koloru w niewidocznym polu.
  • W drzewach wizualnych dla wszystkich różnych elementów sterujących należy powiązać właściwości, które mają być animowane, z właściwością Kolor na niewidocznym elemencie głównym.
0

Zdaję sobie sprawę, że ten problem jest trochę martwy w momencie publikacji tego postu, ale znalazłem rozwiązanie, które wymaga bardzo niewiele kodu.

Możesz wykonać UserControl with custom properties (przewiń w dół do około 8), który zawiera Twój prostokąt, a także animacje i wyzwalacze stanu. Ta kontrola użytkownika określałaby właściwość publiczną, taką jak status, który powodowałby zmianę koloru po zmianie.

Jedynym wymaganym kodowaniem jest utworzenie zmiennych w kodzie.

Kontrola użytkownika może być wielokrotnie używana bez przepisywania scenariuszy XAML lub wyzwalaczy danych.

0

Najbardziej „XAML sposób”, aby osiągnąć ten cel mogę myśleć jest stworzenie dedykowanego MarkupExtension które mogłyby pociągnąć za animację ze słownika zasobów oraz zestaw niezbędnych właściwości - Zakładam, że te są ograniczone do podzbioru Storyboard.Target, Storyboard.TargetName i Storyboard.TargetProperty. Mimo, że wymaga to pewnych kodów, jest to jednorazowy wysiłek, ponadto, MarkupExtension s są przeznaczone do stosowania z XAML. Oto najprostsza wersja:

[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"); 
    } 
} 

Użycie jest bardzo proste:

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

Będąc tak proste, jak to może być to rozwiązanie ma swoje ograniczenia - nie obsługuje ani Binding ani DynamicResource rozszerzeń dla wyżej wymienionych właściwości. Jest to jednak możliwe do osiągnięcia, ale wymaga dodatkowego wysiłku. Binding obsługa powinna być całkiem prosta - kwestia poprawnego korzystania z XamlSetMarkupExtensionAttribute (oraz niektórych kodów standardowych). DynamicResource obsługa byłaby trochę trudniejsza, a oprócz użycia XamlSetMarkupExtensionAttribute wymagałoby zawijania IServiceProvider, aby zwrócić odpowiednią implementację IProvideValueTarget, ale nadal jest możliwe.

Powiązane problemy