WPF: отображение и скрытие элементов в ItemsControl с эффектами - PullRequest
7 голосов
/ 19 июля 2011

Я использовал эту замечательную статью в качестве основы для отображения и скрытия элементов с эффектом перехода.Он работает очень аккуратно, так как позволяет связать свойство Visibility как обычно, а затем определить, что происходит при изменении видимости (например, анимировать его прозрачность или запускать раскадровку).Когда вы скрываете элемент, он использует приведение значений, чтобы он оставался видимым до завершения перехода.

Я ищу подобное решение для использования с ItemsControl и ObservableCollection.Другими словами, я хочу связать ItemsSource с ObservableCollection как обычно, но контролировать, что происходит, когда элементы добавляются и удаляются, и запускать анимацию.Я не думаю, что использование принуждения значения будет работать здесь, но очевидно, что элементы все еще должны оставаться в списке до завершения их переходов.Кто-нибудь знает какие-либо существующие решения, которые могли бы сделать это легко?

Я бы хотел, чтобы любое решение было достаточно универсальным и простым для применения в списках любых элементов.В идеале стиль и поведение анимации должны быть отдельными, и применение его к определенному списку будет такой простой задачей, как присвоение ему присоединенного свойства.

Ответы [ 3 ]

8 голосов
/ 31 июля 2011

Внедрение легко, но для затухания элементы должны будут оставаться в списке источников, пока анимация не будет завершена (как вы сказали).

Если мы все еще хотим иметь возможность использовать источник ObservableCollection в обычном режиме (Добавить / Удалить и т. Д.), То нам нужно будет создать коллекцию зеркал, которая постоянно синхронизируется с коллекцией источников с задержкой на удаление до тех пор, пока анимация завершена. Это можно сделать с помощью события CollectionChanged.

Вот реализация, которую я сделал, используя прикрепленное поведение. Он может использоваться для ItemsControl, ListBox, DataGrid или чего-либо еще, что происходит от ItemsControl.

Вместо привязки ItemsSource, связать прикрепленное свойство ItemsSourceBehavior.ItemsSource. Он создаст зеркало ObservableCollection с помощью Reflection, вместо него будет использовать зеркало как 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 голос
/ 21 января 2016

@ Фредрик Хедблад Прекрасно сделано.У меня есть несколько замечаний.

  • При добавлении элемента анимация иногда начинается с ранее добавленного элемента.

  • Вставка элементов в список, добавил их все внизу (поэтому поддержка сортированного списка отсутствует)

  • (личная проблема: необходима отдельная анимация для каждого элемента)

В приведенном ниже коде есть addaptedверсия, которая решает проблемы, перечисленные выше.

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));
}
0 голосов
/ 19 июля 2011

Present framework делает нечто похожее на это.Вот демо этого.Вы можете использовать это или сделать что-то подобное с VisualStateManager.

...