2009-10-31 8 views
13

¿Hay alguna manera de definir una animación en algún lugar de xaml (por ejemplo, como recurso) una vez y luego volver a utilizarla varias veces? Tengo una gran cantidad de pinceles independientes en diferentes plantillas de datos que de forma independiente necesitan para comenzar el mismo tipo de animación basada en un datatrigger. Ahora, dado que parece que una animación tiene que definir un Storyboard.TargetName y Storyboard.TargetProperty. Esto prácticamente frustra el propósito de la reutilización. De alguna manera me gustaría declarar "usar esta animación del recurso pero aplicarla a otro elemento esta vez".definir animaciones y desencadenantes como recurso reutilizable?

Para mí, esto parece ser una solicitud bastante básica, importante y esencial, y me sorprende que no sea tan sencillo de llevar a cabo. ¿Me estoy perdiendo de algo?

Lo mismo se aplica a los factores desencadenantes. Supongamos que tengo muchos elementos visuales diferentes que representan el mismo tipo de estado usando animaciones de color. P.ej. atenuarse a verde cuando "activo" desaparecer a "rojo" cuando "error" etc. La única diferencia entre las imágenes es su forma/árbol visual el comportamiento de animación deseado es el mismo, todos tienen un elemento en algún lugar de su árbol visual que tiene una propiedad de tipo color Creo que no es difícil imaginar lo tedioso que es redefinir las mismas animaciones y conjuntos de datatriggers una y otra vez. Todo desarrollador odia esto. Busco desesperadamente una solución más fácil que no requiera ningún (o al menos muy poco) código C# detrás.

Lo que he encontrado hasta el momento es la siguiente:

Definir las animaciones en un recurso lik este (repetir esta operación para todos los estados básicos que existen, como la activación, activo, inactivo, error):

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

el uso en guión gráfico en los disparadores (repetir esto infinidad de veces para cada estado de cada X stateviusal differnt, siempre llegar a un nuevo nombre para el guión gráfico):

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

Puede imaginarse fácilmente cuánta saturación XAML tengo que copiar y pegar repetidamente para todos esos billones de DataTriggers.

Sería genial definir todos estos activadores una vez y aplicarlos a diferentes imágenes del estado. ¿Cómo se resuelve algo como esto en WPF? ¿Algún consejo?

Respuesta

1

No parece haber ninguna buena solución XAML única para este proplem general. Terminé escribiendo mis propias propiedades adjuntas que definen todo el comportamiento de animación para un elemento dado. algo como esto:

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

El resto (crear la animación, etc.) se realiza en el código subyacente.

+0

Ya sé, probé un montón de cosas diferentes para proponer una sugerencia para esto y simplemente no fue posible sin hacer algo en el código. Esta es una buena solución como creo que vas a obtener. Hurra por la extensibilidad de WPF, ¿verdad? :) –

+0

Puede ser una buena idea usar la API de comportamiento silverlight para esto. ¿Estás de acuerdo? ¿Es compatible con el marco .net 3.5 estándar? – bitbonk

+0

No, no es compatible, aunque se supone. Honestamente, creo que has encontrado una gran solución que aprovecha la arquitectura de WPF de una manera muy "natural" y no debes adivinarte a ti mismo en absoluto aquí. –

3

¿Podría intentar algo como esto?

  • Envuelva todas sus plantillas de control actuales con un elemento raíz invisible, p. Ej. un Border o un StackPanel, cuyo recuadro delimitador cubrirá todo el control.
  • Crea una plantilla de estilo o control para este cuadro invisible que contiene todos tus disparadores y animaciones.
  • Haga que las animaciones animen una propiedad de Color arbitraria en el cuadro invisible.
  • En los árboles visuales para todos los controles diferentes, vincula las propiedades que quieras animar a la propiedad Color en el elemento raíz invisible.
0

Me doy cuenta de que este problema está un poco muerto en el momento de esta publicación, pero encontré una solución que requiere muy poco código.

Puede hacer un UserControl with custom properties (Desplácese hasta aproximadamente la Figura 8) que contiene su rectángulo, así como las animaciones y los activadores de estado. Este control de usuario definiría una propiedad pública como el estado que activaría el cambio de color cuando se modifica.

El único retraso de código requerido es crear sus variables en el código.

El control de usuario puede reutilizarse una y otra vez sin necesidad de volver a escribir los guiones XCD o desencadenadores de datos.

0

El "XAML manera" más para lograr este objetivo se me ocurre es crear dedicado MarkupExtension que tirar de la animación del diccionario de recursos y establecer las propiedades necesarias - Asumo que los están limitados a un subconjunto de Storyboard.Target, Storyboard.TargetName y Storyboard.TargetProperty. A pesar de que requiere un código subyacente, es un esfuerzo de una sola vez, además, MarkupExtension s están diseñados para ser utilizados con XAML. Aquí está la versión más 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"); 
    } 
} 

Su uso es muy sencillo:

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

Al ser tan simple como puede ser esta solución tiene sus limitaciones - que no admite ni Binding ni DynamicResource extensiones para las características antes mencionadas. Sin embargo, esto se puede lograr, pero requiere un esfuerzo adicional. El soporte Binding debe ser bastante simple: una cuestión de uso adecuado de XamlSetMarkupExtensionAttribute (más un código estándar). El soporte DynamicResource sería un poco más complicado, y además de usar XamlSetMarkupExtensionAttribute, sería necesario ajustar el IServiceProvider para devolver la implementación adecuada de IProvideValueTarget, pero aún es posible.

Cuestiones relacionadas