2009-10-31 6 views
13

Existe-t-il un moyen de définir une animation quelque part dans xaml (par exemple en tant que ressource) une seule fois, puis de la réutiliser plusieurs fois? J'ai beaucoup de pinceaux indépendants sur différents datatemplates qui ont indépendamment besoin de démarrer le même type d'animation basé sur un datatrigger. Maintenant, il semble qu'une animation doit définir un Storyboard.TargetName et Storyboard.TargetProperty. Cela défait à peu près le but de la réutilisabilité. Je voudrais en quelque sorte déclarer "utiliser cette animation à partir de la ressource mais l'appliquer à un autre élément cette fois".définir des animations et des déclencheurs comme ressource réutilisable?

Pour moi, cela semble être une demande plutôt basique, importante et essentielle et je suis surpris que ce n'est pas si simple à accomplir. Est-ce que j'ai râté quelque chose?

La même chose s'applique aux déclencheurs. Supposons que j'ai beaucoup d'éléments visuels différents qui représentent tous le même type d'état en utilisant des animations de couleur. Par exemple. La seule différence entre les visuels est leur forme/arbre visuel le comportement d'animation désiré est le même, ils ont tous un élément quelque part dans leur arbre visuel qui a une propriété de type couleur. Je pense qu'il n'est pas difficile d'imaginer à quel point il est fastidieux de redéfinir les mêmes animations et les mêmes jeux de déclencheurs de données encore et encore. Chaque développeur déteste ça. Je cherche désespérément une solution plus facile qui ne nécessite pas (ou du moins très peu) de code C# derrière.

Ce que je suis venu avec à ce jour est la suivante:

Définir les animations dans une ressource lik ce (Répétez cette opération pour tous les états de base qu'il ya, comme l'activation, actif, inactif, erreur):

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

l'utilisation en story-board dans les déclencheurs (répéter cette zillions de fois pour chaque état X chaque stateviusal differnt, viennent toujours avec un nouveau nom pour le story-board):

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

Vous pouvez facilement imaginer combien XAML bourgeonnent je dois copier et coller à plusieurs reprises pour tous ces DataTriggers zillion.

Il serait cool de définir tous ces déclencheurs une fois et de l'appliquer à différents visuels d'état. Comment est-ce que quelque chose comme ça est résolu dans WPF? Un conseil?

Répondre

1

Il ne semble pas y avoir de bonne solution XAML pour ce problème général. J'ai fini par écrire mes propres propriétés attachées qui définissent tout le comportement de l'animation pour un élément donné. quelque chose comme ceci:

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

Le reste (créant l'animation etc.) est fait dans le codebehind.

+0

Je sais que j'ai essayé un tas de choses différentes pour trouver une suggestion pour cela et que ce n'était pas possible sans faire quelque chose dans le code. C'est aussi une bonne solution que je pense que vous obtiendrez. Hurrah pour l'extensibilité de WPF, non? :) –

+0

Cela peut être une bonne idée d'utiliser l'API silverlight behavior pour cela. Êtes-vous d'accord? Est-il compatible avec le framework standard .net 3.5? – bitbonk

+0

Non, ce n'est pas compatible, bien qu'ils soient supposés. Honnêtement, je pense que vous avez trouvé une excellente solution qui exploite l'architecture WPF d'une manière très "naturelle" et ne devrait pas vous deviner du tout ici. –

3

Pourriez-vous quelque chose comme ceci? Enveloppez tous vos modèles de contrôle actuels avec un élément racine invisible, p. Ex.,

  • une Bordure ou un StackPanel, dont la boîte englobera le contrôle entier.
  • Créez un style ou un modèle de contrôle pour cette zone invisible contenant tous vos déclencheurs et animations.
  • Les animations animent une propriété Color arbitraire dans la zone invisible.
  • Dans les arborescences visuelles pour tous vos différents contrôles, liez les propriétés que vous souhaitez animer à la propriété Color de l'élément racine invisible.
0

Je réalise que ce problème est un peu mort au moment de cette publication, mais j'ai trouvé une solution qui nécessite très peu de code.

Vous pouvez faire un UserControl with custom properties (Faites défiler jusqu'à environ la figure 8) qui contient votre rectangle ainsi que les animations et les déclencheurs d'état. Ce contrôle utilisateur définirait une propriété publique telle que l'état qui déclencherait le changement de couleur lorsqu'il serait modifié.

Le seul code requis est de créer vos variables en code.

Le contrôle utilisateur peut être réutilisé encore et encore sans réécrire les storyboards XAML ou les déclencheurs de données.

0

Le plus « XAML chemin » pour atteindre cet objectif que je peux penser est de créer dédié MarkupExtension qui rassemblerait l'animation du dictionnaire des ressources et définir les propriétés nécessaires - Je suppose que ceux-ci sont limités à un sous-ensemble de Storyboard.Target, Storyboard.TargetName et Storyboard.TargetProperty. Bien qu'il nécessite un code-behind, il est un effort ponctuel, en outre, MarkupExtension s sont conçus pour être utilisés avec XAML. Voici la version la plus simple:

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

L'utilisation est très simple:

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

Être aussi simple que possible cette solution a ses limites - il ne supporte pas ni Binding ni DynamicResource extensions pour les propriétés mentionnées ci-dessus. Ceci est cependant réalisable, mais nécessite un effort supplémentaire. Binding support devrait être assez simple - une question de l'utilisation correcte de XamlSetMarkupExtensionAttribute (plus un certain code passe-partout). DynamicResource support serait un peu plus délicat, et en plus d'utiliser XamlSetMarkupExtensionAttribute nécessiterait l'emballage du IServiceProvider pour retourner adéquate IProvideValueTarget mise en œuvre, mais est toujours possible.

Questions connexes