ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

老PB项目界面升级实战:PowerUI 2.0换肤接入与踩坑全记录

老PB项目界面升级实战:PowerUI 2.0换肤接入与踩坑全记录 简介面向 PowerBuilder 9/10/11.5/12.5 开发者的 PowerUI 2.0是专为 PB 平台打造的界面增强工具库。它通过 PBNI 完成底层绘图控件交互则用 PowerScript 实现既保持 PB 原生开发习惯又兼容原有控件可有效改善传统应用界面单一、美化成本高的问题。技术源码按 BSD 协议开放便于学习二次开发。压缩包共 252 个文件、44.77MB涉及 png、bmp、gif 等外观主题pbl、pbd 等 PowerScript 库以及 dll 原生扩展与 pbw、pbt、pbr 工程文件方便对照样例快速接入。已有 344 人学习下载适合需要为 PB 应用定制现代界面的中高级开发者从中可掌握 PBNI 调用、控件封装和主题资源组织方式。 2015年春天我接到一个持续了大半年的维护项目一家制造企业的生产管理MIS系统PowerBuilder 9写的跑了快十年。功能层面客户没话讲但每次开月度例会屏幕上那种灰色按钮加平面数据窗口的界面总会被人拎出来说事。老板给的指示也直接——业务流程一朵云都不许动界面必须像个当代软件。这就是我开始研究并试用 PowerUI 2.0 for PB9/10/11.5/12.5 的起因。PowerUI 在 PowerBuilder 圈子里不算无名之辈它属于那种“需要的人到处找不需要的人根本不知道”的界面增强框架纯 PowerScript 实现不依赖注册一堆 ActiveX 皮肤控件通过接管窗口消息完成标题栏、按钮、Tab、DataWindow 的统一样式。下面整理的就是我实际接入这套框架的真实记录重点放在版本兼容、消息机制、DataWindow 冲突和部署现场这些文档里很少讲清楚的事。如果你也在维护 PB9 到 PB12.5 之间的老项目这篇应该能帮你少走不少弯路。1. 接手一个 PB9 老系统之后我决定给它换一层皮1.1 为什么界面美化对 PB 项目意味着“续命”老 PB 项目的典型状态圈内人都懂DataWindow 的报表能力至今都不落伍业务逻辑堆了十几年稳得跟老黄牛一样。但界面停在 Windows 2000 那套视觉语言里灰底、黑字、平面按钮、毫无圆角。这种系统在企业里多的是尤其制造、物流、医院、政务这些行业。客户不会因为界面旧就换系统可运营团队会因为这个迟迟不签下一年的合同——领导汇报时界面拿不出手这是很现实的问题。给这种系统做界面改造通常有三条路推翻重写、Web 化改造、界面层换肤。前两条听着光鲜周期以年计业务逻辑迁移过程中的风险能把维护团队拖垮。最稳妥的反而是第三条不动业务、不动数据库、不动 DataWindow 里几百条表达式只把“皮”换掉。PowerUI 这类框架存在的价值就是让 PB 存量系统以最低成本实现“看起来换了代”的效果说白了这就是给项目续命让老系统再多跑五六年。1.2 PowerUI 2.0 在众多方案里为什么值得选当时我对比过几类方案各自的情况大概是这样的方案优点缺点商业 ActiveX 皮肤控件SkinSoft、AppFace 等皮肤样式丰富要注册 OCX部署时容易在客户机器上报“控件未注册”PB9 这种老运行时不时出兼容问题自己写 External Functions 处理 WM_NCPAINT定制性强每个窗口都要写代码维护量巨大XP/Win7/Win10 表现不一致纯图片背景换肤实现简单窗口拉伸就露馅按钮状态、焦点态做不出来PowerUI 2.0纯 PBL 分发、支持多版本、局部启用知名度不高资料少需要自己试错我最后选 PowerUI三个原因。第一版本覆盖广能同时照顾 PB9 和 PB12.5 这两个差异巨大的时代一套方案通吃不用分项目维护两套皮肤逻辑。第二接入成本低核心类集中在几个 n_ 开头的 PBL 对象里不碰业务代码。第三也是最重要的它支持局部启用——不是所有窗口都必须换肤老系统里有些窗口塞满了自绘代码和第三方控件这些地方完全可以不挂皮肤避免冲突。2. 部署前先看这张版本兼容表别急着往工程里拖 PBL2.1 PB9/10 与 11.5/12.5 的差异会直接影响加载方式PB9/10 是 ANSI 时代的产物PB11 开始 IDE 迁到 .NET FrameworkPB11.5 和 PB12 对 Unicode 的支持已经完整。这个底层差异不是小事同样一个外部函数如果用到了宽字符参数在 PB9 里根本没有对应类型反过来在 PB12.5 里用 ANSI 字符串去处理中文路径乱码就会一个接一个。PowerUI 2.0 针对这两代差异分别做了不同构建的 PBL加载时选错库报错往往不是报在你写的代码里而是报在对象继承关系上排查起来很绕。我整理了一份加载对照表按这个选基本不会错PB 版本编码模型建议使用的 PowerUI 库文件关键注意点PB9ANSIPowerUI_PB9.pbl 加公共库路径避免中文外部 API 用 ANSI 版本PB10ANSIPowerUI_PB10.pbl与 PB9 差异不大可共用公共库PB11.5UnicodePowerUI_PB115.pbl需要 .NET Framework 2.0窗口对象模型有变化PB12.5UnicodePowerUI_PB125.pbl建议配合较新的 build多窗口消息处理更频繁2.2 按版本选择正确的 PBL 和运行时文件我的习惯是建一个独立目录树把 PowerUI 的 PBL 按版本分开放不加到应用库搜索路径里而是在应用对象 Open 事件里用 SetLibraryPath 临时挂载。这样做的好处是升级框架、切换版本都方便也不会污染生产库路径。比如我在 PB12.5 项目里只挂 PowerUI_PB125.pbl 加公共库PB9 项目则挂 PowerUI_PB9.pbl互不干扰。实际挂载代码大概是这样的// 应用对象 Open 事件中动态加载 PowerUI 库 string ls_path ls_path GetCurrentDirectory() \pui\PowerUI_PB125.pbl SetLibraryPath(ls_path)另外有两件事必须确认。一是发行包自带的运行时 DLL有些构建会带一个 pui_runtime.dll 之类的底层文件要一并放进应用目录别漏。二是 32 位 exe 必须配 32 位 DLL在 Win7 64 位客户端上如果配错表现是静默失败程序不报错、皮肤不生效排查起来比直接弹窗难受得多。还有如果包里有 extras\themes 目录里面往往是一批 .ini 或 .pui 格式的主题定义文件发布时千万别漏了否则主题应用不上看起来跟没换装一模一样。3. 核心机制PowerUI 不是皮肤文件而是动态控件层3.1 它接管了窗口消息而不是单纯换图这里我打个比方PowerUI 干的事情相当于给老房子做整体软装不拆承重墙把地板、墙面、门套统一翻新。在 PB 里“承重墙”是业务逻辑和 DataWindow“软装”则是窗口绘制消息。PowerUI 在窗口 Open 时给窗口挂上消息处理器利用 PB 的 pbm_other 事件截获 WM_NCPAINT、WM_CTLCOLOR、WM_DRAWITEM 这一批系统消息自己画标题栏背景、按钮圆角、Tab 页签样式。客户看到的“换皮肤”本质上是这些消息的返回值和绘制内容全变了。这跟“换一张背景图”有本质区别。换图方案在窗口拉伸时会露馅按钮的悬停、按下、禁用状态也完全做不出来。消息接管方案能响应这些状态变化因为每次鼠标悬停或按下系统都会触发对应的 WM_DRAWITEM 消息PowerUI 按主题规则重新画一遍所以按钮才有那种“活”的视觉反馈。示意代码长这样关键看工作顺序// 窗口 Open 事件中的示意调用不同 PowerUI 构建命名略有差异 n_pui_session lnv_ui lnv_ui create n_pui_session lnv_ui.of_SetTheme(ModernBlue) lnv_ui.of_AttachWindow(This)这里要提醒一句代码里的类名和方法名不同发行包可能不同我见过叫 n_pui_manager、n_tr_theme 的命名各有各的脾气。真正重要的是顺序——先建会话再设主题最后挂窗口。顺序一旦反了经常出现“窗口开了但控件还是老样子”的假象很多人在这上面浪费了半天时间。3.2 主题定制目录和 DataWindow 增强的配合PowerUI 的主题通常是一组外部配置文件里面定义了按钮默认高度、Tab 激活色、全局字体、圆角半径这些参数。我强烈建议把它放到客户端安装目录下的 pui\themes 里不要编死在代码中。项目验收之后客户自己就能改颜色改字体能省掉大量“这个按钮不够蓝”“这个标题能不能大一点”这类来回沟通成本。配置文件的格式一般很简单就是键值对有手就能改。对 DataWindowPowerUI 不会去接管数据处理它只把数据窗口内部的 Edit 框、CheckBox、RadioButton 等子控件的绘制统一化。你要让它出效果最好在 Modify 里把 DataWindow 的 Border、Font 属性指到主题值dw_1.Modify(DataWindow.Font.FaceNameMicrosoft YaHei) dw_1.Modify(DataWindow.Font.Height-9) dw_1.Modify(DataWindow.Border6)如果发现某列文字在换肤后显示不全多半是行高被固定死了把 autosize height 打开或者统一一下字体字号问题马上消失。这个坑几乎每个接入的人都会踩一次也是我跟几个同行交流时的高频话题。4. 实战接入一个老窗口换装的全过程4.1 改造前的基线备份和影响面评估老项目通常没有完善的自动化测试调试全靠人肉点界面。所以动手前我只做一件事把整个工作区的 PBL 复制一份到带日期的备份目录同时把目标窗口对象单独导出成 SRD 文件。我见过太多次因为改一个窗口导致整个 PBL 无法编译的教训PowerBuilder 的 PBL 一旦坏掉恢复成本非常高因为很多团队连源码版本管理都没有PBL 就是唯一真相毁不起。影响面评估阶段我问自己三个问题这个窗口有没有自绘代码有没有用外部 API 处理窗口消息里面有没有第三方 ActiveX 控件三个问题只要有任何一个答“是” PowerUI 挂上去就存在冲突可能。自绘代码和 PowerUI 同时处理同一个 WM_NCPAINT 时结果就是窗口变成“半个月皮肤、半个月裸奔”左边正常右边花掉。遇到这种窗口宁可放着不换肤也不要硬去调。4.2 在窗口事件里挂接 PowerUI 会话实操上我推荐一个做法在应用对象 Open 事件里创建全局的会话实例窗口层只负责 Attach 和 Detach。这样做的好处是主题切换、资源释放都集中在应用级业务窗口完全不用操心会话生命周期。封装好后业务窗口里的代码干净得可怕// 应用对象 Open 事件 g_pui_session create n_cst_pui_session g_pui_session.of_LoadTheme(pui\themes\ModernBlue.ini) // 主窗口 Open 事件 g_pui_session.of_AttachWindow(This) // 主窗口 Close 事件 g_pui_session.of_DetachWindow(This)局部换肤的关键就是 DetachWindow。报表打印预览窗口、某些特殊的弹窗不想要皮肤那就不调 Attach一点问题都没有。但这里有一个容易忽略的点如果主窗口挂上了会话它弹出的子窗口默认会继承皮肤所以要在子窗口的统一入口处加一个判断决定是否继续挂接。我一开始没做这个控制导致一个老旧的配置弹窗也被换了肤里面三个 ActiveX 控件当场花屏。4.3 验证与回退策略挂完皮肤后的回归重点我列了一份自己的清单Tab 页切换是否卡顿、DataWindow 编辑框输入时光标位置是否正确、下拉数据窗口展开是否闪烁、菜单弹出速度、窗口最小化再还原后控件有没有残留。PowerUI 的重绘逻辑是在 pbm_other 里做的窗口刷新频率一高开发机上看不出的闪烁问题到了客户的老机器上会特别明显。所以验证时别只用好机器找一台配置低的旧电脑跑一遍很多问题就现形了。回退策略也很重要。每个窗口的改造都独立保存一个 PBL 版本验证不过就直接用原来版本的 PBL 把这个窗口覆盖回来。我强烈建议先挑一个“界面烂但业务简单”的窗口做试点跑两周没问题再铺开。全库直接挂皮肤的教训我是亲眼见过的上线当天客户打电话说菜单显示成两排回滚花了一整晚项目组全员陪绑。5. 踩坑记录PB 版本差异、DataWindow 冲突和部署现场5.1 PB9 下图形控件闪烁PB12.5 下换肤失效PB9 上最典型的问题是 Picture 和 Graph 控件在换肤后疯狂闪烁。原因在于 PB9 的窗口默认没有开启双缓冲PowerUI 接管绘制消息后重绘频率提高老 GDI 接口处理不过来画面就开始抖。解决方式不是调 PowerUI而是改用法尽量不用 Picture 控件做界面装饰改用静态文本框或自绘必须用 Picture 的在控件事件里先画到内存位图再一次贴出能明显缓解。还有一个偷懒的做法是给这个控件加上跳过皮肤绘制的标记让它保持原样至少不闪。PB12.5 这边正相反最常遇到的是“换了没反应”。大多数情况是 PowerUI 版本和 PB12.5 的具体 build 不匹配pbm_other 事件被 PB 内部优先消费掉了。这时候别自己去改 PowerUI 的 PBL那样做会带来更大的维护成本以后框架一升级全是冲突。正确做法是找对应构建版本支持的补丁包或者把 PB12.5 升到该框架测试过的 build。我拿到的这个 201504202216 构建在 12.5 的某个补丁版本上表现非常稳定换到别的 build 上就时灵时不灵。5.2 DataWindow 打印预览与 PowerUI 渲染的冲突打印预览是重灾区。PowerUI 会把预览时的背景、边框也按照屏幕皮肤画一遍结果客户在预览里看到一套皮肤实际打印出来又是另一套第一反应一定是报表出 bug 了。我最后的处理方式是在 print 之前先 DetachWindow打印结束再重新 Attach 回来。这个动作必须封装到公共打印函数里不能散落在各个窗口里否则总有漏网之鱼。还有个隐藏问题TreeView 和 ListView 换肤后字体变了行高还是原来的值条目文字直接被截断。原因在于 PowerUI 设置了统一字体但 TreeView 的行高属性是独立维护的两者不联动。解决办法有两个要么把主题字体统一设成和原系统一致的宋体 9 号改动最小要么在主题配置里显式带上 treeview 行高参数稍微多花点功夫但效果更可控。5.3 发布时 runtime 文件缺一不可给客户做的安装包最容易被省略的就是 PowerUI 框架自己的运行时文件。这个框架大部分是 PBL 对象编译时会进到 exe 里但少数构建版本还附带一个 DLL 文件少掉这个文件时程序表现是“不报错、不换肤”排查难度比直接弹错还高。我在发布清单里专门列一行运行时文件名并且规定每次发布前必须在一台干净的虚拟机里验证一次换肤效果不能只盯着开发环境。一份典型的发布清单是这个样子文件作用缺失时的表现应用 exe 及配套 PB 运行时pbvm 系列程序主体程序无法启动PowerUI 编译后的 PBL 对象界面框架类库编译不过或窗口打不开pui_runtime.dll视发行版本而定消息钩子底层实现不报错换肤无效themes 配置目录主题定义主题无法应用控件样式错乱32/64 位依赖匹配系统对接客户端静默失败无任何提示最后说一件组织层面的经验。如果公司里同时有多个 PB 项目要推这种界面改造我建议把 PowerUI 的初始化封装成一个公共对象 n_cst_pui_service把主题加载、会话管理、窗口挂接、打印前后 detach 这些固定动作全部吸收进去。这样每个业务窗口只写一行代码后面换版、换主题、修框架 bug 都只动一个服务对象。这是我做第三个项目时才琢磨明白的事——前面几个项目的窗口里散落着大量重复代码每次框架小版本升级都要全局搜一遍改一遍实在划不来。早点把这层封装做好后边能省下的时间不是一星半点。本文还有配套的精品资源点击获取
返回列表