简介:PB(PowerBuilder)全面教程是一份面向初学者与有经验开发者的系统学习资料,聚焦企业级数据库应用开发,内容覆盖DataWindow数据窗口、GUI拖放式界面设计、PBL脚本语言、多数据库连接以及.NET/Java桥接和Web服务等进阶主题,同时也解释了主题窗口(ztwz)与接口组件(jsjz)等关键概念。压缩包共44个文件,以42个分章节htm网页教程为主体,便于按目录顺序逐页学习,另含1个源代码链接(洪越源代码.url)与1个说明文件txt,整体仅132KB,轻量易下载,适合离线查阅与快速检索。资源目前已有288人学习下载。教程从基础操作讲到高级应用,既有知识点拆解,也有源码参考和说明文档辅助,可帮助读者系统搭建PB知识体系,并在实际项目中熟练运用数据窗口、事件驱动模型、数据库连接等核心能力,是一份性价比很高的入门与提高参考资料。 先花十秒钟说明白一件事:你在搜索引擎里敲“PB教程”,大概率会翻到一堆跟开发八竿子打不着的东西。PB这个缩写确实烂大街,但在医院HIS、银行核心、制造业ERP这些圈子里混过的人都知道,PB只有一个含义——PowerBuilder。
这玩意儿被很多人叫“老古董”,可你去招聘网站翻一翻,HIS实施工程师、医疗软件维护、老系统二次开发的岗位,几乎都写着“熟悉PowerBuilder优先”。原因很简单:过去二十多年里,大量关键业务系统就是用PB堆出来的,它们还在稳定运行,而且短期内不可能被推翻重写。
这篇教程我就是写给这三类人看的:刚接手PB维护项目、面对一堆PBL文件不知道从哪下手的新人;在HIS系统里做日常维保、天天跟数据窗口和打印较劲的工程师;以及想了解一下这套“古董技术”到底凭什么还活着的开发者。我不会给你讲一堆虚头巴脑的概念,直接讲怎么理解PB的架构、怎么用数据窗口、怎么排查问题,以及怎么把它接入现在的系统。
1. 先搞清楚:此PB非彼PB,PowerBuilder到底是什么
1.1 一个在HIS和ERP圈子里活得挺好的“老将”
PowerBuilder是1991年由PowerSoft公司推出的开发工具,后来被Sybase收购,再后来随着Sybase一起并入SAP。在.NET和Java还没成气候的年代,PB几乎是Windows桌面应用和C/S架构系统的首选,尤其在国内的医院信息系统、电信计费、政府政务、制造业ERP里,存量极其庞大。
人家说它老,我不否认。它的IDE界面确实停留在上世纪,脚本语言PowerScript也没什么现代语法糖。但它的核心武器——DataWindow(数据窗口),放在今天依然是数据库应用开发的效率之王。别人写一个报表界面可能要半天,PB里拖个数据窗口对象,配好SQL,设置一下显示风格,十分钟搞定,而且查询、更新、打印、导出全套功能都自带。你告诉我哪个现代框架敢这么干?
1.2 看懂PB应用架构,你才算真正入了门
刚接触PB的人最容易懵的,是打开工作区之后看到一堆对象类型,不知道它们之间什么关系。我用最直白的方式给你捋一遍。
应用一定有且只有一个Application对象,它是整个程序的入口。双击Application对象打开它的Open事件,写在那里的代码就是程序启动的起点,通常在这里配置数据库连接、打开主窗口、设置全局变量。
然后是一堆Window(窗口)对象。每个窗口就是用户看到的一个界面,窗口上可以放按钮、输入框、数据窗口控件(DataWindow Control)这些可视化元素。在PB里,窗口是承载业务功能的容器,实际的数据展示和操作几乎都交给数据窗口控件来完成。
再往下是DataWindow(数据窗口)对象。这个要重点理解:数据窗口控件是一个摆在窗口上的“壳”,数据窗口对象才是真正干活的东西。数据窗口对象里定义了查询SQL、数据显示格式、列的编辑风格、校验规则、打印样式等等。一个数据窗口对象可以被多个窗口里的多个控件复用,这也是PB高效的原因之一。
一个PB应用的项目文件组织大致长这样:
app.pbl // 存放Application对象、全局函数、菜单等 main_window.pbl // 存放窗口对象、窗口上的控件 dw_patient.pbl // 存放各种数据窗口对象 utility.pbl // 存放公共用户对象、结构体 app.exe // 编译后的可执行文件 app.pbd // 库文件编译后的动态库,和exe放在一起发布看到这里你应该明白了:PB开发的核心工作,不是在写代码,而是在组织对象。一个PBL(PowerBuilder Library)就是一个对象库文件,里面可以装很多对象。程序编译时可以选择把所有内容编进EXE,也可以把部分对象编译成PBD动态库,在运行时加载。明白了这套对象模型,你看老系统就不会再一头雾水。
2. 数据窗口:PB的魂,打印预览、查询、更新全靠它
2.1 数据窗口的几种显示风格,各有什么用途
数据窗口对象有很多种显示风格(Presentation Style),我挑实际项目中最高频的几种:
- Grid(表格风格):数据按行列展示,列头固定,底部自动有滚动条,查询结果列表基本都用它。
- Freeform(自由格式):字段像表单一样上下排列,适合做录入界面,比如病人基本信息编辑、工单详情查看。
- Group(分组风格):按某个字段分组统计,带组头组尾,适合做简单报表。
- CrossTab(交叉表格):类似Excel透视表,行列交叉统计,做业务统计报表时非常实用。
- Label(标签风格):批量打印标签、条码、病历卡时用。
你可以把数据窗口理解成一个“数据+界面+行为”的结合体:它从数据库或内存里读数据,按你定义的样式画到屏幕上,顺带把增删改操作也管理起来。这玩意儿在1989年(严格说是初版PowerBuilder 1.0发布后出现的概念)就有这种设计思路,领先时代太多了。
2.2 printpreview()函数真的有,而且配套函数必须一起学会
很多人搜“pb的数据窗口有printpreview()函数吗”,说明死磕过打印功能。答案很明确:有。数据窗口控件直接提供Precision Preview(打印预览)能力,而且不是光有预览,还配套了好几个必须掌握的打印相关函数。
// 进入打印预览状态 dw_list.PrintPreview(true) // 设置预览显示比例,100就是实际大小 dw_list.PrintPreviewZoom(100) // 显示/隐藏预览状态下的标尺 dw_list.PrintPreviewRulers(true)这段代码干的事很清楚:先把数据窗口切到打印预览模式,然后设置缩放比例,再打开标尺方便检查页边距。实际项目里我几乎总是把这几个函数配合使用,因为只调用PrintPreview(true)的话,默认显示比例可能忽大忽小,用户总是看不清。
退出预览状态更简单,调用dw_list.PrintPreview(false)就行。
除了预览,打印相关还有一个高频组合:
// 弹出系统打印设置对话框 dw_list.PrintSetup() // 直接打印到默认打印机 dw_list.Print()这里有个细节坑一定要记住:PrintSetup()弹出来的打印设置框,设置完以后并不会自动应用到你当前的数据窗口上,你需要重新调整数据窗口的打印参数,比如纸张大小、边距,再触发打印。我见过有同事在代码里调了PrintSetup,然后直接Print,结果用户设置的纸张方向根本没生效,纸张竖着打,内容却横着排,被护士站打电话过来骂了一上午。
正确的做法是在窗口Open事件里就把数据窗口的打印参数设置好,尤其针对那种需要连续打印多页的清单、报表。
dw_1.Object.DataWindow.Print.PaperSize = 1 // 1是默认纸张 dw_1.Object.DataWindow.Print.Orientation = 1 // 1纵向,2横向 dw_1.Object.DataWindow.Print.PaperSource = 1顺便说一句,数据窗口的打印属性和预览属性在设计器里也能调,打开数据窗口对象,右键属性,切到Print Specifications选项卡就能看到。但代码设置的优势是可控、灵活,可以根据业务动态改,我建议你直接记代码。
2.3 数据窗口取数、更新与事务管理
要说数据窗口使用频率最高的操作,肯定是Retrieve取数和Update回写。
// SQLCA是全局事务对象,在Application Open事件里配置 dw_list.SetTransObject(SQLCA) dw_list.Retrieve()第一行SetTransObject是把事务对象(连接句柄)和数据窗口绑定,第二行Retrieve()才是真正执行查询。
如果你要带条件查询,直接在Retrieve后面传参数:
dw_list.Retrieve(li_dept_id, ls_begin_date)这里有个原则问题:绑定事务前,一定要确保SQLCA已经成功连接数据库,否则Retrieve会直接报错。很多新手写代码习惯把连接逻辑放在用的时候才写,就容易踩这个顺序的坑。
数据更新也一样简单:
dw_detail.SetTransObject(SQLCA) dw_detail.Update()但Update的坑在于事务控制。PB默认情况下,调用Update后数据不会自动提交,你必须手动COMMIT;或ROLLBACK;。这跟很多人用惯了ORM自动事务的习惯完全不同:
if dw_detail.Update() = 1 then COMMIT USING SQLCA; else ROLLBACK USING SQLCA; MessageBox("错误", "保存失败") end if我见过不少老系统的问题就出在这里:忘记COMMIT,用户明明点了保存,重启程序数据没了,因为事务一直没提交。排查的时候又不敢随便动代码,只好在数据库端反复查锁。自己写新代码时,一定要把事务提交这件事写成肌肉记忆。
3. 调用PB模型:让老系统真正“跑”起来
3.1 调用存储过程和外部数据源
搜索热词里有“调用pb模型”,这在PowerBuilder语境下最常遇到的场景就是调用存储过程、外部数据源,以及被外部系统调用。先说调用存储过程。
PB调存储过程有两条路:一是创建存储过程类型的数据窗口,把存储过程当作数据源;二是在代码里直接用嵌入式SQL调用。
用数据窗口调存储过程的步骤如下:新建数据窗口对象时,数据源选择Stored Procedure,然后从列表里选出要调的存储过程。这个存储过程返回的结果集,会被当作普通查询结果处理,可以显示、编辑、更新。这是最推荐的方式,因为写完之后和普通数据窗口没有区别,查询、打印、导出都能用。
如果是在代码里直接调,写法是这样的:
DECLARE proc_get_patient PROCEDURE FOR get_patient_info(:patient_id); EXECUTE proc_get_patient; FETCH proc_get_patient INTO :ls_name, :ls_sex; // 处理完一定要关闭游标 CLOSE proc_get_patient;需要注意,存储过程的参数传递里,输入参数用冒号加变量名的格式,和普通PowerScript变量区分开来。这种嵌入式调用适合简单操作,比如调一个纯计算的存储过程,不需要返回结果集到界面上。
3.2 让PB“被调”:COM组件、WebService与数据对接
HIS系统现在几乎没有独立存在的,体检系统、LIS检验系统、RIS放射系统、医保平台,互相都要传数据。可PB老系统怎么做接口?
第一种是中间表方案。这是最“土”但最可靠的方式:两个系统共用同一个数据库,A系统往某某表写数据,B系统定时扫描这张表,拿到新数据去处理。PB这边通常写一个定时器(Timer事件),每隔几分钟扫描一下待处理表,把数据读出来再写进去。这方案不需要改老代码的架构,出问题也容易排查,在医疗行业的系统集成里用了很多年,至今还在大量使用。
第二种是WebService调用。PB 11及以上版本支持直接在代码里创建WebService代理对象。比如要调用一个体检系统提供的查询接口,可以这样写:
io_ws = CREATE n_ws_health_check io_ws.CreateInstance("http://192.168.1.100:8080/ws/HealthCheck?wsdl") li_ret = io_ws.GetReport(ls_patient_id, ls_report)但这里有个大坑:PB调用WebService依赖本机的SOAP客户端库,部署环境稍微复杂一点,比如服务器没有装对应版本的.NET或C++运行库,调用就会莫名其妙失败。而且WS地址是硬编码在代码里的,如果对面系统换IP或者换端口,就需要重新编译发布一遍。所以现在很多实际项目宁可加一层Java或C#写的转换网关,把HTTP接口包装好,再让PB这边调,稳定性会好很多。
第三种是把PB功能封装成COM/COM+组件。PB支持把用户对象编译成COM组件,注册到Windows系统里,然后让别的语言(比如C#、VB)调用。这在老系统与新平台并行过渡的时期非常好用:新做的前端界面,后台仍然调PB封装好的业务组件,老逻辑不用重写。
3.3 在PB内部做“模型调用”:窗口打开、事件触发
还有一种“调用”是在PB应用内部发生的——打开窗口、触发事件、调用用户对象方法,这个难度不大但频率极高。
// 打开一个窗口并传参数 OpenWithParm(w_patient_detail, ls_patient_id) // 在窗口里通过Message对象取参数 ls_patient_id = Message.StringParm // 调用用户对象的方法 luo_calc = CREATE uo_calculate li_result = luo_calc.Calc(li_a, li_b)维护老代码时,你在系统里经常看到这种写法。需要提醒的是,OpenWithParm传参数是通过Message全局对象接力完成的,窗口Open事件执行完以后Message对象就失效了,所以要立刻取值。老代码里经常有人在这里翻车,窗口里忘记取参数或者取完又被其他代码覆盖,导致界面显示空白。
4. HIS系统里的PB维护实战:那些年我们修过的数据窗口
4.1 HIS为什么还在用PB,数据窗口的强项正好是医疗业务需要的
医院系统对软件的要求其实很朴素:稳定、响应快、操作直接。医生开单、护士录入、药房发药,操作界面就一个要求——别绕弯子,点两下能办完的事绝不点三下。PB的C/S架构天然就是客户端直连数据库,响应速度极快,加上数据窗口直接绑定SQL,查询结果秒出,这套体验在局域网环境下吊打很多B/S系统。
还有一个现实因素:医院信息科的人对老系统的熟悉程度非常高,临床科室的电脑里还装着一堆老PB客户端的快捷方式,有些还写着“住院医生站”“护士工作站”,界面朴素得像上世纪遗留物,但护士们用习惯了,谁要提换系统,第一个跳出来反对的往往不是技术部门,而是使用部门。
4.2 日常维护高频操作:改报表、查数据、调界面
PB维护工作里,最常接到的需求就是改报表。医生工作站打印出来的检验报告单、病区日报表、药房库存表,都是数据窗口做的。改报表的核心就是打开数据窗口对象,调整字段位置、列宽、字体大小,改完保存,然后重新编译生成EXE和PBD发布到客户端。
这里要格外注意一点:修改数据窗口对象后,一定要重新生成包含它的PBD文件,并把它下发到所有客户端。很多人改完代码只在开发环境测试没问题,忘了重新发布PBD,结果用户那边跑的还是老界面,电话立刻打过来:“你改的什么啊,没变化!”这个坑我踩过不止一次,后来养成了习惯:每次修改数据窗口后,发布前先跟客户端确认版本号。
查数据是另一个高频操作。HIS维护里经常被问到“这个药为什么库存不对”“这个病人为什么重复挂号”,这种问题用SQL查数据库肯定比看代码快。PowerBuilder的Database Painter自带一个交互式SQL窗口,可以直接执行查询。但生产环境数据库连接串的账号权限通常受限,很多维护人员习惯直接连数据库管理工具跑SQL,这个没问题,但千万记得:生产数据库只做查询,不要在它上面乱更新。
4.3 常见问题排查速查表
我把日常维护中遇到最多的几类问题整理成了表格,方便直接对照排查:
| 现象 | 可能原因 | 排查思路 |
|---|---|---|
| 数据窗口打开后一片空白 | SQL未检索/事务连接失败/数据窗口不可见 | 先SetTransObject再Retrieve,看看SQLCA的SQLCode返回值,再到数据库端跑一遍SQL确认有无数据 |
| 点击打印没反应 | 打印参数设置错误/默认打印机丢失 | 检查数据窗口的Print属性,测试打印一个简单文档排除驱动问题 |
| 打印预览显示比例不对 | 未设置PrintPreviewZoom参数 | 在预览前加上dw_control.PrintPreviewZoom(n),n按实际需要取80或100 |
| 保存不生效,重启数据消失 | 事务未提交 | 检查Update后是否有COMMIT,没有就补上 |
| 窗口打开就报错,ErrorLine指向某函数 | 对象丢失或全局函数签名变了 | 看栈信息和ErrorLine,去PBL库里搜被调用的函数是否存在,参数个数是否一致 |
| 客户端提示“PBL文件不存在” | PBD或其他动态库未更新到客户端 | 重新发布PBD,确认发布路径 |
做维护最重要的一条心得:改代码前先把原文件做备份,改完以后保留修改记录。PB不像现在的新技术栈有完善的版本管理中间产物,PBL文件是二进制的,出了问题要还原就很麻烦,没有备份只能靠回忆,那感觉真的酸爽。我自己一般改之前会把涉及的PBL复制一份带时间戳的备份,改完之后出问题也能秒回滚。
5. 从开发到发布:PB工程的版本管理与部署
5.1 PBL文件是好是坏,用Git管理PB工程的正确姿势
说到PBL的二进制属性,很多从现代开发转过来的同事第一个吐槽点就是:没法Diff。用Git管理PB工程,你没法像看代码变更一样看PBL里改了什么,只能看文件整体变化。
我的建议是,PB工程必须做版本控制,但要结合它的特点来:
- PBL按对象拆分,不要一个超大PBL装几百个对象,多人改同一个PBL会频繁冲突。
- 提交时态度要明确:要么整对象提交,要么就接受二进制冲突的现实,不要试图去合并两个PBL。
- 有条件的话,用PowerBuilder自带的Object Exporter把对象导出成文本格式(.srw/.srd/.srf),再提交到Git,这样可以做代码级Diff。PB的库管理工具里有“Export”功能,可以把指定对象导出成文本文件。这个习惯一旦养成,回溯问题会轻松很多。
另外,PB还有一个杀手锏级特性:PBL文件里每个对象都自带一个注释字段,里面有创建时间、修改时间、修改人(要配合PBORC.INI配置)。我们团队的习惯是在每个对象头部维护一段修改日志,谁改了什么、为什么改,都写清楚。老系统的代码注释本来就不多,这个对象级日志是维护时唯一的“自保”手段。
5.2 发布EXE前的三个关键准备
维护项目发布版本,我给自己定了一条铁律:每次发版前必须过三关。
第一关:检查数据库连接配置。很多老系统把数据库连接信息写在配置文件中,或者直接写在Application Open事件里。发布前确认连接字符串指向的是正确的环境(测试库还是生产库),千万别把测试环境配置发上去,这种事真出过,护士站打开程序直接连到测试库,数据全是乱的不说,还被用户当成生产数据用了一整天。
第二关:重新编译所有修改过的对象并生成PBD。PB的编译不是一条命令全自动搞定,你在开发环境改完数据窗口仅保存,编译时对象并不会自动进入EXE或PBD,必须手动执行Build/Rebuild。这一步漏掉了,改的东西等于白改。我推荐在发布流程里加一个检查项:打开目标PBL库,确认对象编译日期正确。
第三关:版本号管理。在窗口中放一个版本号,或者用版本资源文件,每次发布更新版本号。客户端遇到问题,第一件事就是问你“我这是不是最新版”,有了版本号,这个问题十秒钟就答完。如果没有,你只能远程看客户端的文件修改日期,效率低得多。
还有一个老生常谈的点:PB客户端发布不是把EXE复制过去就完事,还要确保目标机器上有对应的数据库客户端(比如Oracle的Instant Client、SQL Server的Native Client),否则数据库连接会失败。这类问题在量产环境里特别隐蔽,因为开发机器上什么都是装好的,新部署的机器啥都没有。
6. 维护PB老项目的一些真心话
我见过太多人对PB嗤之以鼻,觉得2025年了还在写这种老掉牙工具的代码很丢人。但我想说的实际情况是:PB项目维护的薪资并不低,而且竞争远没有Java、前端那么卷,因为会的人越来越少,但系统不会自己消失。尤其医院、银行、制造行业,这些核心系统对稳定性要求极高,你在维护过程中学到的东西,比如如何在一个庞大而代码老旧的系统里定位问题、如何高效处理数据窗口的各类业务场景,这些能力放在任何一个技术栈里都是通用的。
如果你现在接手的正是一套PB老系统,别急着抱怨,先把PBL库里的对象结构理一遍,看看数据窗口和存储过程是怎么组织的,再顺着Application对象的Open事件把启动流程走一遍。用不了半个月,你就能对这坨看似乱七八糟的老代码建立起完整的认知地图。
至于那些还在用PB开发新系统的团队,说实话这几年我很少见了。但如果你们真在做,恭喜你,你们手上握着一台仍然能高效运转的“老式机床”。别追求什么酷炫的前端效果,把数据窗口的打印、导出、报表这些硬功夫练扎实,系统照样能在行业内活得很滋润。
最后送上一句我自己的经验:老系统最怕的不是没人会,而是半懂不懂的人瞎改。你每动一个数据窗口、每改一条SQL,都先想想影响范围,改完多测几个场景,尤其是打印和导出这两个高频动作。医疗行业里,一张打错的患者检验单,可能引发的事故远比你想的严重。做这一行,细心比聪明重要。
本文还有配套的精品资源,点击获取