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