Wpf 如何实现淡入淡出添加/删除的列表项
假设我将一个Wpf 如何实现淡入淡出添加/删除的列表项,wpf,animation,listbox,Wpf,Animation,Listbox,假设我将一个列表框绑定到一个可观察集合,并且我想设置添加/删除列表框项的动画,例如FadeIn/Out,向下/向上滑动等。我如何才能做到这一点?为淡入和淡出创建两层板,并将其值绑定到您为列表框的不透明掩码创建的笔刷上。沿着这条路走下去,你必须包装observetecollection并实现BeforeDelete事件,…然后你可以使用EventTrigger来控制情节提要 这是一个正确的痛苦,虽然。您最好创建一个数据模板并处理框架元素。在事件触发器中加载和框架元素。卸载事件 我在下面为您准备了一
列表框
绑定到一个可观察集合
,并且我想设置添加/删除列表框项
的动画,例如FadeIn/Out,向下/向上滑动等。我如何才能做到这一点?为淡入和淡出创建两层板,并将其值绑定到您为列表框的不透明掩码创建的笔刷上。沿着这条路走下去,你必须包装observetecollection
并实现BeforeDelete事件,…然后你可以使用EventTrigger
来控制情节提要
这是一个正确的痛苦,虽然。您最好创建一个数据模板
并处理框架元素。在事件触发器
中加载
和框架元素。卸载
事件
我在下面为您准备了一个快速样品。您必须自己整理删除代码,但我相信您能胜任
<ListBox>
<ListBox.ItemsSource>
<x:Array Type="sys:String">
<sys:String>One</sys:String>
<sys:String>Two</sys:String>
<sys:String>Three</sys:String>
<sys:String>Four</sys:String>
<sys:String>Five</sys:String>
</x:Array>
</ListBox.ItemsSource>
<ListBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding}"
Opacity="0">
<TextBlock.Triggers>
<EventTrigger RoutedEvent="FrameworkElement.Loaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
Duration="00:00:02"
From="0"
To="1" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
<EventTrigger RoutedEvent="FrameworkElement.Unloaded">
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Opacity"
Duration="00:00:02"
From="1"
To="0" />
</Storyboard>
</BeginStoryboard>
</EventTrigger>
</TextBlock.Triggers>
</TextBlock>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
一个
两个
三
四
五
HTH,如果不重新写入项控件
基本实现,则Stimul8d淡出可能是不可能的。问题在于,当ItemsControl
从集合中接收到INotifyCollectionChanged
事件时,它会立即(在深层私有代码中)将项容器标记为不可见(IsVisible
是一个只读属性,它从隐藏缓存获取其值,因此无法访问)
您可以通过以下方式轻松实现淡入:
public class FadingListBox : ListBox
{
protected override void PrepareContainerForItemOverride(
DependencyObject element, object item)
{
var lb = (ListBoxItem)element;
DoubleAnimation anm = new DoubleAnimation(0, 1,
TimeSpan.FromMilliseconds(500));
lb.BeginAnimation(OpacityProperty, anm);
base.PrepareContainerForItemOverride(element, item);
}
}
但是“淡出”等价物永远不起作用,因为容器已经不可见并且无法重置
public class FadingListBox : ListBox
{
protected override void ClearContainerForItemOverride(
DependencyObject element, object item)
{
var lb = (ListBoxItem) element;
lb.BringIntoView();
DoubleAnimation anm = new DoubleAnimation(
1, 0, TimeSpan.FromMilliseconds(500));
lb.BeginAnimation(OpacityProperty, anm);
base.ClearContainerForItemOverride(element, item);
}
}
即使您有自己的自定义容器生成器,也无法克服此问题
protected override DependencyObject GetContainerForItemOverride()
{
return new FadingListBoxItem();
}
这是有意义的,因为如果容器在它所代表的数据消失后仍然可见,那么理论上你可以点击容器(启动触发器、事件等)并且可能会遇到一些微妙的错误。对于我来说FrameworkElement。卸载的事件不起作用-该项会立即消失。我很难相信多年的WPF经验并没有创造出更漂亮的东西,但看起来唯一可行的方法就是这里描述的一个黑客:?…在花了几个小时疯狂地搜索谷歌的荒野之后,我想我应该和大家分享一下我是如何解决这个问题的,因为这似乎是一件非常简单的事情,但是WPF却让它变得非常令人沮丧,直到你完全理解动画是如何实现的。一旦你这么做了,你就会意识到FrameworkElement.Unload对于动画来说是一个无用的事件。我在StackOverflow(除其他外)上看到了这个问题的许多版本,有各种各样的黑客方法来解决这个问题。希望我能提供一个最简单的例子,然后你可以想出你的许多目的
我不会展示淡入示例,因为已经有很多使用加载的routed事件的示例介绍了淡入示例。在*@$中,皇家的痛苦在于移除物品,这一点正在逐渐消失
这里的主要问题源于当你将故事板放入控件/数据模板/样式时,它们是如何变得怪异的。无法将DataContext(以及对象的ID)绑定到情节提要。“已完成”事件触发时,不知道它是在谁身上完成的。潜水的可视化树是无用的,因为您的所有数据模板项目都有相同的名称为其容器!当然,您可以编写一个函数来搜索整个集合中设置了Remove flag属性的对象,但这很难看,说实话,只是您不想承认是故意写的。如果在动画的长度范围内删除多个对象(这是我的情况),那么它将不起作用。你也可以只写一个清理线程来做类似的事情,然后迷失在时间地狱中。没有乐趣。我离题了。关于解决方案
假设:
您正在使用由一些自定义对象填充的ObservableCollection
您可以使用DataTemplate为这些对象提供自定义外观,因此您需要为其移除设置动画
将ObservableCollection绑定到一个列表框(或类似的简单对象)
您已经在OC中的对象类上实现了InotifyPropertyChange
那么这个解决方案非常简单,非常痛苦,如果你花了很长时间试图解决这个问题
创建一个故事板,在窗口的Window.Resources部分(DataTemplate上方)设置淡出动画
(可选)将持续时间单独定义为资源,以便尽可能避免硬编码。或者只是硬编码持续时间
在对象类中创建一个名为“Removing”、“isRemoving”的公共布尔属性。确保为此字段引发属性更改事件
创建一个绑定到“删除”属性的DataTrigger,并在True上播放淡出情节提要
在对象类中创建一个私有Dispatchermer对象,并实现一个简单的计时器,该计时器的持续时间与淡出动画的持续时间相同,并将对象从其标记处理程序的列表中删除
下面是代码示例,其中
public partial class MainWindow : Window
{
public static ObservableCollection<Missiles> MissileRack = new ObservableCollection<Missiles>(); // because who doesn't love missiles?
public static Duration FadeDuration;
// main window constructor
public MainWindow()
{
InitializeComponent();
// somewhere here you'll want to tie the XAML Duration to your code-behind, or if you like ugly messes you can just skip this step and hard code away
FadeDuration = (Duration)this.Resources["cnvFadeDuration"];
//
// blah blah
//
}
public void somethread_ShootsMissiles()
{
// imagine this is running on your background worker threads (or something like it)
// however you want to flip the Removing flag on specific objects, once you do, it will fade out nicely
var missilesToShoot = MissileRack.Where(p => (complicated LINQ search routine).ToList();
foreach (var missile in missilesToShoot)
{
// fire!
missile.Removing = true;
}
}
}
public class Missiles
{
public Missiles()
{}
public bool Removing
{
get { return _removing; }
set
{
_removing = value;
OnPropertyChanged("Removing"); // assume you know how to implement this
// start timer to remove missile from the rack
start_removal_timer();
}
}
private bool _removing = false;
private DispatcherTimer remove_timer;
private void start_removal_timer()
{
remove_timer = new DispatcherTimer();
// because we set the Interval of the timer to the same length as the animation, we know the animation will finish running before remove is called. Perfect.
remove_timer.Interval = MainWindow.TrackFadeDuration.TimeSpan; // I'm sure you can find a better way to share if you don't like global statics, but I am lazy
remove_timer.Tick += new EventHandler(remove_timer_Elapsed);
remove_timer.Start();
}
// use of DispatcherTimer ensures this handler runs on the GUI thread for us
// this handler is now effectively the "Storyboard Completed" event
private void remove_timer_Elapsed(object sender, EventArgs e)
{
// this is the only operation that matters for this example, feel free to fancy this line up on your own
MainWindow.MissileRack.Remove(this); // normally this would cause your object to just *poof* before animation has played, but thanks to timer,
}
}
<Window
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="Test" Height="300" Width="300">
<Window.Resources>
<Duration x:Key="cnvFadeDuration">0:0:0.3</Duration> <!-- or hard code this if you really must -->
<Storyboard x:Key="cnvFadeOut" >
<DoubleAnimation Storyboard.TargetName="cnvMissile"
Storyboard.TargetProperty="Opacity"
From="1" To="0" Duration="{StaticResource cnvFadeDuration}"
/>
</Storyboard>
<DataTemplate x:Key="MissileTemplate">
<Canvas x:Name="cnvMissile">
<!-- bunch of pretty missile graphics go here -->
</Canvas>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=Removing}" Value="true" >
<DataTrigger.EnterActions>
<!-- you could actually just plop the storyboard right here instead of calling it as a resource, whatever suits your needs really -->
<BeginStoryboard Storyboard="{StaticResource cnvFadeOut}" />
</DataTrigger.EnterActions>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Window.Resources>
<Grid>
<ListBox /> <!-- do your typical data binding and junk -->
</Grid>
</Window>
<ListBox ItemsSource="{Binding ShadowView}" IsSynchronizedWithCurrentItem="True">
<ListBox.ItemTemplate>
<DataTemplate>
<Border Loaded="OnItemViewLoaded">
<TextBlock Text="{Binding}"/>
</Border>
</DataTemplate>
</ListBox.ItemTemplate>
</ListBox>
private void OnItemViewLoaded (object sender, RoutedEventArgs e)
{
var fe = (FrameworkElement) sender ;
var dc = (DependencyObject) fe.DataContext ;
dc.SetValue (ShadowViewSource.ViewProperty, fe) ;
}
private readonly ShadowViewSource m_shadow ;
public ICollectionView ShadowView => m_shadow.View ;
public MainWindow ()
{
m_collection = new ObservableCollection<...> () ;
m_view = CollectionViewSource.GetDefaultView (m_collection) ;
m_shadow = new ShadowViewSource (m_view) ;
InitializeComponent ();
}
using System ;
using System.Collections.Generic ;
using System.Collections.ObjectModel ;
using System.Collections.Specialized ;
using System.ComponentModel ;
using System.Linq ;
using System.Windows ;
using System.Windows.Data ;
using System.Windows.Media.Animation ;
namespace ShadowView
{
public class ShadowViewSource
{
public static readonly DependencyProperty ViewProperty = DependencyProperty.RegisterAttached ("View", typeof (FrameworkElement), typeof (ShadowViewSource)) ;
private readonly ICollectionView m_sourceView ;
private readonly IEnumerable<object> m_source ;
private readonly ICollectionView m_view ;
private readonly ObservableCollection<object> m_collection ;
public ShadowViewSource (ICollectionView view)
{
var sourceChanged = view.SourceCollection as INotifyCollectionChanged ;
if (sourceChanged == null)
throw new ArgumentNullException (nameof (sourceChanged)) ;
var sortChanged = view.SortDescriptions as INotifyCollectionChanged ;
if (sortChanged == null)
throw new ArgumentNullException (nameof (sortChanged)) ;
m_source = view.SourceCollection as IEnumerable<object> ;
if (m_source == null)
throw new ArgumentNullException (nameof (m_source)) ;
m_sourceView = view ;
m_collection = new ObservableCollection<object> (m_source) ;
m_view = CollectionViewSource.GetDefaultView (m_collection) ;
m_view.MoveCurrentTo (m_sourceView.CurrentItem) ;
m_sourceView.CurrentChanged += OnSourceCurrentChanged ;
m_view.CurrentChanged += OnViewCurrentChanged ;
sourceChanged.CollectionChanged += OnSourceCollectionChanged ;
sortChanged.CollectionChanged += OnSortChanged ;
}
private void OnSortChanged (object sender, NotifyCollectionChangedEventArgs e)
{
using (m_view.DeferRefresh ())
{
var sd = m_view.SortDescriptions ;
sd.Clear () ;
foreach (var desc in m_sourceView.SortDescriptions)
sd.Add (desc) ;
}
}
private void OnSourceCollectionChanged (object sender, NotifyCollectionChangedEventArgs e)
{
var toAdd = m_source.Except (m_collection) ;
var toRemove = m_collection.Except (m_source) ;
foreach (var obj in toAdd)
m_collection.Add (obj) ;
foreach (DependencyObject obj in toRemove)
{
var view = (FrameworkElement) obj.GetValue (ViewProperty) ;
var begintime = 1 ;
var sb = new Storyboard { BeginTime = TimeSpan.FromSeconds (begintime) } ;
sb.Completed += (s, ea) => m_collection.Remove (obj) ;
var fade = new DoubleAnimation (1, 0, new Duration (TimeSpan.FromMilliseconds (500))) ;
Storyboard.SetTarget (fade, view) ;
Storyboard.SetTargetProperty (fade, new PropertyPath (UIElement.OpacityProperty)) ;
sb.Children.Add (fade) ;
var size = new DoubleAnimation (view.ActualHeight, 0, new Duration (TimeSpan.FromMilliseconds (250))) ;
Storyboard.SetTarget (size, view) ;
Storyboard.SetTargetProperty (size, new PropertyPath (FrameworkElement.HeightProperty)) ;
sb.Children.Add (size) ;
size.BeginTime = fade.Duration.TimeSpan ;
sb.Begin () ;
}
}
private void OnViewCurrentChanged (object sender, EventArgs e)
{
m_sourceView.MoveCurrentTo (m_view.CurrentItem) ;
}
private void OnSourceCurrentChanged (object sender, EventArgs e)
{
m_view.MoveCurrentTo (m_sourceView.CurrentItem) ;
}
public ICollectionView View => m_view ;
}
}