2011-07-19 24 views
7

He estado usando this great article como una base para mostrar y ocultar elementos con un efecto de transición. Funciona muy bien ya que le permite vincular la propiedad Visibility de la forma habitual, luego define qué sucede cuando la visibilidad cambia (por ejemplo, anima su opacidad o activa un guión gráfico). Cuando oculta un elemento, usa la coerción de valor para mantenerlo visible hasta que finaliza la transición.WPF: Mostrar y ocultar elementos en un ItemsControl con efectos

Estoy buscando una solución similar para usar con ItemsControl y ObservableCollection. En otras palabras, quiero vincular el ItemsSource a un ObservableCollection de forma normal, pero controlo lo que sucede cuando los elementos se agregan y eliminan y desencadenan animaciones. No creo que el uso de la coerción de valores funcione aquí, pero obviamente, los elementos aún deben permanecer en la lista hasta que finalicen sus transiciones. ¿Alguien sabe de alguna solución existente que lo haga fácil?

Me gustaría que cualquier solución sea razonablemente genérica y fácil de aplicar a las listas de cualquier tipo de artículos. Idealmente, el estilo y el comportamiento de la animación serían independientes, y su aplicación a una lista en particular sería una tarea simple, como otorgarle una propiedad adjunta.

+0

tengo solución que funcione para añadir elementos ... yo también estoy buscando la eliminación de elementos cosa ... – Bathineni

Respuesta

8

El fundido de entrada es fácil, pero para el fundido de salida los elementos deberán permanecer en la lista fuente hasta que se complete la animación (como usted dijo).

Si todavía queremos poder usar la fuente ObservableCollection normalmente (Agregar/Quitar etc.) entonces tendríamos que crear una colección espejo que esté constantemente sincronizada con la colección fuente con un retraso para eliminar hasta la animación esta completado. Esto se puede hacer con el evento CollectionChanged.

Aquí hay una implementación que hice de esto, usando un comportamiento adjunto. Se puede usar para ItemsControl, ListBox, DataGrid o cualquier otra cosa que se derive de ItemsControl.

En lugar de Encuadernar ItemsSource, enlazar la propiedad adjunta ItemsSourceBehavior.ItemsSource. Creará un espejo ObservableCollection usando Reflection, use el espejo como ItemsSource y maneje las animaciones FadeIn/FadeOut.
Tenga en cuenta que no he probado esto exhaustivamente y que podría haber errores y varias mejoras que se pueden hacer, pero funcionó muy bien en mis escenarios.

Uso Muestra

<ListBox behaviors:ItemsSourceBehavior.ItemsSource="{Binding MyCollection}"> 
    <behaviors:ItemsSourceBehavior.FadeInAnimation> 
     <Storyboard> 
      <DoubleAnimation Storyboard.TargetProperty="Opacity" 
          From="0.0" 
          To="1.0" 
          Duration="0:0:3"/> 
     </Storyboard> 
    </behaviors:ItemsSourceBehavior.FadeInAnimation> 
    <behaviors:ItemsSourceBehavior.FadeOutAnimation> 
     <Storyboard> 
      <DoubleAnimation Storyboard.TargetProperty="Opacity" 
          To="0.0" 
          Duration="0:0:1"/> 
     </Storyboard> 
    </behaviors:ItemsSourceBehavior.FadeOutAnimation> 
    <!--...--> 
</ListBox> 

ItemsSourceBehavior

public class ItemsSourceBehavior 
{ 
    public static readonly DependencyProperty ItemsSourceProperty = 
     DependencyProperty.RegisterAttached("ItemsSource", 
              typeof(IList), 
              typeof(ItemsSourceBehavior), 
              new UIPropertyMetadata(null, ItemsSourcePropertyChanged)); 
    public static void SetItemsSource(DependencyObject element, IList value) 
    { 
     element.SetValue(ItemsSourceProperty, value); 
    } 
    public static IList GetItemsSource(DependencyObject element) 
    { 
     return (IList)element.GetValue(ItemsSourceProperty); 
    } 

    private static void ItemsSourcePropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) 
    { 
     ItemsControl itemsControl = source as ItemsControl; 
     IList itemsSource = e.NewValue as IList; 
     if (itemsControl == null) 
     { 
      return; 
     } 
     if (itemsSource == null) 
     { 
      itemsControl.ItemsSource = null; 
      return; 
     } 

     Type itemsSourceType = itemsSource.GetType(); 
     Type listType = typeof(ObservableCollection<>).MakeGenericType(itemsSourceType.GetGenericArguments()[0]); 
     IList mirrorItemsSource = (IList)Activator.CreateInstance(listType); 
     itemsControl.SetBinding(ItemsControl.ItemsSourceProperty, new Binding{ Source = mirrorItemsSource }); 

     foreach (object item in itemsSource) 
     { 
      mirrorItemsSource.Add(item); 
     } 
     FadeInContainers(itemsControl, itemsSource); 

     (itemsSource as INotifyCollectionChanged).CollectionChanged += 
      (object sender, NotifyCollectionChangedEventArgs ne) => 
     { 
      if (ne.Action == NotifyCollectionChangedAction.Add) 
      { 
       foreach (object newItem in ne.NewItems) 
       { 
        mirrorItemsSource.Add(newItem); 
       } 
       FadeInContainers(itemsControl, ne.NewItems); 
      } 
      else if (ne.Action == NotifyCollectionChangedAction.Remove) 
      { 
       foreach (object oldItem in ne.OldItems) 
       { 
        UIElement container = itemsControl.ItemContainerGenerator.ContainerFromItem(oldItem) as UIElement; 
        Storyboard fadeOutAnimation = GetFadeOutAnimation(itemsControl); 
        if (container != null && fadeOutAnimation != null) 
        { 
         Storyboard.SetTarget(fadeOutAnimation, container); 

         EventHandler onAnimationCompleted = null; 
         onAnimationCompleted = ((sender2, e2) => 
         { 
          fadeOutAnimation.Completed -= onAnimationCompleted; 
          mirrorItemsSource.Remove(oldItem); 
         }); 

         fadeOutAnimation.Completed += onAnimationCompleted; 
         fadeOutAnimation.Begin(); 
        } 
        else 
        { 
         mirrorItemsSource.Remove(oldItem); 
        } 
       } 
      } 
     }; 
    } 

    private static void FadeInContainers(ItemsControl itemsControl, IList newItems) 
    { 
     EventHandler statusChanged = null; 
     statusChanged = new EventHandler(delegate 
     { 
      if (itemsControl.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated) 
      { 
       itemsControl.ItemContainerGenerator.StatusChanged -= statusChanged; 
       foreach (object newItem in newItems) 
       { 
        UIElement container = itemsControl.ItemContainerGenerator.ContainerFromItem(newItem) as UIElement; 
        Storyboard fadeInAnimation = GetFadeInAnimation(itemsControl); 
        if (container != null && fadeInAnimation != null) 
        { 
         Storyboard.SetTarget(fadeInAnimation, container); 
         fadeInAnimation.Begin(); 
        } 
       } 
      } 
     }); 
     itemsControl.ItemContainerGenerator.StatusChanged += statusChanged; 
    } 

    public static readonly DependencyProperty FadeInAnimationProperty = 
     DependencyProperty.RegisterAttached("FadeInAnimation", 
              typeof(Storyboard), 
              typeof(ItemsSourceBehavior), 
              new UIPropertyMetadata(null)); 
    public static void SetFadeInAnimation(DependencyObject element, Storyboard value) 
    { 
     element.SetValue(FadeInAnimationProperty, value); 
    } 
    public static Storyboard GetFadeInAnimation(DependencyObject element) 
    { 
     return (Storyboard)element.GetValue(FadeInAnimationProperty); 
    } 

    public static readonly DependencyProperty FadeOutAnimationProperty = 
     DependencyProperty.RegisterAttached("FadeOutAnimation", 
              typeof(Storyboard), 
              typeof(ItemsSourceBehavior), 
              new UIPropertyMetadata(null)); 
    public static void SetFadeOutAnimation(DependencyObject element, Storyboard value) 
    { 
     element.SetValue(FadeOutAnimationProperty, value); 
    } 
    public static Storyboard GetFadeOutAnimation(DependencyObject element) 
    { 
     return (Storyboard)element.GetValue(FadeOutAnimationProperty); 
    } 
} 
+1

¡Esta es una gran solución! Hubo un pequeño error al iniciar la animación de fundido de salida: los controladores de eventos no se eliminaron del guión gráfico, por lo que se acumularon y se eliminaron varios elementos cuando se eliminó uno. He editado tu respuesta y solucionado el problema. –

+1

Llegué tan lejos como la lista reflejada anteriormente, pero no pude encontrar la manera de adjuntar el guión gráfico a los elementos. Mi solución implicaba forzar la propiedad ItemsSource para generar y devolver la lista duplicada. De esta forma, puede continuar utilizando la misma propiedad antigua ItemsSource y establecer storyboards usando estilos. Voy a publicar esa solución aquí también si la hago funcionar. Gracias Meleak. –

+0

@Tim Rogers: Coaccionar la propiedad ItemsSource suena genial, ¡será interesante ver si lo haces funcionar! También estoy jugando con una animación de movimiento para 'NotifyCollectionChangedAction.Move', también actualizaré aquí si logro juntar algo –

0

Present framework hace algo similar a esto. Aquí hay un demo de él. Puede usarlo o hacer algo similar con VisualStateManager.

+0

similares, pero no me ayuda a unirse a un 'ObservableCollection' y se enganchan dentro de sus eventos por lo que puedo ver –

0

@Fredrik Hedblad bien hecho. Tengo algunas observaciones

  • Al agregar un elemento, la animación a veces comienza en el elemento agregado previamente.

  • insertar elementos en la lista, todos ellos añade a la parte inferior (por lo que no hay soporte lista ordenada)

  • (edición personal: se necesita la animación independiente para cada elemento)

En código de abajo en tener una versión adaptada, que resuelve los problemas enumerados anteriormente.

public class ItemsSourceBehavior 
{ 
    public static void SetItemsSource(DependencyObject element, IList value) 
    { 
     element.SetValue(ItemsSourceProperty, value); 
    } 

    public static IList GetItemsSource(DependencyObject element) 
    { 
     return (IList) element.GetValue(ItemsSourceProperty); 
    } 

    private static void ItemsSourcePropertyChanged(DependencyObject source, DependencyPropertyChangedEventArgs e) 
    { 
     //If animations need to be run together set this to 'false'. 
     const bool separateAnimations = true; 

     var itemsControl = source as ItemsControl; 
     var itemsSource = e.NewValue as IList; 
     if (itemsControl == null) 
     { 
      return; 
     } 
     if (itemsSource == null) 
     { 
      itemsControl.ItemsSource = null; 
      return; 
     } 

     var itemsSourceType = itemsSource.GetType(); 
     var listType = typeof (ObservableCollection<>).MakeGenericType(itemsSourceType.GetGenericArguments()[0]); 
     var mirrorItemsSource = (IList) Activator.CreateInstance(listType); 
     itemsControl.SetBinding(ItemsControl.ItemsSourceProperty, new Binding {Source = mirrorItemsSource}); 

     foreach (var item in itemsSource) 
     { 
      mirrorItemsSource.Add(item); 
      if (separateAnimations) 
       StartFadeInAnimation(itemsControl, new List<object> {item}); 
     } 

     if (!separateAnimations) 
     { 
      StartFadeInAnimation(itemsControl, itemsSource); 
     } 

     (itemsSource as INotifyCollectionChanged).CollectionChanged += 
      (object sender, NotifyCollectionChangedEventArgs ne) => 
      { 
       if (ne.Action == NotifyCollectionChangedAction.Add) 
       { 
        foreach (var newItem in ne.NewItems) 
        { 
         //insert the items instead of just adding them 
         //this brings support for sorted collections 
         mirrorItemsSource.Insert(ne.NewStartingIndex, newItem); 

         if (separateAnimations) 
         { 
          StartFadeInAnimation(itemsControl, new List<object> {newItem}); 
         } 
        } 

        if (!separateAnimations) 
        { 
         StartFadeInAnimation(itemsControl, ne.NewItems); 
        } 
       } 
       else if (ne.Action == NotifyCollectionChangedAction.Remove) 
       { 
        foreach (var oldItem in ne.OldItems) 
        { 
         var container = itemsControl.ItemContainerGenerator.ContainerFromItem(oldItem) as UIElement; 
         var fadeOutAnimation = GetFadeOutAnimation(itemsControl); 
         if (container != null && fadeOutAnimation != null) 
         { 
          Storyboard.SetTarget(fadeOutAnimation, container); 

          EventHandler onAnimationCompleted = null; 
          onAnimationCompleted = ((sender2, e2) => 
          { 
           fadeOutAnimation.Completed -= onAnimationCompleted; 
           mirrorItemsSource.Remove(oldItem); 
          }); 

          fadeOutAnimation.Completed += onAnimationCompleted; 
          fadeOutAnimation.Begin(); 
         } 
         else 
         { 
          mirrorItemsSource.Remove(oldItem); 
         } 
        } 
       } 
      }; 
    } 

    private static void StartFadeInAnimation(ItemsControl itemsControl, IList newItems) 
    { 
     foreach (var newItem in newItems) 
     { 
      var container = itemsControl.ItemContainerGenerator.ContainerFromItem(newItem) as UIElement; 
      var fadeInAnimation = GetFadeInAnimation(itemsControl); 
      if (container != null && fadeInAnimation != null) 
      { 
       Storyboard.SetTarget(fadeInAnimation, container); 
       fadeInAnimation.Begin(); 
      } 
     } 
    } 

    public static void SetFadeInAnimation(DependencyObject element, Storyboard value) 
    { 
     element.SetValue(FadeInAnimationProperty, value); 
    } 

    public static Storyboard GetFadeInAnimation(DependencyObject element) 
    { 
     return (Storyboard) element.GetValue(FadeInAnimationProperty); 
    } 

    public static void SetFadeOutAnimation(DependencyObject element, Storyboard value) 
    { 
     element.SetValue(FadeOutAnimationProperty, value); 
    } 

    public static Storyboard GetFadeOutAnimation(DependencyObject element) 
    { 
     return (Storyboard) element.GetValue(FadeOutAnimationProperty); 
    } 

    public static readonly DependencyProperty ItemsSourceProperty = 
     DependencyProperty.RegisterAttached("ItemsSource", 
      typeof (IList), 
      typeof (ItemsSourceBehavior), 
      new UIPropertyMetadata(null, ItemsSourcePropertyChanged)); 

    public static readonly DependencyProperty FadeInAnimationProperty = 
     DependencyProperty.RegisterAttached("FadeInAnimation", 
      typeof (Storyboard), 
      typeof (ItemsSourceBehavior), 
      new UIPropertyMetadata(null)); 

    public static readonly DependencyProperty FadeOutAnimationProperty = 
     DependencyProperty.RegisterAttached("FadeOutAnimation", 
      typeof (Storyboard), 
      typeof (ItemsSourceBehavior), 
      new UIPropertyMetadata(null)); 
} 
Cuestiones relacionadas