当前位置: 首页 > news >正文

开发集合控件的拖拽流程优化——以TreeView为例

简介

文章不会介绍简单的拖拽开发流程,而是记录如何在已有拖拽控件上进一步优化,提高控件的性能和使用体验。具体的优化内容主要涉及到一下几个方面。

  1. 虚拟化提高控件加载性能。
  2. 拖拽操作的防误触。
  3. 在拖拽时的高光显示以及靠近上端或低端时滚动条自动滚动。
  4. 拖拽结点的位置移动。

虚拟化

很多时候,当我们一次在集合控件中添加大量元素后,会导致程序卡顿甚至停止响应,并且UI的渲染速度也十分缓慢。这时候就需要用到虚拟化技术。

VirtualizingPanel

VirtualizingPanel是WPF中一个特殊的面板抽象基类,它的核心功能是仅渲染当前可见区域内的元素,而非所有数据项。该类中提供了与虚拟化相关的参数设置。

参数 描述
VirtualizingPanel.CacheLength 控件需要缓存的项目数。这意味着在视口之外的区域中,面板会保留一定数量的项目以提高滚动平滑度。
VirtualizingPanel.CacheLengthUnit CacheLengthUnit 属性定义 CacheLength 的单位。其中 Item 表示缓存的长度以项目的数量为单位,Pixel 表示缓存的长度以像素为单位。
VirtualizingPanel.IsContainerVirtualizable 当元素滚动出可见区域时,面板会尝试回收这些元素的容器(如 ListBoxItem、TreeViewItem)以节省资源。IsContainerVirtualizable 属性用于指定:某个具体的容器元素是否允许被虚拟化面板回收。
VirtualizingPanel.IsVirtualizing 面板是否启用虚拟化。这是虚拟化的核心设置,设置为 True 表示面板会仅对视口内的项目进行渲染和处理,而不是一次性加载所有项目。
VirtualizingPanel.IsVirtualizingWhenGrouping 面板在分组时是否继续进行虚拟化。当设置为 True 时,面板在分组数据时仍然会应用虚拟化策略,以保持性能优化。
VirtualizingPanel.ScrollUnit 定义滚动的单位。可以选择 Item 或 Pixel,其中 Item 表示每次滚动一个项目,Pixel 表示每次滚动一定像素。值:Item 表示每次滚动一个项目的单位,而不是固定像素数,这对于项目高度一致的情况尤其有效。
VirtualizingPanel.VirtualizationMode 指定虚拟化模式。Recycling 模式表示控件会重用已经不再可见的项目的容器,而不是销毁它们。

VirtualizingStackPanel

VirtualizingStackPanel 是 WPF 中最常用的虚拟化面板控件,专为高效处理大量数据项的列表场景 ** 设计,通过 “仅渲染可见区域元素” 的机制显著提升性能。它是 VirtualizingPanel 的子类。WPF中的部分集合控件(ListBox、ListView、DataGrid、TreeView都已经默认使用该控件)。 但对于,TreeView仅针对展开的节点层级生效。 未展开的的一级结点则不会采用虚拟化。

如何使用虚拟化

需要明确一点,如果在控件中没有使用继承VirtualizingPanel的虚拟化控件(如VirtualizingStackPanel),而是仅仅通过VirtualizingPanel设置了相关的虚拟化属性,那么控件是不会具备虚拟化效果的。
通过上面的介绍可知,已经有部分控件完全实现了虚拟化的支持。但对于ItemsControl,由于其使用场景以及作为集合控件的基类,WPF中并未直接支持其虚拟化,需要手动设置其ItemsPanelTemplate,来实现虚拟化。

点击查看代码
 <ScrollViewer Height="300"> <!-- 提供滚动容器 --><ItemsControl ItemsSource="{Binding LargeDataSource}"><!-- 替换为虚拟化面板 --><ItemsControl.ItemsPanel><ItemsPanelTemplate><VirtualizingStackPanel IsVirtualizing="True"  <!-- 启用虚拟化(默认True) -->VirtualizationMode="Recycling"/> <!-- 优化容器复用 --></ItemsPanelTemplate></ItemsControl.ItemsPanel></ItemsControl>
</ScrollViewer>

最后,当我们想要实现一些自定义的集合控件效果,在重写控件的ItemsPanel的控件模板时,一定不要忘记把ItemsPanel的模板设置为VirtualizingStackPanel,否则,即便我们应用了虚拟化属性。但由于未使用虚拟化控件,也无法开启虚拟化导致重写后的控件模板性能大大降低影响使用。

点击查看代码
<Setter Property="ItemsPanel"><Setter.Value><ItemsPanelTemplate><VirtualizingStackPanelMargin="0"IsItemsHost="True"VirtualizingPanel.IsVirtualizing="True"VirtualizingPanel.VirtualizationMode="Recycling" /></ItemsPanelTemplate></Setter.Value>
</Setter>

防误触

这个非常好理解,当我们点击TreeViewItem时,可能我们不经意间,碰到鼠标左键一滑就直接开启拖拽。从而导致误操作。
优化方向从两方面考虑。

  1. 鼠标点击时距离TreeView边缘的距离。
  2. 当鼠标在按住状态下移动一定距离后,才开启拖拽功能。

思路非常好理解,下面就直接给出对应的代码实现。
在下面的方法中(对于为什么是隧道事件后面会讲),主要完成了两点操作。

  1. 获取选中项
  2. 判断点击位置,如果在合理范围就记录点击位置,然后将拖拽标志设置为True。
PreviewMouseLeftButtonDown
private void ModuleTree_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{if(ModuleTree.Items.Count==0){ModuleTree.Focus();return;}Point pt = e.GetPosition(ModuleTree);HitTestResult result= VisualTreeHelper.HitTest(ModuleTree, pt);if(result == null){return;}TreeViewItem selectedItem = ElementHelper.FindVisualParent<TreeViewItem>(result.VisualHit);if (selectedItem != null) {SelectedNode = selectedItem.DataContext as ModuleNode;selectedItem.IsSelected = true;}//靠近滚轮则不执行拖动if (ModuleTree.ActualWidth - pt.X > 80){if (SelectedNode != null && SelectedNode.IsCategory == false){m_MousePressY = pt.Y;m_MousePressX = pt.X;m_DragModuleName = SelectedNode.Name;m_DragMoveFlag = true;}}}
在下面的鼠标移动事件中,由于我们先前已经记录了点击的位置,如果移动的幅度超过限制,那我们就开启拖拽操作。
ModuleTree_MouseMove
  private void ModuleTree_MouseMove(object sender, MouseEventArgs e){if(m_DragMoveFlag == true){Point pt = e.GetPosition(ModuleTree);   // 与点击位置超过10个像素,表示能开始拖动if(Math.Abs(pt.Y-m_MousePressY)>10 || Math.Abs(pt.X - m_MousePressX) > 10){string showTxt = SelectedNode.Name;m_DragCursor = CursorHelper.CreateCursor(200,28,12,ImageHelper.ImageSourceToBitmap(SelectedNode.IconImage),26, showTxt);m_DragMoveFlag= false;// 启动拖拽DragDrop.DoDragDrop(ModuleTree,$"{m_DragModuleName}",DragDropEffects.Move);}}}

高光显示和自动滚动

补充

TreeViewItem的事件拦截

在开发TreeView和ListBox等相关功能时,往往需要在集合控件上通过点击事件(MouseLeftButtonDown事件),记录点击位置,获取选中项等操作。
当时,我在写代码时在TreeView使用的是MouseLeftButtonDown,这是一个冒泡事件。然后,我发现当我点击对应的TreeViewItem时,无法触发对应的事件,这个事件只在我点击TreeView的空白部分时才会触发。后来查资料发现,在集合控件中,其本身的Item就已经定义了MouseLeftButtonDown事件(用来修改选中项和变更选中状态等),然后Item会把这个事件截断,导致TreeView无法接收到。
总结一下,就是对于集合控件,想通过鼠标的Down开启拖拽事件,一定要去使用隧道事件PreviewMouseLeftButtonDown。这点很关键而且很细节。

http://www.vanclimg.com/news/1015.html

相关文章:

  • 第七天
  • 付老师名言
  • [羊城杯 2021]Baby_Forenisc-内存取证-Volatility 2工具下载使用- Volatility 2.6 的 Linux 免安装版(Standalone 版本)
  • 北大 2024 强基数学
  • 【ESP8266】Vscode + platformIo + Esp8266 新建工程 关键步骤
  • Revo Uninstaller Pro专业版领取:2025最佳Windows软件卸载工具
  • Datawhale AI夏令营 Dify入门 Task05 智能客服
  • PlantUML绘制时序图
  • helm环境快速部署实战
  • 用 Python 实现多干扰线图像验证码的识别系统
  • Python 实现多干扰线图像验证码识别
  • 学习链接
  • 03Gin中间件开发与鉴权实践
  • 入门
  • 浅析扫描线
  • CRUD
  • I2C
  • 小新Pad2022刷机记录
  • 最左前缀原则和覆盖索引相关问题
  • 【LeetCode 142】算法:环形链表 II
  • Gin框架介绍
  • 正则表达式中的元字符
  • sequence的启动
  • L. Dynamic Convex Hull 题解
  • 实时通信技术深度对比:WebSocket与SSE的最佳实践(1018)
  • 微服务架构的轻量级解决方案(6064)
  • WebSocket服务端的高效处理(1104)
  • 服务端推送技术的现代实现(6185)
  • 异步编程在Web开发中的应用(1191)
  • 从零开始构建高性能实时聊天系统:Hyperlane框架实战指南(3242)