简介:面向 Delphi 与 C++ Builder 开发者的 DevExpress ExpressBars Suite v6.37 控件套件,专注解决工具栏、菜单、状态栏与导航栏等界面组件的快速构建与深度定制问题,适合需要在 Windows 桌面应用中实现专业 UI 的中高级开发者。压缩包共 912 个文件,约 9.17MB,主要包含 pas/cpp 源代码、dpk/bpk 工程包、dfm 窗体定义以及 res 资源文件等,便于阅读控件实现并直接集成到开发环境。该版本内含完整源码,可通过源码深入理解 XP 主题管理、GDI+ 绘图、国际化支持等底层机制。资源还附带 BarsDemo、RibbonNotepadDemo 等多个示例工程,覆盖常用场景,方便对照学习。目前已有 220 人学习,对想要掌控 DevExpress 界面组件工作原理的开发者来说,是难得的可读源码的完整资源。 DevExpress ExpressBars Suite v6.37 for Delphi/BCB 这套含完整源代码的组件包,是我近几年维护老项目时翻得最多的东西。不少同行觉得DevExpress是给新Delphi用的,其实在Delphi 7和C++Builder 6那个年代,ExpressBars几乎已经是工具栏、菜单栏、停靠面板的默认选择。它解决的也不是“放一个按钮”这种小事,而是把菜单、工具栏、右键菜单、快捷键、图标、皮肤全部统一到一套体系里,这在老项目里能省下大量代码。含完整源代码这一点尤其重要:组件在今天的操作系统上出问题时,你可以直接打开源码定位,而不是对着闭包组件干瞪眼。
如果你手里也有一份ExpressBars v6.37的安装包,或者正在维护一个用Delphi 7/BCB 6写的旧系统,这篇东西应该能帮你把它的价值完整榨出来。我会从安装时的坑、源码调试体验、实战编排、以及和新版组件混用这四条线展开,每个问题都是我在实际项目里真金白银趟过的。
1. 这套老组件如今仍值得翻出来的三个理由
1.1 它解决的从来不是“放一个按钮”的问题
Delphi 7自带的TMainMenu和TToolBar,在简单界面上完全够用。可一旦业务命令数量超过几十个,你会发现同一批操作要在菜单、工具栏、右键菜单里反复出现,每个地方都要单独维护一份OnClick,启用禁用状态还得手动同步。ExpressBars的思路是先用TdxBarManager统一建立命令池,每个命令对应一个TdxBarItem对象,然后菜单、工具栏、右键菜单只是把同一个Item挂载到不同容器里。好处很明显:命令的业务逻辑只写一次,状态控制也只做一处,展示层随便换。
v6.37这一代的TdxBarManager,稳定性和资源占用控制得都不错。我在老项目里见过连续运行几个月的POS终端,工具栏菜单照常工作,没有出现句柄泄漏或GDI对象暴涨的问题。当然它也有老组件常见的毛病:在高DPI屏幕上显示会发虚,部分控件对Unicode的支持不够彻底。但瑕不掩瑜,对维护期系统来说,功能稳定比花哨重要,这也是很多老Delphi项目迟迟不愿意动这块的原因。
1.2 有源码和没源码,维护成本完全是两个量级
闭包组件是个黑盒,出问题时只能靠猜。v6.37含完整源代码,意味着我可以把断点直接打进dxBar.pas里,看工具栏按钮在点击时到底走了哪条消息链路。比如有一次项目里工具栏按钮的Hint显示乱码,我顺着源码发现是内部处理WM_Notify时对宽字符转换的兼容分支有问题,直接在自己的副本里改了处理逻辑,问题当场解决,前后不到半小时。
另一个实际价值是裁剪。老安装包里有些单元你用不到,比如dxBarExtDB、dxBarExtCtrls这类扩展包,在源码级别上可以按需排除,有效缩小DCU体积,也避免引入不必要的依赖。这在新版组件里反而不容易做到,因为新版包和包之间互相依赖严重,删掉一个文件可能拖垮整个单元引用链。有源码的老版本,反而成了最能“做减法”的选择。
1.3 老版本本身也是理解DevExpress组件架构的教科书
如果你用过新版DevExpress VCL,会发现很多命名和设计思路都能在v6.37里找到源头。比如TdxBarItem和TdxBarButton这对类关系,新版的TdxBarButton依然存在,只是加了更多属性和事件。读一遍老源码,等于掌握了DevExpress VCL控件体系的一条主线。很多技术交流里聊到“DevExpress控件怎么扩展”,底层逻辑其实都在这一套源码里。
2. 安装到Delphi 7/BCB 6:三个让新手卡壳的关口
2.1 安装顺序不对,后面全是“File not found”
ExpressBars v6.37依赖底层的ExpressCore库。解压之后你会看到源码目录里不只有dxBar.dpk,还有dxCore.dpk以及其他一些基础包。正确顺序一定是先编译dxCore,再编译dxBar,最后编译设计期包。如果跳过dxCore直接打开dxBar.dpk,Delphi会立刻报File not found: dxCore.dcu。这个报错很多新手遇到过,根源不在文件缺失,而在前置包没有编译。
具体操作大致是这样:在Delphi 7里打开dxCore.dpk,点Compile,然后点Install;再打开dxBar.dpk,同样先Compile再Install;最后处理带Designtime或RunTime标识的扩展包。BCB 6的流程类似,但C++Builder的包管理器会把编译产物放到Lib目录下,如果你之前手动改过Lib路径,需要确认dxCore.lib在搜索范围内。顺序对了,后面就顺了。
2.2 从报错到定位的完整排查链路
假设你现在编译dxBar.dpk就报错,先别急着把整个目录重新拷一遍。按下面这条链路走一遍,基本都能定位。这套排查顺序我在不同机器上验证过多次,顺序不能乱,因为前置错误往往会掩盖真实原因:
- 先看报错信息里缺失的是DCU还是LIB文件。如果是DCU,大概率是前置包没有编译,或者搜索路径没包含源码目录。
- 打开Project > Options > Directories/Conditionals,在Search path里把ExpressBars源码根目录和ExpressCore源码目录都加进去。这一步能解决一半问题。
- 确认你用的是不是同一套Delphi版本生成的DCU。v6.37年代没有统一DCU格式,Delphi 7的DCU不能给BCB 6用,反过来也不行。如果你机器上装了多个Delphi版本,最容易发生这种串用。
- 如果还报错,打开Packages窗口,看dxCore运行时包是否真的处于Installed状态。有时候Compile成功但Install失败,后续包一样找不到类型库。
2.3 路径里别带空格和中文,这不是玄学
老版本组件对路径的容忍度很低。ExpressBars v6.37的源码包如果解压到带空格的目录,比如C:\Program Files\DevExpress\ExpressBars,编译时会出现Unit not found或者Cannot open file这种随机问题。原因是老的Borland编译器在处理搜索路径时,对空格的分割策略并不完善。建议直接放在C:\DX\ExpressBars\这样的纯英文短路径下,能避掉大量莫名其妙的问题。中文路径就更别试了,那个年代的IDE对编码的处理还没到今天这么成熟的水平。
3. 有源码之后调试与定制,才是这份包最大的价值
3.1 把断点打进去,组件内部逻辑一览无余
闭包组件遇到疑难杂症只能开Ticket等回复,含源码的组件则可以直接在本地加断点观察。我印象最深的一次是排查工具栏按钮的OnClick没有触发。表面上看是事件没挂上,跟踪源码后发现TdxBarButton在Down状态下点击会走另一个消息分支,事件分发的位置和普通按钮完全不一样。如果没源码,这一步至少得猜一两天。
类似的还有按钮图标不刷新、下拉菜单宽度计算错误、快捷键注册冲突等问题,这些问题在源码里都有清晰的线索。源码调试的另一个好处是能看到DevExpress内部的防御性写法,比如对nil的判断、对旧Delphi版本的兼容分支。这些经验写进自己的代码里也很有用,算是一份很高质量的参考资料。
3.2 一个真实魔改案例:给所有工具栏按钮统一加提示前缀
有一回我接手的项目,界面要求所有按钮Hint统一显示成“功能-说明”的格式。如果逐条改设置,几十个按钮要改半天。有了源码,我直接在自己维护的dxBar.pas副本里,找到Hint属性的读取方法,在返回前做了前缀拼接。这样全部按钮自动生效,改动面最小。
当时改的位置大致在TdxBarItem.GetHint或者DoGetHint相关方法里。这里要提醒一句:改源码要有版本管理意识。我习惯把改过的单元文件名保留原名,但在工程里单独建一个Patches目录,并把修改点用注释标记好。这样升级包版本或者对比原始代码时,能快速知道自己动了什么,不会在半年后看着一坨改动线索发懵。
3.3 源码里被忽略的三个扩展点
第一,TdxBarManager的OnClick事件。它不只是转发Click,还会把Item的Tag、Name、Category传出来,适合做统一命令分发。第二,TdxBarButton的重绘相关虚方法,比如DoPaint,可以在这里给按钮增加自己的视觉元素。第三,TdxBarEditorCloseUp等与下拉交互相关的方法,适合定制下拉面板行为。这三个扩展点是老项目里最常用的“半官方”魔改位置,比在线程里发消息控制界面要稳妥得多。
4. 从空窗体到完整布局:ExpressBars实战编排的几个套路
4.1 先理清Manager、Bar、Item三者关系
刚接触ExpressBars的人,容易被TdxBarManager、TdxBar、TdxBarItem这几个类绕晕。简单记法:TdxBarManager是整个体系的根,它持有两个集合——Bar集合和Item集合;Item是“命令”,Bar是“容器”。菜单栏、工具栏、右键菜单都是Bar,只是配置属性不同。你往一个Bar里塞几个Item,再往另一个Bar里塞同样的Item,两个地方的操作状态自动同步,因为底层引用的是同一个Item对象。
这个设计与Windows的Command-based UI思想一脉相承。老项目里几十个菜单项和工具栏按钮不乱,就是因为所有按钮共享同一个命令池,状态管理只发生在一处。理解这一点后,你会明白为什么ExpressBars的老用户都不太愿意退回原生控件的写法。
4.2 动态生成菜单和工具栏的代码骨架
在没有窗体设计器的环境下,完全用代码创建一组工具栏也很快。核心流程是:创建TdxBarManager,创建Bar,往Bar里添加Item,给Item绑定事件。下面这段是我在维护项目里反复用到的简化版:
procedure TMainForm.SetupToolbar; var AMgr: TdxBarManager; ABar: TdxBar; AItem: TdxBarButton; begin AMgr := TdxBarManager.Create(Self); AMgr.Style := bmsUseLookAndFeel; ABar := AMgr.Bars.Add; ABar.Caption := 'Main'; ABar.DockPos := 0; AItem := TdxBarButton.Create(AMgr); AItem.Caption := '&Open'; AItem.Hint := 'Open file'; AItem.Glyph.LoadFromFile('open.bmp'); AItem.OnClick := DoOpenFile; ABar.ItemLinks.Add(AItem.Items[0]); end;这里每个类在源码里都是公开的,不必非在IDE里拖控件。要注意的是,Item的创建者传入AMgr后,Item会归AMgr管理,不能随便Free;需要统一释放时用AMgr.Free即可。v6.37源码里这套生命周期管理写得很清楚。另外,如果用的是BCB 6,创建Item时还要注意组件归属选项在不同版本里有细微差别,以你手头源码为准。动态代码最忌讳自己接管组件生命周期后又在别处释放,一段代码里出现两次Free,运行时随机崩溃很难查。
4.3 老项目里最常见的状态同步技巧
菜单项和工具栏按钮经常要随着数据状态一起变化,比如没有打开文件时,“保存”按钮应该是灰的。最简单的做法是在TdxBarManager.OnUpdate事件里统一刷新。在这个事件里遍历ItemLinks,按业务状态修改每个Item的Enabled。因为有命令池机制,同一Item在菜单和工具栏里会出现两次,但Enabled只改一次,所有展示同步生效。
另一个常见需求是快捷键。ExpressBars里快捷键并不是挂在Form的KeyPreview上,而是通过Item的ShortCut属性注册到内部热键表。源码里能看到它注册的是全局快捷键还是窗体级快捷键,这在多窗体项目里关联到焦点问题。处理方式一般是把快捷键统一放在主窗体的BarManager里,避免重复注册。很多老项目里还同时用着TClientDataSet的CloneCursor做数据集拷贝,用SendMessage在进程间传消息,这些老部件和ExpressBars搭配多年,早已磨合稳定,单独拆出来换新反而不划算。
5. 老项目迁移与新版混用时的兼容性避坑
5.1 v6.37和现代DevExpress VCL的关键差异
| 维度 | ExpressBars v6.37 | 新版DevExpress VCL |
|---|---|---|
| 安装包结构 | 独立套件,依赖ExpressCore | 全家桶,模块化但依赖复杂 |
| 单元命名 | dxBar.pas等短名称 | 多数保留但新增大量单元 |
| DPI支持 | 基本不支持,字体发虚 | 原生支持High DPI |
| 许可证 | 老许可证仍有效但无新授权 | 新订阅制,需持续购买 |
| 包名 | dxBar_run.dpk等 | 版本化包名带年份后缀 |
如果你只是维护老系统,继续用v6.37完全没问题。但如果要开发新项目,我不建议再用这个版本。原因不只是功能缺失,更在于新操作系统和旧组件之间的兼容风险会随时间不断增加。新版Delphi里老包格式经常需要手动转换才能安装,像Delphi 13这类更晚的版本,对老组件的兼容支持已经明显收窄。
5.2 混用时的DCU冲突与包依赖问题
很多项目会同时装新版DevExpress VCL和老的ExpressBars v6.37。这里面的坑在于:新版VCL同样包含TdxBarButton、TdxBarManager等类,单元名和旧版一样,运行时包会按搜索路径顺序加载。如果你把v6.37的源码路径排在系统搜索路径前面,新版组件会莫名引用到旧DCU,然后报Ambiguous overloaded这类奇怪错误。
解决办法是把两套组件彻底隔离:老项目单独用一个虚拟机或者单独的用户环境路径,新版组件放到另一个目录,不要在同一个IDE里同时加载两套新旧版本包。如果实在要共存,务必在工程选项里显式指定Unit别名,而不是依赖系统搜索顺序。这个教训来自我一次被折腾一下午的排错经历,说出来都是泪。
5.3 迁移前先做好这三件事,再去动代码
第一,备份。不只是源码备份,还要把整个Delphi环境目录、注册表里的包信息、现有DCU产物全部留一份。第二,梳理自定义改动。如果你像我在3.2节那样改过源码,要把补丁整理成独立文件,不要散落在源码包各个角落。第三,准备回归清单。ExpressBars涉及面广,菜单、工具栏、右键菜单、快捷键、停靠窗口,每一块都要有对应的验证用例。做好这三件事,迁移失败也能随时回滚,心理压力会小很多。
最后分享一个我的习惯:老项目交接时,除了源码和数据库脚本,我一定会把对应版本的ExpressBars完整安装包连同所有补丁副本放进项目的ThirdParty目录。有人觉得多余,但我在多个项目里用它救过急,比如新同事电脑上装不上老IDE时,直接从这个目录恢复环境,省去满网络找安装包的狼狈。这个习惯我保留到现在,也是为什么我对v6.37这套老组件感情这么深的原因。
本文还有配套的精品资源,点击获取