



在开发 NativeFlexUI 的核心组件 TreeViewVirtual 时,我遇到了一个让人头皮发麻的 Bug。
明明之前第一次写 Tab 标签页的时候测试过,显示一切正常(上一篇文章里还有截图为证!)。结果今天写 TreeViewVirtual Demo 的时候,却撞上了“灵异事件”。
这种感觉似曾相识,瞬间让我想起了当时被 duilib 支配的恐惧——就是那种“下断点就过,不下断点就不过”的脏东西,俗称“海森堡 Bug”。当时我对 duilib 了解不深,只觉得它不符合现代气质,也不想深究源码。但现在,轮到我自己写的 NativeFlexUI 出现了这种诡异现象,这就不能忍了。
Windows 开发的“敬畏之心”
有了当时处理 duilib 的经验,我的第一反应是:窗口消息没处理好。duilib那个拖动就是没处理好的造成的。
在写底层窗口封装时,我已经非常谨慎了。但我发现,在 Windows 平台上开发,特别是涉及到底层窗口消息机制时,你真的需要保持一种极度的敬畏之心。
那个从 90 年代一直堆屎山堆到 2026 年的 Windows API,真的不是开玩笑的。虽然现在 AI 知识库很全,但在 Windows 那个古老的机制面前,如果你少了一点敬畏,哪怕你把 NFXTreeViewVirtual 的 C++ 代码写出花来都没用——该不能点,就是不能点。 AI只管逻辑通不通,
案发现场
场景其实非常简单,一个 NFXTabView 容器包含两个标签页:
- Tab 1:资源管理器(当前显示)。
- Tab 2:网络邻居(当前隐藏,属性设为
Display: None)。
诡异的症状
代码跑起来后,出现了让我百思不得其解的现象,简直就是 GUI 开发界的“鬼压床”——你看得见它,你要说动不了,鼠标滚轮动的时候,消息又在动:
- 点击失效 :我在 Tab 1 上疯狂点击文件列表,界面毫无反应,仿佛屏幕前挡了一层看不见的玻璃。点击事件完全失效。
- 隔壁老王 :Tab 2 消息又完全正常,我往死了怼NFXTabView 隐藏状态(Display: None) 怼到旁边还出现了个 5 像素宽的滚动条
- 那个其实有点反应过来问题在哪儿了,真的要笑死人。
逻辑通了,Windows 不答应
当时我真的差点气笑。反应过来后发现,这其实是一个极其低级但又极具代表性的坑。
虽然我在布局层面把 Tab 2 设为了 Display: None,布局引擎(Yoga)也确实把它的宽高算成了 0。但是 HitTest 和 Event Routing 逻辑没对这个“隐藏状态”做隔离,导致那个“隐身”的 Tab 2 虽然None,却依然在拿所有消息。
总结: 在 Windows 开发面前,任何“我以为逻辑通了”都是错觉。你要是不对这些从 90 年代传下来的机制保持敬畏,它们随时会化身“隔壁老王”,在后台偷偷处理掉你原本发给前台的消息。
老老实实去给每一个重写了 Render 和 OnEvent 的子类,加上那道防线了:if (IsHidden()) return; —— 简单,但真的保头发。
说实话,昨晚刚跟朋友喝了酒,盯着屏幕的诡异,我一度怀疑是酒精让我产生了“多线程冲突”。
直到我想起酒桌上的一段对话,才发现那简直就是这个 Bug 的灵魂写照: 我问哥们儿:“你哪儿认识妹子,态度这么端正。” 朋友当场把我怼死,你觉得像我这样的人,除了商K和夜场,还能从哪儿认识小妹妹?
那一刻,我悟了。哈哈哈哈哈!这就是“回归本源”啊!他的消息渠道,永远锁定在那地方。
这和我的bug有啥区别, Windows 核心的消息句柄早就被后台那个“夜场”(隐藏的 Tab 2)给劫持了。你以为来的的是清纯学妹,实际上后台响应的全是商K妈咪!
狂妄是TMD有代价的
- 当时我那么气愤的喷duilib,还是我太狂了。在 Windows 这座从 90 年代堆到 2026 年、深不可测的“屎山”面前。 真的duilib 算是很用心很用心了。
- 回头看,duilib算对得起喜欢它的人了。但是该喷我还是要喷,还兼顾个锤子,老系统,那些在系统上跑的都是MFC ,stb_image.h 这个库在windows有意义吗?
- Windows WIC 你要的所有它都支持,还不占用体积,能调用系统的坚决不外挂,本身就是在windows开发界面库,用Direct2D +WIC就很完美。
- 虚拟化渲染 + 显存资源复用,我的TreeViewVirtual肯定不会像duilib那样加载动图,滚动条肯定不会像大爷一样一卡一卡的! 德芙一样的丝滑!
- 采用Direct2D唯一的坏处就是要先给内存交10多M的税。对于现在的机器来说,这个相当划算了!