WPF 实现已绑定集合项目的移除时动画过渡

摘要:
此数据的容器将立即删除,这对于动画转换来说太晚了。我的解决方案1。由于删除绑定源集合上的数据将导致相应的UI容器立即被删除,因此应在转换动画完成后实现此方法-缺点1。开发人员需要知道绑定的UI容器在移除时支持动画转换(欢迎讨论自动检测方法)。2.不能为标记为要删除的每个数据项添加bool属性,是吗?itemsToRemove.Remove(项);

信不信由你,这个场景貌似没有官方的完美解决方案。我认为这个要求一点都不过分,花了很长时间 bai google du,就是没找到好的方案。这是我花了一天时间才搞通的一个方案,跟大家分享,虽然仍然不太完美,但是希望对大家有用。

对完美的憧憬

一个已绑定到 ItemsControl 的集合,是不能通过 ItemsControl.Items 属性来访问的,如果你这样做,就会在运行时收到 InvalidOperationException。事实上,我们也不应该关心数据具体绑定到什么样的 ItemsControl 上,要删除数据,只需在 ObservableCollection 上进行 Remove 操作就可以了,然后在绑定数据的容器(例如 ListBoxItem)上收到 Unloading 事件,我们再在容器的 Style 里加一个 EventTrigger 写一段动画,这个过程中,ObservableCollection 认为数据已经删除,但 ItemsControl 仍然将数据保留到事件处理结束,这样就一切完美了。

但是在现阶段,只要绑定的集合删除一项数据,该项数据的容器会被立马删除,然后才姗姗来迟地激发 Unloaded 事件,这事件对于要做动画过渡的要求已经为时过晚,容器死不能复生了。

我的方案

1、切入点

既然在绑定源集合上删除数据会导致对应 UI 容器被立即删除,那么就不能上来就用 Remove 方法了,应该让这个方法在过渡动画完成后才执行—— 缺点1,Dev 需要知道绑定的 UI 容器支持移除时动画过渡(欢迎讨论自动检测的方法)。这里我选择使用扩展方法:

public static class ItemAnimatingRemover
{
    public static void RemoveWithAnimation<TData>( this ICollection<TData> dataSource, TData item )
        where TData : DependencyObject
    {
    }

TData 需要为 DependencyObject,为了能在 WPF 中绑定,相信这个要求不过分。

2、标记为要删除的数据项

总不能在我们的数据项里面每个都加上一个 bool 属性吧?这样就太大牺牲了,我选择使用 Attached Property。

public static readonly DependencyProperty RemovalAnimationBeginProperty =
    DependencyProperty.RegisterAttached(
        "RemovalAnimationBegin", typeof(bool), typeof(ItemAnimatingRemover));

private static void SetRemovalAnimationBegin( DependencyObject obj )
{
    obj.SetValue(RemovalAnimationBeginProperty, true);
}

3、要交给动画系统了,得找个地方寄存一下集合源

如果人家觉得看动画不耐烦了,再触发一次,就直接删除算了。

static Hashtable itemsToRemove = new Hashtable();

public static void RemoveWithAnimation<TData>( this ICollection<TData> dataSource, TData item )
    where TData : DependencyObject
{
    if ( itemsToRemove.Contains(item) )
    {
        dataSource.Remove(item);
        itemsToRemove.Remove(item);
    }
    itemsToRemove.Add((DependencyObject)item, dataSource);
    SetRemovalAnimationBegin(item);
}

这样 RemoveWithAnimation 方法就写完了,剩下的事由动画系统处理,动画完了,我们再来 Remove,所以需要暂存一下我们的 dataSource,使用 Hashtable 的原因是,TData 不确定,XAML 对泛型的支持也不好,所以,动画完了回来后,不预先保存是会丢失的。下面来看我们怎么在 XAML 中处理。

4、XAML 中响应 RemovalAnimationBegin Attached Property

这事相当需要技巧,因为现阶段 XAML 要在 ControlTemplate.Triggers 中设置自定义的 Attached Property Trigger 恐怕只有以下这一种写法能在运行时生效(文档骗人的555),假设 Designer 已经做好一个叫 ItemRemove 的 Storyboard:

<Style x:Key="DefaultItemContainerStyle" TargetType="{x:Type ListBoxItem}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ListBoxItem}">
<ControlTemplate.Triggers>
    <DataTrigger Binding="{Binding Path=(t:ItemAnimatingRemover.RemovalAnimationBegin)}" Value="true">
        <DataTrigger.EnterActions>
            <BeginStoryboard Storyboard="{StaticResource ItemRemove}"/>
        </DataTrigger.EnterActions>
    </DataTrigger>

要点:1、要用 DataTrigger;2、Binding 一定要写 Path=

现在,你可以玩一下删除动画了,但是动画完成了以后,数据项并没有真正被删除掉啊!只是从 UI 上失踪而已,我们要把它彻底清除掉。

5、动画完成以后……

动画完成后 Storyboard 的 Complete 事件会激发,so, OnComplete = …? 不行。MSDN Library 告诉我们,不能在 XAML 为 Storyboard 定义 EventHandler(看 Animate in a Style 部分最后一条),原因也很简单,因为 Completed 不是 RoutedEvent。What about an EventTrigger? 也不行。问题在于你不能自定义 TriggerAction,尽管 TriggerAction 是公共的抽象类,但是要重写的 Invoke 方法却被修饰为 internal,虽然能用 IL 重写,但是…… 再者,Storyboard 也没有 Triggers 集合让我们添加 Trigger 呀。

既然我们不能处理 Complete 事件,那么就创造一个 Complete 属性通知代码好了,更直接。

public static readonly DependencyProperty RemovalAnimationCompleteProperty =
    DependencyProperty.RegisterAttached(
        "RemovalAnimationComplete", typeof(bool), typeof(ItemAnimatingRemover),
        new PropertyMetadata(OnRemovalAnimationComplete));

等下再给出 OnRemovalAnimationComplete 的实现,现在看 XAML 怎么在动画完成时触发 RemovalAnimationComplete Attached Property:

<Storyboard x:Key="ItemRemove">
    <!-- Omitted codes generated by tools -->
    <BooleanAnimationUsingKeyFrames Storyboard.TargetProperty="(t:ItemAnimatingRemover.RemovalAnimationComplete)">
        <DiscreteBooleanKeyFrame KeyTime="00:00:00.4500000" Value="True"></DiscreteBooleanKeyFrame>
    </BooleanAnimationUsingKeyFrames>
</Storyboard>

假如这个过渡动画需要 0.4 秒完成,那么我们在 0.45 秒的地方手动插入一个关键帧,设置 RemovalAnimationComplete 属性为 true。—— 缺点2,每种过渡动画都要算一下过渡时间,而且过渡时间变了就要重新改一次(还是事件好啊!求事件解法……)。

6、清理

现在我们实现 OnRemovalAnimationComplete 方法,注意上面 XAML 设置的 Attach Property 目标是装数据的容器(如 ListBoxItem)。

static void OnRemovalAnimationComplete( DependencyObject container, DependencyPropertyChangedEventArgs e )
{
    if ( !(container is ContentControl) )
    {
        return;
    }
    var data = ((ContentControl)container).Content;
    if ( !itemsToRemove.Contains(data) || (bool)e.NewValue == false )
    {
        return;
    }
    var dataSource = itemsToRemove[data];
    dataSource.GetType().GetMethod("Remove").Invoke(dataSource, new object[] { data });
    itemsToRemove.Remove(data);
}

TData 类型在这个方法在编译时已经丢失,只能用反射调用了。

总结&感想

有些东西啊,对外声称很强大,在公开的 Demo 中也确实用很少的工作量完成了一个个很炫的功能,但是并不是所有东西都一下子能完美实现的。不能被这些光环冲昏了头脑,认为 XYZ 是万能的,这里的 WPF 就是一个例子。最后,我的方案不是很完美,主要的两个缺点我在文章中都列举出来了,希望有兴趣研究的朋友一起讨论一下,也许大家有更好的方法。

完整的例子下载

免责声明:文章转载自《WPF 实现已绑定集合项目的移除时动画过渡》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇【ZT】DBA职责与日常工作计划NLTK的使用下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

MacBookPro磁盘空间不够

256G的SSD还是快被占满了,剩余12G,本来一切运行正常。 要往U盘里拷点资料,突然电脑就罢工了,cleanMyMac 显示磁盘容量剩余 1.8G。 finder 罢工,无法重启,无法强退。 无法拷贝,无法删除文稿,无法删除应用,无法清空废纸篓。 用CleanMyMAC 卸载应用无效。 按照系统提示清理空间,资源浏览器中的内容无法删除(估计底层还是调用...

C#高级编程笔记(22至25章节)文件注册表权限事务

22安全(using System.Security.Principal;) AppDomain.CurrentDomain.SetPrincipalPolicy(PrincipalPolicy.WindowsPrincipal);//当前线程用户 var principal = WindowsPrincipal.Current as WindowsPri...

[Hardware] 机械硬盘和固态硬盘功耗对比

 因为有几块闲置的机械硬盘, 需要家用服务器中利用起来. 但需要考虑功耗. 3.5寸机械硬盘满速功耗约12W, 2.5寸的5W. 固态硬盘: 满速10W, 平时2-3W, 待机<1W. 如果机器散热较好, 可以上机械, 如果空间密闭, 建议还是固态....

IHttpHandler接口

IHttpHandler接口:定义Asp.net为了使用自定义Http处理程序同步处理Http Web请求而实现的协定。 说明:一旦定义的自己的HttpHandler,对系统的HttpHandler将是覆盖关系。 命名空间:System.Web 程序集:System.Web 语法:public interface IHttpHandler IHttpH...

RequestBodyAdvice和ResponseBodyAdvice详解,@ControllerAdvice注解

一、源码解析 这是spring 4.2新加的两个接口 1、RequestBodyAdvice public interface RequestBodyAdvice { boolean supports(MethodParameter var1, Type var2, Class<? extends HttpMessageConverter&l...

非对称加密----加解密和数字签名

一、对称加密 对称加密:加密和解密使用相同密钥的加密算法。 对称加密的特点: 1)、速度快,通常在消息发送方需要加密大量数据时使用。 2)、密钥是控制加密及解密过程的指令。 3)、算法是一组规则,规定如何进行加密和解密。 典型应用场景:离线的大量数据加密(用于存储的) 常用的加密算法:DES、3DES、AES、TDEA、Blowfifish、RC2、RC...