wpf - 如何实现添加/删除列表项的淡入和淡出

标签 wpf animation listbox

假设我有一个 ListBox绑定(bind)到 ObservableCollection我想动画添加/删除ListBoxItems例如。 FadeIn/Out、SlideDown/Up 等。我该怎么做?

最佳答案

在疯狂地搜索 Google 之后,我想我应该分享一下我是如何解决这个问题的,因为这似乎是一件非常简单的事情,但 WPF 让它变得非常令人沮丧,直到你深入了解动画是如何实现的。一旦你这样做了,你就会意识到 FrameworkElement.Unloaded 对动画来说是一个无用的事件。我已经在 StackOverflow (以及其他)上看到了这个问题的许多版本,有各种骇人听闻的方法来解决这个问题。希望我可以提供一个最简单的示例,然后您可以将其用于多种用途。

我不会展示 Fade In 示例,因为已经有大量使用 Loaded 路由事件的示例涵盖了该示例。移除元素时淡出是 *@$ 中的皇家痛苦。

这里的主要问题源于当您将 Storyboard放入控制/数据模板/样式时,它们会变得很奇怪。将 DataContext(以及您的对象 ID)绑定(bind)到 Storyboard 是不可能的。 Completed 事件触发时对它刚刚完成的对象是零概念。浏览可视化树是没有用的,因为所有数据模板项的容器名称都相同!所以当然,你可以编写一个函数来搜索整个集合以查找设置了删除标志属性的对象,但这很丑陋而且老实说,只是你不想承认故意写的东西。如果您在彼此的动画长度内删除了多个对象(这是我的情况),它将不起作用。你也可以只写一个清理线程来做类似的事情,然后迷失在时间 hell 中。没有什么好玩的。我跑题了。解决方案。

假设:

  • 您正在使用填充了一些自定义对象的 ObservableCollection
  • 您使用 DataTemplate 为它们提供自定义外观,因此您为什么要动画它们的删除
  • 您将 ObservableCollection 绑定(bind)到 ListBox(或类似的简单对象)
  • 您在 OC 中的对象类上实现了 INotifyPropertyChanged。

  • 那么解决方案真的很简单,很痛苦,所以如果你花了很长时间试图解决这个问题。
  • 在窗口的 Window.Resources 部分(在 DataTemplate 上方)创建一个 Storyboard,为您的淡出设置动画。
  • (可选)将持续时间单独定义为资源,这样您就可以尽可能避免硬编码。或者只是硬编码持续时间。
  • 在您的对象类中创建一个名为“Removing”、“isRemoving”、whatev 的公共(public) bool 属性。确保为此字段引发属性更改事件。
  • 创建一个绑定(bind)到“Removing”属性的 DataTrigger,并在 True 上播放淡出 Storyboard。
  • 在您的对象类中创建一个私有(private) DispatcherTimer 对象,并实现一个简单的计时器,该计时器与您的淡出动画具有相同的持续时间,并从其滴答处理程序的列表中删除您的对象。

  • 代码示例如下,希望这一切都容易掌握。我尽可能简化了示例,因此您需要根据自己的环境对其进行调整。

    代码背后
    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/

    相关文章:

    WPF - XAML - DataGridRow 样式触发器 IsMouseOver 工具提示数据绑定(bind)

    c# - 使用其中的新项目对 ObservableCollection<T> 进行排序的最有效方法

    ios - UIView animateWithDuration 和用户交互

    javascript 无法设置属性 selectindex 列表框

    c# - 将项目从文本框添加到列表框

    wpf - 如何使用触发器制作文本框 Visibility=Hidden

    html - 悬停在一个元素上触发多个 CSS 动画

    ios - 对键盘显示和隐藏设置动画自动布局约束

    c# - 可以加载到 Windows 窗体控件中的最大数据量是多少?

    c# - listbox selectionmode = multiextended 如何摆脱按下鼠标键选择项目