
简介这套PowerBuilderPB教程面向企业级数据库应用开发的学习者从零基础入门到进阶提升均有覆盖重点讲解数据窗口、图形化界面布局、事件驱动逻辑与PBL脚本编写并延伸到多数据库连接、工作流协作及与.NET、Java、Web服务的集成思路。资源包共44个文件以42个htm网页文档为主体辅以1个url快捷入口和1个txt说明文本整体约132KB体量轻便适合按章节在浏览器中查阅。已有291人学习说明其在PB学习群体中具备一定参考价值。教程将界面设计、数据处理、脚本开发与项目组织串联起来既能帮助新手建立完整的PB知识框架也便于有经验的开发者查找高级用法、理解扩展集成的实现方向作为日常开发与查阅的资料较为实用。1. PB教程不是“点菜单”先把它当成一条可复现的工程链PB教程这类资源最怕被当成“菜单点一遍”的说明书窗口拖出来了数据窗口也能检索一到保存、换库、打包就翻车。这份 PB教程资源的定位是把 PowerBuilder 的窗口、数据窗口、PowerScript、事务对象和部署串成一条可复现的练手链适合正在维护企业数据库客户端、接手老 PB 工程或者想用 PB 快速做管理类工具的人。它不解决“PB 还流不流行”这种争论只解决一个更实际的问题拿到包之后先跑哪一步、参数怎么设、失败看哪里。下面按工程落地顺序拆能抄的地方直接抄不能抄的地方我会标出边界。2. 把 PB 工程跑起来工作区、PBL 与数据库连接的三层校验拿到一个 PB 教程包很多人第一反应是双击里面的工程文件看到窗口能打开就认为环境没问题。这个判断太早。PB 的工程结构、对象库路径和数据库事务对象是三层独立的东西任何一层没对齐后面都会出现“界面能看、按钮能点、数据不动”的假象。我的习惯是先做三层校验工作区能不能完整加载 TargetPBL 有没有缺路径事务对象能不能在不碰业务表的情况下拿到明确的 SQLCode。2.1 先认工程边界工作区、Target 和 PBL 不是一回事PB 常见工程文件可以粗略分成工作区、Target 定义和对象库几类。工作区负责把多个 Target 组织在一起Target 记录应用对象、库列表和编译入口PBL 才是窗口、菜单、数据窗口、函数这些对象真正存放的地方。教程包如果只复制了部分 PBL或者 Target 里的库列表还指向原作者机器上的绝对路径打开后就会看到对象变灰、双击没反应甚至编译时才报缺对象。文件/对象作用排查时看什么工作区文件组织一个或多个 Target打开后 Target 是否完整显示Target 定义指定应用对象、库列表、编译顺序Library List 是否指向当前包内的 PBLPBL 对象库存放窗口、数据窗口、函数等是否缺库、是否有只读属性编译产物运行所需的动态库或包目标机器是否同目录部署我一般会先把整个教程包复制到一个路径短、无空格、无特殊字符的目录再打开工作区。不是迷信路径而是 PB 这类老工程对相对路径和库列表很敏感路径一变原本能加载的 PBL 可能就变成灰色。接着看 Target 的库列表如果某一项指向不存在的盘符不要急着全盘搜索同名文件先确认教程包内对应的 PBL 是否真的存在再把库列表改成当前包内的相对位置。改完做一次完整重建不要只点运行。完整重建能暴露对象引用缺失、函数签名不匹配、数据窗口对象找不到这类问题。这里有个选型理由为什么不直接新建一个空白工程练手因为教程包的价值往往在对象之间的引用关系窗口怎么调函数、函数怎么调数据窗口、数据窗口怎么绑事务对象这些关系在新工程里要重新搭。先让原工程跑通再拆出最小示例比一上来重写更接近真实维护场景。2.2 建连接与最小可运行窗口连接串只做占位不写死凭据PB 里最常见的数据库入口是事务对象SQLCA 是全局默认事务对象但我不建议把连接代码散落在每个窗口的 Open 事件里。教程示例为了短常常在窗口里直接 CONNECT读者照着抄最后每个窗口都连一次事务边界全乱。更稳的做法是先写一个连接函数参数从外部传入连接串只做占位不把账号密码写死在脚本里。// of_connect_demo连接参数由调用方传入避免把密码写死在 PBL 里 // as_dbms、as_server、as_db、as_user、as_pass、as_dbparm 均为外部参数 integer li_rc SQLCA.DBMS as_dbms SQLCA.ServerName as_server SQLCA.Database as_db SQLCA.UserId as_user SQLCA.DBPass as_pass SQLCA.LogId as_user SQLCA.LogPass as_pass SQLCA.AutoCommit False SQLCA.DBParm as_dbparm CONNECT USING SQLCA; IF SQLCA.SQLCode 0 THEN MessageBox(连接失败, SQLCode String(SQLCA.SQLCode) ~r~n SQLDBCode String(SQLCA.SQLDBCode) ~r~n SQLCA.SQLErrText) RETURN -1 END IF RETURN 0这段代码的关键不在 CONNECT 本身而在三个参数边界。DBMS决定用哪类数据库接口ServerName和Database在不同驱动下含义不完全一样DBParm更是不能照抄很多连接失败不是账号错而是 DBParm 里塞了当前驱动不认识的选项。AutoCommit设成 False 是为了让后续 Update 和 COMMIT/ROLLBACK 有明确边界如果教程示例里是 True保存看起来成功回滚却回不去这是后面避坑章要展开的点。SQLCode、SQLDBCode、SQLErrText三个值要一起看SQLCode 是 PB 层的结果码SQLDBCode 更接近驱动层返回码SQLErrText 才是能读的错误描述。连接函数通过后做一个最小窗口只放一个按钮用来确认事务对象状态。不要一上来就绑复杂数据窗口否则连接错误会被检索错误掩盖。// cb_ping 的 Clicked 事件只验证事务对象是否可用不碰业务表 IF SQLCA.SQLCode 0 THEN MessageBox(PB 探针, 事务对象已连接可以进入数据窗口检索) ELSE MessageBox(PB 探针, 事务对象不可用先检查 of_connect_demo 的返回值) END IF这个探针窗口的价值是隔离变量。连接失败就只查连接参数、驱动、网络和权限连接成功但检索失败再去查数据窗口的 SQL、检索参数和 SetTransObject。很多排错时间浪费在“一个按钮报错里面既连接又检索又更新”根本分不清是哪一层。2.3 用日志和事务对象确认不是“假连上”PB 老项目里有一种很隐蔽的情况开发机装了完整的数据库客户端连接成功换到测试机只装了部分运行库连接也提示成功但一检索就报错。要提前发现这种环境差异最好在连接后写一条本地日志把 DBMS、SQLCode、SQLDBCode 和错误文本落下来但不记录明文密码。日志不用复杂能按时间回看就够。// of_log_connect记录连接上下文密码字段不写入日志 long ll_file string ls_line ls_line String(Now()) | dbms SQLCA.DBMS | sqlcode String(SQLCA.SQLCode) | dbcode String(SQLCA.SQLDBCode) | err SQLCA.SQLErrText ~r~n ll_file FileOpen(ls_log_path, LineMode!, Write!, LockWrite!, Append!) IF ll_file -1 THEN FileWrite(ll_file, ls_line) FileClose(ll_file) END IFFileOpen的参数里LineMode!表示按行写Write!是写模式LockWrite!防止多进程同时写乱Append!保证追加而不是覆盖。ls_log_path建议放在应用目录下的日志子目录不要写到系统目录。这个日志不是给用户看的是给排错的人看的同一台机器上连接成功但检索失败时先看日志里的 SQLDBCode 有没有变化再看数据窗口的 SetTransObject 是否真的执行过。PB 里“假连上”很常见日志能把黑匣子打开一半。3. DataWindow 落地从检索、编辑到 Update() 的提交边界DataWindow 是 PB 教程里最容易被低估的部分。教程通常演示“拖一个数据窗口连上数据库Retrieve 出数据”但真实业务里能显示只是起点能不能改对、能不能只提交改过的行、并发时会不会覆盖别人才是分水岭。我的建议是把 DataWindow 分成三段看数据源决定拿什么数据更新属性决定怎么写回提交边界决定什么时候算成功。3.1 先定数据源和更新属性能显示不等于能回写数据源常见有三类SQL SELECT、Quick Select、外部数据源。SQL SELECT 适合多表关联、计算列和复杂条件Quick Select 适合单表快速起步外部数据源适合手工构造结果集或做临时校验。教程示例为了短经常用 Quick Select读者以为所有数据窗口都能直接 Update结果多表关联一保存就报“不可更新”。这不是 PB 的问题是数据窗口的更新属性没配。更新属性里要重点看目标表、可更新列、唯一键、Where Clause 和 Key Modification。目标表决定写哪张表可更新列要排除计算列、拼接列和只读列唯一键决定 PB 怎么定位一行Where Clause 决定并发时用什么条件匹配原行。常见做法是单表用主键做唯一键Where Clause 选 Key Columns 或 Key and Modified Columns不要一上来选所有列否则任何一个字段被别的会话改过更新就会影响 0 行。更新属性作用常见设置Table指定写回的目标表单表更新先锁定一张表Updateable Columns指定哪些列允许写回排除计算列、显示列、拼接列Unique Key定位一行记录用业务主键或数据库主键Where Clause控制并发匹配条件Key Columns 或 Key and ModifiedKey Modification主键变更时的处理策略按业务规则选择不要默认全放开这里有个选型理由为什么不在数据窗口里直接写复杂 SQL然后靠存储过程更新如果教程包面向的是管理类客户端DataWindow 的更新属性能把“检索、编辑、提交”放在一个对象里调试链路短如果业务规则很重才把更新下沉到函数或数据库层。先学会 DataWindow 的边界再决定哪些逻辑外移比一上来全塞进存储过程更容易排错。3.2 行状态与 Update() 的提交边界DataWindow 的行状态是 PB 的看家能力之一新手容易只看界面有没有变忽略行状态。常见状态包括未修改、数据修改、新行、新行再修改等。Update()会根据这些状态生成 INSERT、UPDATE、DELETE所以保存前要先AcceptText()把编辑框里的值接回数据窗口否则用户刚输入的值还没进入行缓冲区更新自然不对。// cb_save 的 Clicked 事件先接住编辑值再更新再按返回值决定提交或回滚 long ll_rc IF dw_1.AcceptText() -1 THEN MessageBox(保存, 编辑值未通过校验先处理当前行) RETURN END IF ll_rc dw_1.Update(True, False) IF ll_rc 1 THEN COMMIT USING SQLCA; IF SQLCA.SQLCode 0 THEN ROLLBACK USING SQLCA; MessageBox(保存, 提交失败 SQLCA.SQLErrText) ELSE MessageBox(保存, 提交完成) END IF ELSE ROLLBACK USING SQLCA; MessageBox(保存, 更新失败SQLCode String(SQLCA.SQLCode)) END IFUpdate(True, False)的第一个参数表示更新前接受编辑值第二个参数表示更新后是否重置行状态。这里用 False 是为了失败时保留行状态方便定位哪一行没写进去如果设成 True失败后状态被重置排查会更麻烦。COMMIT和ROLLBACK必须跟在Update()后面不要写成“Update 成功就弹成功”提交失败同样要回滚。SQLCode 要看 COMMIT 之后的值不是 Update 之前的值。3.3 检索参数和过滤别把筛选写成字符串拼接教程里的 Retrieve 经常不带参数直接全表取数。真实项目里检索参数是性能和安全的第一道闸。能在数据源里用参数的地方不要用字符串拼 SQL能在 Retrieve 里传参的地方不要先取全量再过滤。DataWindow 的检索参数在数据源里定义调用时按顺序传返回行数小于 0 表示失败等于 0 表示没命中大于 0 才是正常行数。// 按部门检索参数通过 Retrieve 传入不拼 SQL 字符串 long ll_rows ll_rows dw_1.Retrieve(ls_dept_code) IF ll_rows 0 THEN MessageBox(检索, Retrieve 返回 String(ll_rows) 检查数据源和参数) RETURN END IF // 本地过滤字段名必须与 DataWindow 列名一致 dw_1.SetFilter(status A AND amount 100) dw_1.Filter() IF dw_1.FilteredCount() 0 THEN MessageBox(过滤, 没有命中记录) END IFls_dept_code是检索参数类型和顺序要和数据源定义一致。SetFilter里的字段名是数据窗口列名不是数据库列名写错不会报 SQL 错只会过滤出空结果。FilteredCount()用来确认过滤后还有多少行。注意SetFilter虽然不直接拼 SQL但如果把用户输入原样拼进表达式仍然会有逻辑注入风险数值条件先转数值字符串条件要限制字符集和长度。这个细节在教程里经常被省略实际排错时却很容易翻车。4. PowerScript 与 SQL 嵌入事务、DataStore 和函数分层怎么落PB 的脚本很容易写成“窗口事件大杂烩”按钮里连接、检索、校验、更新、弹窗全在一起能跑但换个人接手就看不懂。教程包里的示例为了演示方便通常也会把逻辑压在窗口里。真正落地时我一般按三层拆窗口只收事件和收集参数业务函数管规则和事务边界DataStore 或数据窗口管取数和校验。这样拆不是为了好看而是为了失败时能定位。4.1 事务对象生命周期别让窗口各自 CONNECT单库应用可以直接用 SQLCA但连接和断开要有明确位置。常见做法是在应用启动时连接在应用关闭时断开多库或报表库场景则为每个库建独立事务对象不和主事务对象混用。最怕的是每个窗口 Open 时 CONNECTClose 时 DISCONNECT窗口一多事务对象被反复覆盖提交和回滚就失去边界。// 多库场景为报表库单独建事务对象不和主库 SQLCA 混用 transaction ltr_report integer li_rc ltr_report CREATE transaction ltr_report.DBMS as_report_dbms ltr_report.ServerName as_report_server ltr_report.Database as_report_db ltr_report.UserId as_report_user ltr_report.DBPass as_report_pass ltr_report.LogId as_report_user ltr_report.LogPass as_report_pass ltr_report.AutoCommit False ltr_report.DBParm as_report_dbparm CONNECT USING ltr_report; IF ltr_report.SQLCode 0 THEN MessageBox(报表库连接失败, ltr_report.SQLErrText) DESTROY ltr_report RETURN -1 END IF // 后续把 ltr_report 传给报表数据窗口的 SetTransObject RETURN 0这段代码的参数说明和主库连接一致重点是ltr_report是独立事务对象不能把主库的 SQLCA 直接换掉。DESTROY之前要先DISCONNECT USING ltr_report;否则连接资源可能留在会话里。多库场景下事务边界要跟着业务走主库更新和报表库查询不要硬塞进同一个 COMMIT除非业务明确要求跨库一致性。PB 教程里很少展开这一点但接手老系统时这是血泪经验。4.2 用 DataStore 做后台计算和批量校验DataStore 是看不见界面的数据窗口适合后台校验、批量取数、导出前计算。它的好处是不用打开窗口就能复用数据窗口的 SQL 和显示格式坏处是生命周期要自己管创建后必须销毁。教程示例如果只演示 DataWindow读者容易忽略 DataStore实际项目里它能把很多“为了取一个值开一个窗口”的坏习惯改掉。// of_check_rule用 DataStore 在后台跑数据窗口不弹界面 datastore lds_rule long ll_rows string ls_result lds_rule CREATE datastore lds_rule.DataObject d_rule_check lds_rule.SetTransObject(SQLCA) ll_rows lds_rule.Retrieve(ls_biz_key) IF ll_rows 0 THEN DESTROY lds_rule RETURN -1 END IF IF ll_rows 0 THEN DESTROY lds_rule RETURN 0 END IF ls_result lds_rule.GetItemString(1, check_result) DESTROY lds_rule IF ls_result PASS THEN RETURN 1 ELSE RETURN 2 END IFDataObject要指向已经存在的数据窗口对象SetTransObject(SQLCA)把事务对象绑上去Retrieve的返回值按小于 0、等于 0、大于 0 三种情况处理。GetItemString(1, check_result)取第一行的校验结果字段字段名必须和数据窗口列名一致。DESTROY不能漏尤其是在循环里创建 DataStore 的场景漏一次就多占一份内存。批量校验时我一般把 DataStore 放在业务函数里窗口只接收返回码不直接操作 DataStore。4.3 函数分层窗口只收事件业务函数管规则SQL 层管事务PB 的窗口事件很容易膨胀一个“保存”按钮里既有界面校验又有业务规则还有 SQL 和弹窗。更稳的拆法是窗口事件只做三件事收集界面值、调用业务函数、根据返回码提示业务函数负责规则校验、事务开始和结束数据窗口或 DataStore 负责具体取数和更新。这样拆完教程示例里的代码可以保留但要把连接、提交、回滚从按钮里挪出去。层级放什么不该放什么窗口事件收集参数、调用函数、显示提示直接 CONNECT、直接 COMMIT业务函数规则校验、事务边界、返回码界面控件引用、弹窗文案数据窗口/DataStore检索、更新、行状态跨多个事务对象的提交函数返回值要统一比如 0 表示成功-1 表示参数错-2 表示数据库错-3 表示业务规则不通过。窗口根据返回码决定提示文案不要把 SQLCA.SQLErrText 直接弹给最终用户看。教程里直接弹数据库错误是为了演示真实项目里要转成可读提示同时把原始错误写日志。这个分层不是架构洁癖是为了在换库、换驱动、加字段时不用满工程搜 CONNECT。5. 避坑排查PB 教程里最容易“照着能点、换库就翻车”的 5 个点PB 教程的示例通常在作者机器上跑通过读者换一个数据库、换一个目录、换一台机器问题就出来了。下面这 5 个点是我见过频率最高的按“现象、原因、解决”写能对上就直接处理对不上也能当排查顺序。5.1 对象库与运行环境的坑现象工作区能打开但 PBL 显示灰色窗口双击没反应编译时报缺对象。原因Target 的库列表还指向教程包外的绝对路径或者复制时漏了某个 PBL也可能是 PBL 被设成只读PB 无法写入临时对象。解决先把整个包放到短路径下打开 Target 的 Library List把不存在的路径删掉或改成包内相对路径检查 PBL 文件属性去掉只读做完一次完整重建不要只运行。如果仍然缺对象按报错的对象名在包内找对应 PBL不要全盘搜索同名文件版本混用会把问题弄得更复杂。现象开发机运行正常打包到目标机器提示缺动态库或应用起不来。原因目标机器没有对应运行库或者数据库客户端动态库不在搜索路径也有可能是开发机 PATH 里有一份旧库打包时没带上。解决列一张部署清单把应用运行库、数据库客户端库、配置文件、日志目录权限都列进去在干净目标机器上做一次安装验证不要只在开发机双击 EXE。数据库连接库尽量和应用同目录或按部署说明放置不要依赖目标机器碰巧装过什么。这个坑教程里很少写因为作者机器通常已经装全了。5.2 连接与事务的坑现象连接提示成功但数据窗口 Retrieve 报“事务对象未设置”或取不到数据。原因数据窗口没有SetTransObject(SQLCA)或者用了SetTrans但事务对象生命周期已经结束也可能是检索参数顺序不对返回 0 行被误判成连接失败。解决在 Retrieve 前确认SetTransObject已执行检查Retrieve返回值小于 0 是失败等于 0 是没命中大于 0 才是正常。把连接探针和检索探针分开先确认 SQLCode再看数据窗口 SQL。不要在一个按钮里同时 CONNECT、Retrieve、Update否则错误会被后一步覆盖。现象保存时看起来成功回滚却回不去或者一部分数据已经写进库。原因事务对象AutoCommit被设成 True或者代码里隐式提交也可能是 Update 之后没有统一 COMMIT/ROLLBACK异常分支直接跳出。解决连接参数里把AutoCommit设为 False保存函数里按Update()返回值决定 COMMIT 还是 ROLLBACK异常分支也要走 ROLLBACK。检查是否有多个事务对象混用主库更新却提交了报表库事务。这个坑在教程示例里特别常见因为示例为了少写代码可能默认自动提交。5.3 数据窗口更新的坑现象Update()返回成功但数据库里的数据没变或者只改了部分行。原因数据窗口的 Update Table 没设、Unique Key 选错、Updateable Columns 把关键列排除了Where Clause 用了所有列别的会话改过字段后匹配不到原行也可能是保存前没AcceptText()编辑值还在编辑框里。解决先检查数据窗口更新属性目标表、唯一键、可更新列、Where Clause 四项逐个确认保存前执行AcceptText()并发要求高的场景用 Key and Modified 或时间戳字段做乐观锁。更新失败时看 SQLCA 的 SQLCode 和 SQLErrText不要只看Update()返回值。教程里“点保存就成功”的示例往往省略了这些属性说明。6. 进阶验证把 PB教程 示例压成一条可重复的冒烟链路教程包里的示例很多一个个点窗口很费时间而且容易漏掉换库后的回归问题。我一般会写一个冒烟函数把连接、检索、更新回滚三段串起来返回码只有通过和不通过先把环境问题挡在业务演示之前。这个函数不追求覆盖所有业务只验证 PB 工程最容易被环境打穿的三条链路。// of_smoke0 表示三段都通过-1 表示连接失败-2 表示检索失败-3 表示回滚更新失败 // 表名、数据窗口名、检索参数替换成教程包里的示例对象即可 integer li_rc li_rc of_connect_demo(...) IF li_rc 0 THEN RETURN -1 IF dw_probe.Retrieve(ls_probe_key) 0 THEN RETURN -2 // 在事务里做一次更新再回滚确认写链路和事务边界都正常 IF dw_probe.Update(True, False) 1 THEN ROLLBACK USING SQLCA; RETURN -3 END IF ROLLBACK USING SQLCA; RETURN 0这段冒烟的关键是最后必须 ROLLBACK不要用真实业务数据做验证。of_connect_demo的参数按第 2 章的连接函数传dw_probe用教程包里最简单的单表数据窗口ls_probe_key选一条可回滚的测试记录。返回码分段失败时能立刻知道是连接、检索还是更新链路的问题。我还会在日志里记下每次冒烟的时间、DBMS、SQLCode 和返回码换库或换机器后先跑一遍再打开业务窗口。从那以后我每次拿到 PB教程 包都先跑连接、检索、回滚更新这三段再去看界面示例这个习惯比背语法更能减少翻车。希望帮到你。本文还有配套的精品资源点击获取