经典的TreeView控件允许按名称或lParam排序,但为何不能两者兼得?

The Old New Thing (Raymond Chen) 工具

摘要

解释为什么经典的Win32 TreeView控件提供按名称和lParam的单独排序方法,并建议在需要两者排序时切换到数据模型模式。

<p>公共控件库中的Win32 TreeView控件提供了两种排序元素的方式。</p> <ul> <li><code>TVM_­SORT­CHILDREN</code>:按名称字母顺序对子项排序。</li> <li><code>TVM_­SORT­CHILDREN­CB</code>:通过自定义回调对子项排序。</li> </ul> <p>自定义回调提供被比较的两个树项的<code>lParam</code>。但如果你想要同时根据文本和<code>lParam</code>进行排序呢?如何获取两者?</p> <p>表示集合的UI控件有两种通用设计模式。</p> <p>一种模式是将UI控件作为数据仓库。关于项的所有信息都存在于UI控件中,体现在其名称、勾选状态、选择状态等。如果你需要了解某个项的信息,就向UI控件询问。</p> <p>第二种模式是数据仓库本身没有UI的某种对象(业内称为“数据模型”)。然后你构建UI元素来表示这些对象。</p> <p>Windows控件通常倾向于数据模型方法,因为关于项的大量信息通常并不体现在其UI表示中。数据模型方法还允许优化:非常大的项集合只为屏幕上可见的项创建UI元素。这在XAML ListView控件以及经典Win32 ListView控件处于所有者数据模式时均可体现。</p> <p>对于公共控件库中的控件,一般模式是提供一个位置来存储不显示在UI中的指针大小值,通常称为“项数据”或简称为<code>lParam</code>。这里存储指向UI对象所表示的数据模型对象的指针。</p> <p>好了,让我们再来看一下TreeView的排序方法。</p> <p><code>TVM_­SORT­CHILDREN­CB</code>消息使用一个回调,该回调传入两个项的<code>lParam</code>进行比较。其原理是这些<code>lParam</code>是指向描述项的大型数据结构的指针,你利用这些大型数据结构来决定这两个项的排序。</p> <p><code>TVM_­SORT­CHILDREN</code>消息不使用回调。它只是一个便捷方法,用于仅按名称排序的情况,因此它直接使用已分配给项的名称。</p> <p>需要两者的情况是:<code>lParam</code>不足以恢复名称,可能是因为它指向的结构不包含名称,或者它根本不是指针。</p> <p>可以想象,如果你只需为每个TreeView项跟踪其名称和指针大小的数据,就会遇到这种情况。你将名称放在TreeView项文本中,其他数据放在<code>lParam</code>中。这个方案在需要排序之前都很好,但你的排序比较函数需要访问这两部分数据。</p> <p>解决办法是切换到数据模型模式。为每个TreeView项分配一个结构体,并将字符串和附加数据放入该结构体中。(或者,你可以巧妙地将结构体设置为<code>HTREEITEM</code>和附加数据。然后通过<code>TVM_<wbr />GET­ITEM</code>消息恢复字符串。)</p> <p><b>额外补充</b>:理论上,<code>TVM_­SORT­CHILDREN­CB</code>可以将<code>HTREEITEM</code>传递给回调。然后回调可以利用<code>HTREEITEM</code>获取字符串和<code>lParam</code>。我认为没有这样做是因为大多数回调函数只需从<code>HTREEITEM</code>获取<code>lParam</code>,而<code>TVM_­SORT­CHILDREN­CB</code>通过直接提供你最初可能想要的内容,帮你省了很多事。</p> <p>本文<a href="https://devblogs.microsoft.com/oldnewthing/20260520-00/?p=112343">经典的TreeView控件允许按名称或lParam排序,但为何不能两者兼得?</a>最先出现在<a href="https://devblogs.microsoft.com/oldnewthing">The Old New Thing</a>。</p>
查看原文
查看缓存全文

缓存时间: 2026/05/21 15:50

# 经典 TreeView 控件允许按名称或 lParam 排序,但为何不能两者兼顾? - 旧事新说 来源:https://devblogs.microsoft.com/oldnewthing/20260520-00?p=112343 公共控件库中的 Win32 TreeView 控件提供了两种元素排序方式: - `TVM_­SORT­CHILDREN`:按名称字母顺序对子项进行排序。 - `TVM_­SORT­CHILDREN­CB`:通过自定义回调函数对子项进行排序。 自定义回调函数会获得两个待比较树项各自的 `lParam`。但如果你想要同时根据文本和 `lParam` 的某种组合来排序,该如何同时获取这两者呢? 使用代表集合的 UI 控件通常有两种设计模式。 一种模式是 UI 控件自身作为数据存储库。关于项目所需的一切信息都存在于 UI 控件中,可能体现在其名称、勾选状态、选中状态等属性中。如果需要了解某个项目的相关信息,只需向 UI 控件索取即可。 第二种模式是数据存储库是一个本身不含任何 UI 的某种对象(业界称之为“数据模型”)。然后你构建 UI 元素作为这些对象的呈现形式。 Windows 控件通常倾向于采用数据模型方法,因为通常有关项目的大量信息并不在其 UI 表示中体现。数据模型方法还允许进行优化:当集合项目非常庞大时,仅创建屏幕上可见项目的 UI 元素。在 XAML ListView 控件以及处于所有者数据模式下的经典 Win32 ListView 控件中都可以看到这种模式。 对于公共控件库中的控件,通用模式是提供一个存储指针大小值的位置,该值不在 UI 中显示,通常称为“项数据”或简称为 `lParam`。你可以在这里存储指向 UI 对象所代表的数据模型对象的指针。 好了,现在让我们再次审视 TreeView 的排序方法。 `TVM_­SORT­CHILDREN­CB` 消息接受一个回调函数,该函数被传入两个待比较项的 `lParam`。其理论是这些 `lParam` 是指向描述项目的更大数据结构体的指针,你可以利用这些更大的数据结构体来决定两个项目的排列顺序。 `TVM_­SORT­CHILDREN` 消息不接受回调函数。它是一种便利方法,适用于仅按名称排序的情况,因此它会直接使用已分配给项目的名称。 需要两者兼顾的情况发生在:`lParam` 不足以恢复名称时,原因可能是它指向的结构体不包含名称,或者它根本就不是一个指针。 我可以想象遇到这种情况:对于每个 TreeView 项目,你唯一需要记录的信息就是它的名称和一个指针大小的数据片段。你将名称放在 TreeView 项目文本中,将其他数据放在 `lParam` 中。这个方案在需要排序之前一直运行良好,但你的排序比较函数需要同时访问这两部分数据。 解决方案是切换到数据模型模式。为每个 TreeView 项目分配一个结构体,将字符串和附加数据都放入该结构体中。(或者,你也可以取巧,让结构体包含 `HTREEITEM` 和附加数据,然后通过 `TVM_GET­ITEM` 消息来恢复字符串。) **额外补充**:从理论上讲,`TVM_­SORT­CHILDREN­CB` 本来可以将 `HTREEITEM` 传递给回调函数。回调函数随后可以使用 `HTREEITEM` 来获取字符串和 `lParam`。我猜测之所以没有这样做,是因为大多数回调函数只需要从 `HTREEITEM` 获取 `lParam`,而 `TVM_­SORT­CHILDREN­CB` 则是帮了你一个忙,直接给你你最可能想要的东西,省了你不少功夫。 ### 分类 ### 主题 ## 作者 Raymond Chen Raymond 参与 Windows 的发展已有三十余年。2003 年,他创办了一个名为“旧事新说”的网站,其受欢迎程度远远超出了他最疯狂的想象,这个发展至今仍让他感到些许不安。该网站催生了一本书,巧合的是书名也叫《旧事新说》(Addison Wesley 出版社,2007 年)。他偶尔会出现在 Windows 开发文档的 Twitter 账号上,讲述一些毫无实用信息的故事。

相似文章

SQLite 通过预排序提升性能

Hacker News Top

本文展示了在将随机数据插入 SQLite 之前进行预排序,可以利用 B+ 树的顺序特性并减少页分裂,从而将插入性能提升 2-3 倍。

无分支快速排序:性能超越 std::sort 和 pdqsort,提供 C 和 C++ API

Hacker News Top

一种新的无分支快速排序实现(blqsort)借助排序网络技术,在 Apple M1 和 AMD Ryzen 系统上的性能超越了 std::sort 和 pdqsort,以单头文件形式提供 C 和 C++ 库。其性能提升得益于无分支分区、中位数之中位数枢轴选择以及针对小数组的自定义排序网络。

并行 O(√n) 开销 LSD 基数排序

Lobsters Hottest

本文介绍 Radsort,一种具有 O(√n) 开销的并行 LSD 基数排序算法,该算法稳定、易于实现,并且对于大数组优于传统方法。