2011-07-19 104 views
7

我一直使用this great article作爲顯示和隱藏具有過渡效果的元素的基礎。它的工作原理非常巧妙,它可以讓您像平常一樣綁定Visibility屬性,然後定義可見性更改時發生的情況(例如,爲其不透明度設置動畫或觸發故事板)。當你隱藏一個元素時,它使用值強制來保持它可見,直到轉換完成。WPF:使用效果在ItemsControl中顯示和隱藏項目

我正在尋找一種類似的解決方案來使用ItemsControlObservableCollection。換句話說,我想像往常一樣將ItemsSource綁定到ObservableCollection,但控制添加和刪除項目並觸發動畫時發生的情況。我不認爲使用價值強制會在這裏起作用,但顯然,項目仍然需要留在列表中,直到他們的轉換完成。有誰知道任何現有的解決方案,這將使這容易嗎?

我想任何解決方案都是合理通用的,並且很容易適用於任何類型的項目列表。理想情況下,風格和動畫行爲將是分開的,並將其應用於特定列表將是一項簡單的任務,例如賦予它一個附加屬性。

+0

我有解決方案,可用於增加項目工作...我也找刪除項目的事情... – Bathineni

回答

8

淡入淡入很簡單,但淡入淡出之前,項目需要保留在源列表中,直到動畫完成爲止(如您所說)。

如果我們仍然希望能夠正常使用源碼ObservableCollection(添加/刪除等),那麼我們將不得不創建一個與源集合保持同步的鏡像集合,並延遲刪除直到動畫完成了。這可以通過CollectionChanged事件完成。

這是我用這個做的一個實現,使用附加的行爲。它可以用於ItemsControl,ListBox,DataGrid或來自ItemsControl的任何其他內容。

而不是綁定ItemsSource,綁定附加屬性ItemsSourceBehavior.ItemsSource。它將使用Reflection創建一個鏡像ObservableCollection,使用鏡像作爲ItemsSource來代替並處理FadeIn/FadeOut動畫。
請注意,我沒有對此進行廣泛的測試,可能會出現一些錯誤和一些可以改進的地方,但它在我的場景中運行良好。

用法示例

<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

這是一個很棒的解決方案!開始淡出動畫時有一個小錯誤:事件處理程序沒有從故事板中移除,因此一直被刪除,導致多個項目被刪除。我編輯了你的答案並解決了問題。 –

+1

我之前已經得到了鏡像列表,但無法弄清楚如何將故事板附加到物品上。我的解決方案涉及強制ItemsSource屬性來生成並返回鏡像列表。這樣,您可以繼續使用相同的舊ItemsSource屬性並使用樣式設置故事板。如果我得到它的工作,我也會在這裏發佈這個解決方案。謝謝Meleak。 –

+0

@蒂姆羅傑斯:強制ItemsSource屬性聽起來不錯,看看你是否能夠正常工作會很有趣!我也在玩'NotifyCollectionChangedAction.Move'的移動動畫,我會在這裏更新,如果我設法把東西放在一起 –

0

Present框架與此類似。這是它的一個demo。您可以使用它或做類似的事情VisualStateManager.

+0

類似,但它並不能幫助我綁定到'ObservableCollection'和鉤住其事件據我所見。 –

0

@Fredrik Hedblad 幹得漂亮。我有幾句話。

  • 添加項目時,動畫有時會從先前添加的項目開始。

  • 插入項目進入榜單,他們都加入到了底(所以沒有排序列表支持)

  • (個人的問題:需要單獨的動畫,每一個項目)

在下面的代碼中有一個加插版本,它解決了上面列出的問題。

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