假设我有一个 ListBox
绑定(bind)到 ObservableCollection
我想动画添加/删除ListBoxItems
例如。 FadeIn/Out、SlideDown/Up 等。我该怎么做?
最佳答案
在疯狂地搜索 Google 之后,我想我应该分享一下我是如何解决这个问题的,因为这似乎是一件非常简单的事情,但 WPF 让它变得非常令人沮丧,直到你深入了解动画是如何实现的。一旦你这样做了,你就会意识到 FrameworkElement.Unloaded 对动画来说是一个无用的事件。我已经在 StackOverflow (以及其他)上看到了这个问题的许多版本,有各种骇人听闻的方法来解决这个问题。希望我可以提供一个最简单的示例,然后您可以将其用于多种用途。
我不会展示 Fade In 示例,因为已经有大量使用 Loaded 路由事件的示例涵盖了该示例。移除元素时淡出是 *@$ 中的皇家痛苦。
这里的主要问题源于当您将 Storyboard放入控制/数据模板/样式时,它们会变得很奇怪。将 DataContext(以及您的对象 ID)绑定(bind)到 Storyboard 是不可能的。 Completed 事件触发时对它刚刚完成的对象是零概念。浏览可视化树是没有用的,因为所有数据模板项的容器名称都相同!所以当然,你可以编写一个函数来搜索整个集合以查找设置了删除标志属性的对象,但这很丑陋而且老实说,只是你不想承认故意写的东西。如果您在彼此的动画长度内删除了多个对象(这是我的情况),它将不起作用。你也可以只写一个清理线程来做类似的事情,然后迷失在时间 hell 中。没有什么好玩的。我跑题了。解决方案。
假设:
那么解决方案真的很简单,很痛苦,所以如果你花了很长时间试图解决这个问题。
代码示例如下,希望这一切都容易掌握。我尽可能简化了示例,因此您需要根据自己的环境对其进行调整。
代码背后
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,
}
}
XAML
<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>
呵呵!~
关于wpf - 如何实现添加/删除列表项的淡入和淡出,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/4171829/