Skip to content

Instantly share code, notes, and snippets.

@gekka
Created October 30, 2020 10:17
Show Gist options
  • Save gekka/aca0c729343c454295800368e01db765 to your computer and use it in GitHub Desktop.
Save gekka/aca0c729343c454295800368e01db765 to your computer and use it in GitHub Desktop.
Answer code for QnA 138091

[QnA] How to apply animation on ListBoxItem before removal?

<Window x:Class="Gekka.GPL.Sample.WPF.App1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:Gekka.GPL.Sample.WPF.App1" mc:Ignorable="d"
        Title="MainWindow" Height="500" Width="300" >
    <DockPanel TextElement.FontSize="20">
        <Button Content="Test" Click="Button_Click"  DockPanel.Dock="Bottom" />
        <local:DelayRemoveListBox ItemsSource="{Binding}"/>
    </DockPanel>
</Window>
namespace Gekka.GPL.Sample.WPF.App1
{
    using System;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    public partial class MainWindow : Window
    {
        private System.Collections.ObjectModel.ObservableCollection<object> testCollection;

        private int x;
        public MainWindow()
        {
            InitializeComponent();
            this.Language = System.Windows.Markup.XmlLanguage.GetLanguage(System.Threading.Thread.CurrentThread.CurrentUICulture.IetfLanguageTag);

            DelayRemoveListBox listBox = new DelayRemoveListBox();
            listBox.SetBinding(DelayRemoveListBox.ItemsSourceProperty, new Binding("."));

            Button buttonAdd = new Button() { Content = "Add", Margin = new Thickness(5) };
            DockPanel.SetDock(buttonAdd, Dock.Bottom);
            buttonAdd.Click += (s, e) =>
            {
                Random rnd = new Random();
                int i = rnd.Next(Math.Max(0, testCollection.Count - 1));
                testCollection.Insert(i,++x);

            };

            Button buttonRemove = new Button() { Content = "Remove", Margin = new Thickness(5) };
            DockPanel.SetDock(buttonRemove, Dock.Bottom);
            buttonRemove.Click += (s, e) =>
            {
                Random rnd = new Random();
                if (testCollection.Count > 0)
                {
                    int i = rnd.Next(testCollection.Count - 1);
                    testCollection.RemoveAt(i);
                }
            };



            DockPanel dock = new DockPanel();
            dock.Children.Add(buttonAdd);
            dock.Children.Add(buttonRemove);
            dock.Children.Add(listBox);
            this.Content = dock;

            testCollection = new System.Collections.ObjectModel.ObservableCollection<object>();
            for (x = 0; x < 10; x++)
            {
                testCollection.Add("TEST" + x.ToString());
            }
            this.DataContext = testCollection;
        }
    }
}

namespace Gekka.GPL.Sample.WPF.App1
{
    using System;
    using System.ComponentModel;
    using System.Linq;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Data;
    using System.Windows.Media.Animation;
    using System.Windows.Media;
    using System.Collections.Specialized;

    class DelayRemoveListBox : ListBox
    {
        public DelayRemoveListBox()
        {
            internalCollection = new System.Collections.ObjectModel.ObservableCollection<DelayRemoveItemVM>();
            BindingOperations.EnableCollectionSynchronization(internalCollection, internalCollection);

            base.ItemsSource = internalCollection;
        }

        private System.Collections.ObjectModel.ObservableCollection<DelayRemoveItemVM> internalCollection;

        public new System.Collections.Specialized.INotifyCollectionChanged ItemsSource
        {
            get { return (System.Collections.Specialized.INotifyCollectionChanged)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }

        public new static readonly DependencyProperty ItemsSourceProperty
            = DependencyProperty.Register
                ("ItemsSource", typeof(System.Collections.Specialized.INotifyCollectionChanged), typeof(DelayRemoveListBox)
                , new PropertyMetadata(null, (o, e) =>
                {
                    DelayRemoveListBox listBox = (DelayRemoveListBox)o;

                    var oc = e.OldValue as System.Collections.Specialized.INotifyCollectionChanged;
                    var nc = e.NewValue as System.Collections.Specialized.INotifyCollectionChanged;

                    if (oc != null)
                    {
                        oc.CollectionChanged -= listBox.OnCollectionChanged;
                        lock (listBox.internalCollection)
                        {
                            listBox.Items.Clear();
                        }

                    }

                    if (nc != null)
                    {
                        var ic = nc as System.Collections.ICollection;
                        lock (listBox.internalCollection)
                        {
                            foreach (object item in ic)
                            {
                                DelayRemoveItemVM vm = new DelayRemoveItemVM(item, listBox);
                                listBox.internalCollection.Add(vm);
                            }
                        }
                        nc.CollectionChanged += listBox.OnCollectionChanged;
                    }
                }));

        private DelayRemoveItemVM GetVM(int orgIndex)
        {
            var orgItem = (this.ItemsSource as System.Collections.IList)[orgIndex];
            lock (this.internalCollection)
            {
                return this.internalCollection.First(_ => _.Item == orgItem);
            }
        }

        private void OnCollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            switch (e.Action)
            {
            case NotifyCollectionChangedAction.Add:
            {
                lock (this.internalCollection)
                {
                    int insertIndex = 0;
                    var list = this.ItemsSource as System.Collections.IList;

                    if (e.NewStartingIndex == 0 || this.internalCollection.Count == 0)
                    {
                        insertIndex = 0;
                    }
                    else if (e.NewStartingIndex == list.Count-1)
                    {
                        insertIndex = this.internalCollection.Count;
                    }
                    else
                    {
                        object o = list[e.NewStartingIndex+1];
                        var vm = this.internalCollection.First(_ => _.Item == o);
                        insertIndex = this.internalCollection.IndexOf(vm);

                    }

                    foreach (object item in e.NewItems)
                    {
                        this.internalCollection.Insert(insertIndex++, new DelayRemoveItemVM(item, this));
                    }
                }
                break;
            }
            case NotifyCollectionChangedAction.Remove:
            {
                if (e.OldItems != null)
                {
                    lock (this.internalCollection)
                    {
                        foreach (object item in e.OldItems)
                        {
                            var vm = this.internalCollection.First(_ => _.Item == item);
                            vm.IsRemoving = true;
                        }
                    }
                }
                break;
            }
            case NotifyCollectionChangedAction.Replace:
            {
                if (e.NewItems != null)
                {
                    lock (this.internalCollection)
                    {
                        var vm = this.internalCollection.FirstOrDefault(_ => _.Item == e.OldItems[0]);
                        vm.Item = e.NewItems[0];
                    }
                }
                break;
            }
            case NotifyCollectionChangedAction.Move:
                throw new NotSupportedException();

            case NotifyCollectionChangedAction.Reset:
                this.internalCollection.Clear();
                break;
            default:
                break;
            }

        }

        protected override DependencyObject GetContainerForItemOverride()
        {
            ListBoxItem lbi = (ListBoxItem)base.GetContainerForItemOverride();

            Binding bnd = new Binding(nameof(DelayRemoveItemVM.IsRemoving));
            lbi.SetBinding(IsRemovingProperty, bnd);

            bnd = new Binding(nameof(DelayRemoveItemVM.Item));
            lbi.SetBinding(ListBoxItem.ContentProperty, bnd);
            return lbi;
        }

        public static bool GetIsRemoving(ListBoxItem obj)
        {
            return (bool)obj.GetValue(IsRemovingProperty);
        }

        public static void SetIsRemoving(ListBoxItem obj, bool value)
        {
            obj.SetValue(IsRemovingProperty, value);
        }

        public static readonly DependencyProperty IsRemovingProperty =
            DependencyProperty.RegisterAttached
            ("IsRemoving"
            , typeof(bool)
            , typeof(DelayRemoveListBox)
            , new UIPropertyMetadata
                (default(bool)
                 , new PropertyChangedCallback(OnIsRemovingPropertyChanged)));

        private static void OnIsRemovingPropertyChanged(DependencyObject dpo, DependencyPropertyChangedEventArgs e)
        {
            ListBoxItem target = dpo as ListBoxItem;
            if (target != null)
            {
                bool newValue = (bool)e.NewValue;
                bool oldValue = (bool)e.OldValue;
                if (newValue == true)
                {
                    var story = new Storyboard();

                    var aniOpacity = new DoubleAnimation(1, 0, new Duration(TimeSpan.FromMilliseconds(500)));
                    Storyboard.SetTargetProperty(aniOpacity, new PropertyPath(ListBoxItem.OpacityProperty));
                    Storyboard.SetTarget(aniOpacity, target);

                    var aniHeight = new DoubleAnimation(target.ActualHeight, 0, new Duration(TimeSpan.FromMilliseconds(1000)));
                    Storyboard.SetTargetProperty(aniHeight, new PropertyPath(ListBoxItem.HeightProperty));
                    Storyboard.SetTarget(aniHeight, target);
                    aniHeight.EasingFunction = new PowerEase() { EasingMode = EasingMode.EaseInOut, Power = 3 };

                    Color color = System.Windows.Media.Colors.Transparent;

                    if (target.Background is System.Windows.Media.SolidColorBrush scb)
                    {
                        SolidColorBrush solid = new SolidColorBrush(scb.Color);
                        target.Background = solid;
                        var aniColor = new ColorAnimation();
                        aniColor.From = scb.Color;
                        aniColor.To = System.Windows.Media.Colors.Red;
                        aniColor.Duration = new Duration(TimeSpan.FromMilliseconds(100));
                        aniColor.FillBehavior = FillBehavior.HoldEnd;

                        Storyboard.SetTargetProperty(aniColor, new PropertyPath(nameof(ListBoxItem.Background) + "." + nameof(SolidColorBrush.Color)));
                        Storyboard.SetTarget(aniColor, target);
                        story.Children.Add(aniColor);
                    }

                    story.Children.Add(aniOpacity);
                    story.Children.Add(aniHeight);

                    story.Completed += (a, b) =>
                    {
                        if (target.DataContext is DelayRemoveItemVM vm)
                        {
                            lock (vm.Collection)
                            {
                                vm.Collection.Remove(vm);
                            }
                        }
                    };
                    story.Begin();

                    target.IsHitTestVisible = false;
                    target.SetValue(ListBoxItem.IsSelectedProperty, false);
                }
            }
        }

        public class DelayRemoveItemVM : System.ComponentModel.INotifyPropertyChanged
        {
            public event System.ComponentModel.PropertyChangedEventHandler PropertyChanged;
            protected virtual void OnPropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));

            public bool IsRemoving
            {
                get { return _Removing; }
                internal set { _Removing = value; OnPropertyChanged(nameof(IsRemoving)); }
            }
            private bool _Removing;

            public object Item
            {
                get { return _Item; }
                internal set { if (_Item != value) { _Item = value; OnPropertyChanged(nameof(Item)); } }
            }
            private object _Item;


            internal DelayRemoveListBox ListBox { get; }
            internal System.Collections.ObjectModel.ObservableCollection<DelayRemoveItemVM> Collection => ListBox.internalCollection;

            public DelayRemoveItemVM(object item, DelayRemoveListBox listBox)
            {
                this.Item = item;
                this.ListBox = listBox;
            }
        }
    }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment