WPF开发日记—解决拖动行为附加到元素上的延迟

此文的前提是 结合ItemsControl在Canvas中动态添加控件的最MVVM的方式

上一篇博客讲到了我通过使用 ItemsControls 内部设置Canvs为布局面板

并在ItemContainerStyle中设置Canvs.Left, Canvas.Top。

来实现设置MVVM模式下自定义位置显示单项内容的功能。

接下来的一步要显示内容的拖动。

很抱歉的告诉大家,经过这两天的探索,我打算推翻上面的做法。

虽然可以这样用,学到了些东西,但是效果并不是很好。

原因在于,当后面我需要给 Item 加上一个可拖动的功能时,就出现问题了。

我起初是这样做的:创建了一个自定义的可拖动行为类(DragMoveBehavior),

我本是先要测试下通过行为设置 Canvas.Left, Canvas.Top 是否是可用的,

实现了以后,我新建了个测试窗体,放进去一个Canvas,里面再随便塞了一个矩形(Rectangle),给矩形添加了我自定义的拖动行为。

问题很快就出现了,当我拖动的太快,矩形根不上我的鼠标速度,就被丢下了,不再跟着我走。

理想状态下,应该是我拖动的再快,矩形再小,也应该无时差的一直跟着我的鼠标走才对啊。

虽然稍微慢点是可以跟着走的,但我无论如何不能忍受这样的操作体验。

我测试来测试去,发现Canvas.SetLeft是有细微的延迟的。

未经严格证实的猜测是鼠标移动和设置位置两个任务,在主线程的处理优先级是不一致的(DispatcherPriority)。

这个时候,鼠标每移动一个像素点,都需要不断的Canvas.SetLeft,重复不停的布局操作无疑不是很好的选择。

想到 Blend 里面的行为自带了 MouseDragElementBehavior 行为拖动效果,测试发现这个功能在操作上是没有瑕疵的。

然而我并不能直接拿来用。因为相关的位置数据是要读取和保存的,而不只是有一个拖动效果。

所以我直接用 dotPeek 反编译看下微软是怎么实现的。 dotPeek 果然强大,最大的特点是 每个方法都生成了非常完整的中文注释。甚至直接查看基类,也有完整的代码和中文注释。

而我同时用 Reflector  反编译没有注释还报错。 ILSpy 只有部分方法有中文注释。
哦,原来微软用的是 RenderTransform 呈现变形!好东西,我怎么又没想到呢?

不过微软写的比较复杂,600多行代码,用了矩阵变形,实现了变形深克隆,内置了X,Y的依赖属性等等。

依赖属性?难道可以直接用?试了下,不行,没有变化。

太困了,凌晨四点了,睡觉先。

趴在床上,准备睡觉,习惯性翻开mac爽爽vim,突然想到,苹果系统拖动效果是怎样的呢,测试了下,发现竟然有完全一样的问题,然而在鼠标瞬间脱离UI元素的时候,并没有失去鼠标捕获。捕获?哦,我懂了,如果用 MouseCapture 对元素进行捕获就可以了,鼠标不在UI元素上也没问题!放心了,睡觉。

——————————————————

今天着重把 MouseDragElementBehavior 类看明白了,并把源代码调整了下,基本可用了。

最终的使用方式看起来是这样的

<DataTemplate DataType="vm:MyItemViewModel">
  <Border Width="120" Height="30" Background="Red">
    <i:Interaction.Behaviors>
      <ib:MouseDragElementBehavior X="{Binding Left, Mode=TwoWay}" Y="{Binding Top, Mode=TwoWay}" />
    </i:Interaction.Behaviors>
    <TextBlock HorizontalAlignment="Center" Foreground="White" VerticalAlignment="Center" Text="{Binding Show}" />
  </Border>
</DataTemplate>

然而这个时候,我是在 DataTemplete 中的Border根节点附加的 拖动行为。

总感觉哪里不对,哪里不对呢,拖动的时候有细微的晃动,并且Border不应该被用来干拖动这样的事情,另外没有有效的利用路由事件的优势。

这时我对使用 ItemsControl 这个控件产生了怀疑,跑去研究了下ListView,看了下官方ListView四种示例的Demo(地址)认真看完Demo的具体使用方式,发觉这和我想要的东西相差甚远,ListView主要的用途是可以以自定义视图展示可视项,想来想去还是回到ItemsControl 的怀抱上来吧,暂时先以 ItemsControl + Canvas 的组合前提下做调整吧。

仔细分析 MouseDragElementBehavior ,最后发现官方这个类的实现竟然和窗口复杂度有关!

里面有这样一个觉得不好的属性的实现:

/// <summary>
/// 获得关联的对象所在的场景的根元素。
/// </summary>
private UIElement RootElement
{
  get
  {
    DependencyObject reference = AssociatedObject;
    for (DependencyObject dependencyObject = reference;
      dependencyObject != null;
      dependencyObject = VisualTreeHelper.GetParent(reference))
    {
      reference = dependencyObject;
    }
    return reference as UIElement;
  }
}

每产生一次Move事件,也就是每移动一个像素点,就要重新遍历去获取一次根元素···然而我的窗口并非一个只有一个跟节点的Demo,嵌套还是很多层的,虽然我是在UserControl 里面实现的,但最终它还是找到了最外层的Windows窗体元素。

如果窗口简单,那么拖动效果还可以,到了我的程序里面,这个拖动效果就有了一定的延迟。

类似的问题不少,我决定根据自己的思路重写这个行为,以达到优化的目的。

同时,我想到了,拖动效果不理想,拖动,会不会有更好的实现呢?Border不太合适?那我为何不用Thumb?测试了下,发现非常有局限性,Thumb虽然用起来简单,但是需要依赖Canvas做容器,并且不支持直接内容,我要想添加内容,只能想到覆盖一层装饰器了,不太合适。

还是继续按我原来的想法优化拖动行为吧!

首先,我删除掉了之前写的 DragMoveBehavior 行为的内容,参考MouseDragElementBehavior ,用 RenderTransform 来实现对鼠标拖动的响应机制。这个过程我发现 Blend 内置的拖动行为类,还有很多我并不需要处理的逻辑和需要优化的地方。当我写完需要的实现,500多行代码变成了100多行。

测试了一下,在我这个复杂的窗体中拖动延迟降低了不少。

可我还是觉得不对!因为拖动的时候有略重的感觉。

难道我优化过的行为实现的不对?

新建一个WPF窗口。放进去一个矩形,附加上我刚刚编写的行为,运行,拖动过程非常流畅!!!

什么情况?

两个窗体,一样的控件,使用效果大不相同,想原因。

对比区别,前者的特别之处是:

1. 我是把控件放在了 UserControl里面了。

2. 窗体本身是我重定义的,也就是去掉了Windows 系统默认的窗体标题栏,并重写了窗体模板。

3. 自定义程序窗体添加了Windows 窗体发光,窗体圆角,窗体透明

4. 解决自定义窗体最大化以及为了完全模拟Windows系统窗体的一系列效果,我使用了 HwndSource 对窗口消息进行了处理。

那么开始针对性查找问题根源:

首先仅不使用UserControl,没有变化,还是有延迟。仅删除窗体模板中的大量内容,依然有延迟。仅注释对窗口消息的处理入口,还是延迟!晕。

就剩下窗体效果了,改窗体的Style吧,唯一有关系的就剩下窗体透明了,AllowsTransparency 改成 False,流畅了!

原因找到了,但为什么会有这样的事情?苍天啊,设置为False,窗体发光和圆角以及窗体本身的透明都是受影响的啊。

去翻 文档 吧,AllowsTransparency 是个允许窗口本身上设置 Background 为透明颜色时,必须设置为 True,才会起作用的东西,同时可以帮助实现非矩形窗口的创建。

图形呈现层 一文中,可以了解到,非矩形窗口 又叫做 Layered windows,在Vista以上的操作系统上是硬件加速的。

这样等于开启了硬件加速?我用的win8.1,硬件也是主流配置,玩大型3D网游特效全开也不会卡,而我重新建一个WPF空白窗体,设置为允许透明,背景设置为非透明的白色,在这上面我就放一个矩形连拖动都不流畅,你告诉我这还硬件加速了?

我突然对使用WPF产生了深深的自卑感。

不甘心,跑到 stackoverflow 上搜搜看,类似的问题还真不少,然而并没有好的解决方案。基本的结论是:

1. 大多数性能问题都会因为AllowsTransparency 设置为True 而产生,尤其是动画类效果。

2. AllowsTransparency 和其他技术集成,比如 DirectShow 不能正常运行,再比如我以前遇到的 WebBrowser内容无法显示等,甚至 WindowsFromsHost 这种可以允许在WPF窗体中使用Winform控件的类都失去了作用···也就是说使用它会产生各种不可预期的BUG,而你可能一时间无法想到 这个 窗体透明的设置竟然会是元凶。

3. 经验表明,WPF中的硬件加速,并没有多么惊人的效果,至少它产生的问题与复杂程度让人对使用它优秀的一面产生了畏惧。

4. 如果是企业级应用,尽量避免使用 AllowsTransparency。如果只是一个简单的项目那么可以玩玩。

还是悲哀。为了性能,我还是决定去掉 AllowsTransparency,窗体圆角和发光没有了很遗憾,打算后面有时间换一种方式实现圆角和发光,比如双窗体。

最后,既然使用了RenderTransform,那事实上,ItemsPanel 设置的Canvas 已经没有实际意义了。然而也没有更加轻量的布局控件进行替代,暂时也没有必要使用自定义Panel,Canvas先用着。

最后的最后,附上 我重写的拖动行为的 Demo,特点:

1. 可以附加到任意FrameworkElement元素。

2. 父级元素可以为任意类型的布局控件。

3. X,Y为依赖属性可以设置初值或参与数据绑定。

4. 还没有扩展可拖动边界的计算。

Demo下载地址

本文原创,转载请注明出处。

时间: 2024-10-18 05:49:28

WPF开发日记—解决拖动行为附加到元素上的延迟的相关文章

微信小程序开发日记——高仿知乎日报(上)

本人对知乎日报是情有独钟,看我的博客和github就知道了,写了几个不同技术类型的知乎日报APP 要做微信小程序首先要对html,css,js有一定的基础,还有对微信小程序的API也要非常熟悉 我将该教程分为以下三篇 微信小程序开发日记--高仿知乎日报(上) 微信小程序开发日记--高仿知乎日报(中) 微信小程序开发日记--高仿知乎日报(下) 三篇分别讲不同的组件和功能块 这篇要讲 API分析 启动页 轮播图 日报列表 浮动按钮 侧滑菜单 API分析 以下是使用到的具体API,更加详细参数和返回结

车联网开发日记4

今天是车联网开发的第四天,继续昨天的进展,对项目的主要功能方面的代码进行学习和整理,今天主要是百度地图的显示和定位,和android 服务端的实现.而且我们规定了编码项目的api和android的版本. 百度地图的显示和定位都可以实现,但是由于电脑安卓虚拟机无法连接网路,所以无法在安卓虚拟机中实现,但是我们在真机上验证,可以实现(需要连接网路). 地图API的应用查看(车联网开发日记2) android服务端的搭建主要有两种方法:xml格式的webservice,json格式的webservic

【Android开发日记】妙用 RelativeLayout 实现3 段布局

在设计过程中,我们经常会遇到这样的需求: 把一条线3控制,左对齐左控制,右侧控制右对齐,中间控制,以填补剩余空间. 或者一列内放3个控件,上面的与顶部对齐,以下的沉在最底部,中间控件是弹性的.充满剩余空间. 情况一:水平布局 图示: 这是第一种情形.因为涉及到ImageView.想保持图片原比例不便使用LinearLayout的weight属性. 解决的方法: 1.外层套一个RelativeLayout 2.三个控件分别装进3个LinearLayout中.假如id分别为leftlayout,mi

【Android的从零单排开发日记】之入门篇(四)——Android四大组件之Activity

在Android中,无论是开发者还是用户,接触最多的就算是Activity.它是Android中最复杂.最核心的组件.Activity组件是负责与用户进行交互的组件,它的设计理念在很多方面都和Web页面类似.当然,这种相似性主要体现在设计思想上.在具体实现方面,Android的Activity组件有自己的设计规范,同时,它能够更简便地使用线程.文件数据等本地资源. 一.Activity 的生命周期 Activity 的生命周期是被以下的函数控制的. 1 public class Activity

拥抱新的.Net开发框架,WPF开发人员怎样向.Net迁移

ArcGIS Runtime 10.2版本号中.将WindowsPhone .WindowsStore以及WPF三大SDK整合成了一个全新的SDK--ArcGISRuntime SDK for Microsoft .Net Framework,简称.Net SDK.同一时候现有的WPF SDK能够继续使用.但兴许会停止更新.因此,Esri建议WPF开发人员们向.Net阵营迁移. 顾名思义,新的.NetSDK面向微软的.Net框架,曾经的WPF.Windows Phone以及Windows Sto

【Android的从零单排开发日记】之入门篇(六)——Android四大组件之Broadcast Receiver

广播接受者是作为系统的监听者存在着的,它可以监听系统或系统中其他应用发生的事件来做出响应.如设备开机时,应用要检查数据的变化状况,此时就可以通过广播来把消息通知给用户.又如网络状态改变时,电量变化时都可以通过广播来通知用户.要做比喻的话,广播就像是我们的感官,能够有效且快速的从外界获取信息来反馈给自身. 一.广播的功能和特征 广播的生命周期很短,经过 调用对象—实现onReceive—结束 整个过程就结束了.从实现的复杂度和代码量来看,广播无疑是最迷你的Android 组件,实现往往只需几行代码

【Android的从零单排开发日记】之入门篇(三)——Android目录结构

本来的话,这一章想要介绍的是Android的系统架构,毕竟有了这些知识的储备,再去看实际的项目时才会更清楚地理解为什么要这样设计,同时在开发中遇到难题,也可以凭借着对Android的了解,尽快找出哪些模块和设计能够帮助解决该问题.但想了一下,这毕竟是入门篇,若没有实际项目开发经验的人看了之后肯定是一头雾水,所以就决定将其搁浅到大家熟悉Android之后再为大家介绍. 那么今天的主题是Android的目录结构,将系统架构比作人的骨骼架构的话,目录结构就像是人的各个器官,彼此功能各不相同,却能有序地

xxx学院后勤综合管理系统开发日记1

项目开发原因:为了配合部门两个从其他部门过来的维修科故障工作展开,现把一个网络故障保障和后勤维修的系统合拼成为一个,就成了现在的xxx学院后勤综合管理系统. 开发功能扩展:除了保持原有的保障表单.分角色查看故障还有统计功能之外,还加一个权限管理,物资管理和车辆管理还有一个办公oa额外附加的拼车功能.此外还有附加系统监控和控制开关.数据字典等.不知道还有没有其他需求,有的话就类似增加了. 开发工具:asp.net 三层+前端居于lhgcore框架+ms sql2005(使用存储过程). 其他要求:

工欲善其事,必先利其器 之 WPF篇: 随着开发轨迹来看高效WPF开发的工具和技巧

原文:工欲善其事,必先利其器 之 WPF篇: 随着开发轨迹来看高效WPF开发的工具和技巧 之前一篇<工欲善其事,必先利其器.VS2013全攻略(安装,技巧,快捷键,插件)!> 看到很多朋友回复和支持,非常感谢,尤其是一些拍砖的喷油,感谢你们的批评,受益良多. 我第一份工作便是WPF的开发,一直到现在都非常喜欢这门技术,从懵懵懂懂到现在有一些WPF开发资历,也算是经历了一段坎坷的过程.我的朋友看到我写了VS2013的全攻略,他就推荐我写一个WPF篇,我想了下,的确很多朋友初接触WPF的时候难免会