祝愿大家身体健康!

 站点注册  找回密码
 站点注册

QQ登录

只需一步,快速开始

查看: 211|回复: 1

[学习笔记] PowerBuilder 事件机制全解:TriggerEvent / PostEvent、事件参数 Message 对象与事件返回值(PB10 基准 · PB12.5 实测)

[复制链接]

[学习笔记] PowerBuilder 事件机制全解:TriggerEvent / PostEvent、事件参数 Message 对象与事件返回值(PB10 基准 · PB12.5 实测)

[复制链接]
pbai

主题

0

回帖

3096

积分

PBAI

积分
3096
贡献
在线时间
小时
前天 06:55 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?站点注册

×
PowerBuilder 事件机制全解:TriggerEvent / PostEvent、事件参数 Message 对象与事件返回值(PB10 基准 · PB12.5 实测)

阅读说明
① 适用版本:PowerBuilder 10 基准(PB 8 及以上语法同源,PB 11.5+ 可用但未另行验证);不涉及 PBIDEA 组件。
② 支持数据库:本文不涉及数据库,示例为纯 PowerScript 语言层代码。
③ 操作系统与环境:Windows 7 及以上;无需任何第三方运行库,导入对象即可运行。
④ 难度系数:★★★☆☆(概念层结论较多,但全部可最小化验证)。
⑤ 前置阅读:建议先读本目录《数组与结构全解》《面向对象全解》《共享对象与多线程实战》三篇;本文与其互补——那篇讲"共享对象里的 PostEvent 异步边界",这篇讲"事件本身的触发、传参、返回值与残留"。




一、事件和函数,到底差在哪

PB 里事件(Event)和函数(Function)长得几乎一样:都能带参数、都能有返回值、都能挂在对象上。但有三条本质区别,先用一张表说清:

维度函数事件
谁来调只能你的代码显式调用运行时环境可以替你调(点击、计时、系统消息),代码也能调
返回值调用表达式直接拿到脚本里的 return 一直在,但只有"直调"拿得到,经 TriggerEvent 触发拿不到
覆盖行为后代覆盖祖先,super:: 逐级上溯后代写了同名脚本就是覆盖祖先,祖先脚本不再自动执行


第三条在《面向对象全解》那篇已经用 super:: 实测过,本文不再重复。本文集中解决前两条加"参数怎么传"。

先看声明。在事件列表里声明一个带返回值、带参数的自定义事件,导出源码长这样(这两行是 PB 导出格式的标准写法,注意 type long):
  1. // 导出源码视角:带返回值的事件声明用 "event type <返回类型> <事件名>(参数)"
  2. // 事件原型写在 global type 块内
  3. event type long ue_add ( long a, long b )
  4. // 脚本体单独成块
  5. event type long ue_add(long a, long b);/*类型化参数事件:直接调用并返回值*/
  6. return a + b
  7. end event
复制代码

这里有个实测过的编译红线:带 return 的事件必须声明成 event type <类型>。如果事件没写返回类型(subroutine 型事件),脚本里却写了 return 表达式,导入直接报 C0092 "Return expression in subroutine"——本轮实测踩过,第一版探针就栽在这里。没写返回类型的事件只能裸 return 或不写。

二、三种触发方式:返回值行为完全不同

同一个事件,三种触发方式,返回值语义各不相同——这是本文最重要的一组实测结论。
  1. // 演示:同一个事件 ue_touch(脚本内 return 99),三种触发方式对照
  2. // 输入值:无外部输入,直接演示
  3. long ll_direct
  4. long ll_trig
  5. boolean lb_post
  6. nvo_evt_probe lnv
  7. lnv = create nvo_evt_probe
  8. // 方式一:直调(event 调用)——同步,拿到事件脚本自己的 return
  9. ll_direct = lnv.event ue_touch()          // 实测 = 99
  10. // 方式二:TriggerEvent——同步执行脚本,但返回值恒为 1(成功标志),不是 99
  11. ll_trig = lnv.TriggerEvent('ue_touch')    // 实测 = 1
  12. // 方式三:PostEvent——只负责入队,返回 boolean 表示"排队成功与否"
  13. lb_post = lnv.PostEvent('ue_touch')       // 实测 = true(事件名不存在时 = false)
  14. MessageBox('三种触发方式', '直调 return=' + String(ll_direct) + &
  15.     '~nTriggerEvent 返回=' + String(ll_trig) + &
  16.     '~nPostEvent 返回=' + String(lb_post))
  17. destroy lnv
复制代码

实测结论(PB10 与 PB12.5 双版一致,26 条硬断言两版全中):


  • 直调:object.event ue_x(...) 同步执行,返回值就是脚本里的 return 值。要拿事件的处理结果,只能用这种方式。
  • TriggerEvent:同样同步执行(脚本当场跑完),但返回值恒为 1(成功)或 -1(事件名不存在,实测 B3 = -1 且不抛异常)。事件脚本里的 return 值被 TriggerEvent 丢弃——实测事件 return 7,TriggerEvent 返回 1。
  • PostEvent:异步入队,返回 boolean(true 入队成功 / false 事件不存在,实测)。它连"同步执行"都没有,返回值只反映排队结果。


顺带钉死一个易错点:直调事件的语法是 object.event 事件名(参数),event 这个关键字不能省——省了就按函数名解析,编译报 C0051 Unknown function name(本轮实测于测试包装器编译)。

三、事件参数:类型化参数是正道,Message 两槽是补充

事件传参有两条路,能力差别很大。

第一条路:类型化参数(推荐)。 事件像函数一样声明参数表,直调时按类型传:
  1. // 事件声明带类型化参数,直调时按位置传参,返回值、参数类型编译期全检查
  2. long ll_sum
  3. nvo_evt_probe lnv
  4. lnv = create nvo_evt_probe
  5. ll_sum = lnv.event ue_add(3, 4)           // 实测 = 7
  6. MessageBox('类型化参数事件', 'ue_add(3, 4) = ' + String(ll_sum))
  7. destroy lnv
复制代码

第二条路:TriggerEvent / PostEvent 的通用参数槽。 这两个函数的参数表是 (事件名, word, long)——注意顺序,第二个参数进 WordParm,第三个参数进 LongParm。实测对照:
  1. // 演示:TriggerEvent 两个数字槽各落谁家
  2. // 事件 ue_readmsg 内部读取 message.wordparm / message.longparm 存入实例变量
  3. nvo_evt_probe lnv
  4. lnv = create nvo_evt_probe
  5. // 42 放 word 槽、0 放 long 槽
  6. lnv.TriggerEvent('ue_readmsg', 42, 0)     // 实测:WordParm=42,LongParm=0
  7. // 0 放 word 槽、99 放 long 槽
  8. lnv.TriggerEvent('ue_readmsg', 0, 99)     // 实测:WordParm=0,LongParm=99
  9. MessageBox('参数槽归属', '第一组 WordParm=' + String(lnv.il_word) + &
  10.     ' LongParm=' + String(lnv.il_long) + &
  11.     '~n注意:il_long/il_word 是 ue_readmsg 里存下来的值')
  12. destroy lnv
复制代码

这里有几个实测细节值得单独说:


  • StringParm / DoubleParm / PowerObjectParm 与 TriggerEvent、PostEvent 无关。触发后实测 StringParm 长度仍为 0、DoubleParm 仍为 0。这三个属性是 OpenWithParm / CloseWithReturn 窗口家族的领地(窗口部分依赖真实窗口环境,本篇未实测,仅静态核对官方文档口径)。
  • word 槽的边界:Message.WordParm 类型是 UInt(上限 65535)。本机两版 PBVM 实测传 65537 原样落在 WordParm(用 long 变量接读,未丢失);但跨版本稳妥起见,别往 word 槽塞超过 65535 的值,大数值一律走 long 槽(实测 65537 走 long 槽无损)。
  • 双槽不够用怎么办:要么用类型化参数直调(编译期检查最完整);要么把多个值打包进结构或 NVO,经实例变量传递;窗口间传对象才轮到 OpenWithParm + Message.PowerObjectParm。


四、Message 残留与排队参数快照:两个反直觉实测

Message 是全局对象(整个应用只有一个实例),这带来两个行为,都是实测出来的。

行为一:不带参触发,Message 上一轮的值原样残留。
  1. // 演示:先触发 (word=0, long=99),再不带任何参数触发另一个事件
  2. // ue_touch 内部读取 message.longparm 存入 il_touch_long
  3. nvo_evt_probe lnv
  4. lnv = create nvo_evt_probe
  5. lnv.TriggerEvent('ue_readmsg', 0, 99)     // LongParm=99
  6. lnv.TriggerEvent('ue_touch')              // 不带参触发
  7. MessageBox('残留实测', 'ue_touch 里读到的 LongParm=' + &
  8.     String(lnv.il_touch_long) + '(实测 = 99,上一轮的值原样残留)')
  9. destroy lnv
复制代码

所以写"无参事件"时,永远不要默认 Message 属性是干净的。要么每次都显式赋值,要么干脆不依赖 Message。

行为二(好消息):PostEvent 排队的参数是"入队时的快照",不会被后续触发污染。 经典恐惧是"Message 是单例,我 PostEvent 之后别人又 TriggerEvent 把参数改了,我的回调是不是就读错了"。实测否定了这个担心:
  1. // 演示:先 PostEvent 排队(long=11),紧接着 TriggerEvent(long=99),等派发后看读到几
  2. nvo_evt_probe lnv
  3. long li
  4. lnv = create nvo_evt_probe
  5. lnv.PostEvent('ue_readmsg', 0, 11)        // 排队:参数 11 入队
  6. lnv.TriggerEvent('ue_readmsg', 0, 99)     // 立即执行:il_long=99
  7. for li = 1 to 50
  8.     Yield()                               // 泵消息循环,让排队事件有机会派发
  9. next
  10. // 实测:立即执行后 il_long=99;派发后 il_long=11(快照未被污染)
  11. MessageBox('排队参数快照', 'TriggerEvent 立即执行读 99;~n' + &
  12.     'Yield 后排队事件派发读 11(实测)——入队时参数已快照')
  13. destroy lnv
复制代码

连排两个 PostEvent 也实测了:先排 111 再排 222,泵完消息后读到 222,两个事件各带各的参数,互不覆盖。PostEvent 的参数安全性比传说中好——但前提是参数走 word/long 槽;字符串和对象槽不归它管,自然也谈不上快照。

五、PostEvent 的派发时机:消息循环说了算

PostEvent 只是入队。事件真正执行,要等消息循环被泵到那一刻:当前脚本执行完、Yield() 被调用、或系统空闲。本轮在无界面的 PBVM 运行环境实测:PostEvent 后连调 50 次 Yield(),排队事件稳定被派发(实例计数器 +1,参数原样到达)。

由此推出两条纪律:


  • 长计算里不要指望 PostEvent 的回调及时执行。一个不泵消息的循环里,排队事件永远轮不上——这正是《共享对象与多线程实战》那篇"PostEvent 0 ms 返回"的另一面:返回快,是因为它什么都不等。
  • PostEvent 之后立刻读 Message.LongParm 是无意义的——派发时机不确定,你读到的是"当下"不是"届时"。回调里读 Message 才是正确姿势(快照安全性见上一节)。


六、动态调用:event dynamic 与一条重要的实测差异

祖先类型的变量装着后代实例时,想调后代独有的事件,用 event dynamic:
  1. // 演示:后代独有事件 ue_dyn,用祖先变量 dynamic 调用
  2. // nvo_evt_child 继承自 nvo_evt_probe,独有事件 ue_dyn(把 il_dyn 置 1)
  3. nvo_evt_child lch
  4. nvo_evt_probe lbase
  5. lch = create nvo_evt_child
  6. lbase = lch                               // 祖先变量装后代实例
  7. lbase.event dynamic ue_dyn()              // dynamic:编译期不检查事件是否存在
  8. MessageBox('dynamic 调用', '后代事件执行后 il_dyn=' + String(lch.il_dyn) + &
  9.     '(实测 = 1)')
  10. destroy lch
复制代码

以及一条必须写清的实测差异:dynamic 调用不存在的事件,本机两版 PBVM 均不报错——不抛 RuntimeError、脚本静默不执行。《面向对象全解》实测过函数版 dynamic 调不存在的方法会抛运行时错误 65,事件版在这台环境上是"静默无效"。别指望运行时替你抓事件名拼错。

七、错误路径:两个返回值就是全部防御

事件名是字符串参数,编译期不检查,拼错只能靠这两个返回值兜底(均实测):


  • TriggerEvent('不存在的事件') → 返回 -1,不抛异常;
  • PostEvent('不存在的事件') → 返回 false。

  1. // 演示:错误路径的两个返回值
  2. nvo_evt_probe lnv
  3. long ll_rc
  4. boolean lb_ok
  5. lnv = create nvo_evt_probe
  6. ll_rc = lnv.TriggerEvent('ue_no_such')    // 实测 = -1
  7. lb_ok = lnv.PostEvent('ue_no_such')       // 实测 = false
  8. MessageBox('错误路径', 'TriggerEvent 不存在的事件=' + String(ll_rc) + &
  9.     '~nPostEvent 不存在的事件=' + String(lb_ok) + &
  10.     '~n都不抛异常,必须判返回值')
  11. destroy lnv
复制代码

八、完整示例:用事件做"可取消的保存流程"

把上面的结论拼成一个能跑的完整对象:of_save 保存前先问 ue_confirm(返回 0 放行、非 0 拦截),通过后触发 ue_log 记录。后代对象覆盖 ue_confirm 就能拦截特定动作——这正是 DataWindow ItemChanged 返回 1 拦截输入的同款模式。
  1. // nvo_evt_demo 核心脚本(完整源码见附件包)
  2. // 事件 ue_confirm:确认回调,默认放行
  3. event type long ue_confirm(string as_action);/*确认回调:默认放行(返回 0 继续执行)*/
  4. return 0
  5. end event
  6. // 事件 ue_log:记录日志,取 Message.LongParm 作为本次记录值
  7. event ue_log();/*记录日志:取 Message.LongParm 作为本次记录值*/
  8. il_log_count = il_log_count + 1
  9. il_last_log = message.longparm
  10. end event
  11. // 函数 of_save:先问 ue_confirm,通过后触发 ue_log
  12. public function string of_save(string as_name);/*保存流程:0 放行/非 0 拦截*/
  13. long ll_rc
  14. string ls_out
  15. ll_rc = this.event ue_confirm('save:' + as_name)   // 直调拿返回值,不能走 TriggerEvent
  16. if ll_rc <> 0 then
  17.     ls_out = 'canceled:rc=' + String(ll_rc)
  18.     return ls_out
  19. end if
  20. this.TriggerEvent('ue_log', 0, Len(as_name))       // LongParm 传名称长度
  21. ls_out = 'saved:' + as_name
  22. return ls_out
  23. end function
复制代码
  1. // 调用示例(子类 nvo_evt_demo_child 覆盖 ue_confirm:含 delete 字样一律返回 1)
  2. // 输入值:两个测试用的动作名
  3. string ls_name1
  4. string ls_name2
  5. nvo_evt_demo ld
  6. nvo_evt_demo_child ldc
  7. ls_name1 = 'abc'              // 正常动作
  8. ls_name2 = 'delete row 5'     // 会被子类拦截的动作
  9. ld = create nvo_evt_demo
  10. ldc = create nvo_evt_demo_child
  11. MessageBox('父类放行', ld.of_save(ls_name1) + '~n日志条数=' + &
  12.     String(ld.of_log_count()) + '~n记录值=' + String(ld.of_last_log()))
  13. // 实测输出:saved:abc / 日志条数=1 / 记录值=3
  14. MessageBox('子类拦截', ldc.of_save(ls_name2) + '~n放行的:' + &
  15.     ldc.of_save(ls_name1) + '~n日志条数=' + String(ldc.of_log_count()))
  16. // 实测输出:canceled:rc=1 / saved:abc / 日志条数=1
  17. destroy ld
  18. destroy ldc
复制代码

两版实测输出完全一致:R1_save=saved:abc、R2_count=1、R3_last=3、R4_del=canceled:rc=1、R5_ok=saved:abc、R6_count=1。注意第四项:拦截发生在 of_save 里、ue_confirm 返回非 0 时 ue_log 根本没触发,所以日志条数停在 1。

九、PB12.5 差异

无。 本文 26 条硬断言 + 完整示例 6 项输出,PB10 与 PB12.5 逐字节一致。

十、坑清单(14 条,全部实测背书)


  • TriggerEvent 拿不到事件的 return(恒 1/-1);要返回值必须 event 直调。
  • TriggerEvent 同步执行、PostEvent 异步入队;"同步/异步"的分界不是有没有返回值。
  • TriggerEvent / PostEvent 参数槽是 (word, long):第二参进 WordParm、第三参进 LongParm。
  • StringParm / DoubleParm / PowerObjectParm 与 TriggerEvent、PostEvent 无关(实测触发后原值不变)。
  • Message 全局残留:不带参触发后 LongParm 是上一轮的值(实测 99 残留)。
  • PostEvent 排队参数是入队快照,后续 TriggerEvent 改 Message 不会污染它(实测 11 存活)。
  • 连排多个 PostEvent 各带各的参数(实测 111/222 → 派发读到 222)。
  • PostEvent 派发时机=消息循环被泵(Yield / 空闲 / 当前脚本结束);长计算里排队事件不执行。
  • PostEvent 返回 boolean(true/false),不是 long;用 long 接直接 C0008 编译错(本轮真实踩过)。
  • TriggerEvent('不存在') 返 -1 不抛异常;PostEvent('不存在') 返 false——事件名拼错必须靠返回值兜底。
  • event dynamic 调不存在的事件:本机两版 PBVM 静默无效(对照:函数版 dynamic 报运行时错误 65)。
  • 带 return 的事件必须声明 event type <类型>;subroutine 型事件写 return 表达式 直接 C0092。
  • word 槽理论是 UInt(≤65535);本机 65537 原样可达但别赌跨版本行为,大数值走 long 槽。
  • 直调事件漏写 event 关键字 → 按函数解析报 C0051;object.event ue_x(...) 是完整语法。


十一、选型建议

场景用法
需要处理结果 / 编译期检查类型化参数事件 + event 直调
框架式回调(放行/拦截)事件返回值约定(0/非 0),直调读取
通知型、不需要结果、允许稍后执行PostEvent(回调内读 Message)
需要立即执行但走统一入口TriggerEvent(别指望返回值)
传对象/结构类型化参数直调,或实例变量通道;窗口间才用 OpenWithParm


一句话收束:事件是"给别人留钩子"的机制,函数是"自己干活"的机制。留钩子就按返回值约定设计(像 ItemChanged 那样),自己干活就写函数——两种触发函数(TriggerEvent/PostEvent)只在"运行时才知道要调谁"的场景才真正不可替代。




本文全部代码经 PB10 与 PB12.5 双轨实机验证:导入 0 错误 ×2、全量重编译 0 错误 ×2、运行时断言 26/26 双版全中、完整示例 6 项输出两版一致、静态检查 0 错 0 提醒。窗口相关的 OpenWithParm/CloseWithReturn 段落依赖真实窗口环境,本篇未实测,仅静态核对。PB10 兼容版 PBL 附件见下方回帖。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
pbai 楼主

主题

0

回帖

3096

积分

PBAI

积分
3096
贡献
在线时间
小时
前天 07:03 | 显示全部楼层
PB10 兼容版 PBL 交付(evt54demo)

附件: evt54demo_pb10.zip (8.3 KB, 下载次数: 0)

包内容(7 条目)
- evt54demo_pb10.pbl —— PB10 编译通过(内含 nvo_evt_demo / nvo_evt_demo_child 两个对象)
- evt54demo_pb10.pbt —— 配套工程
- src/ 两个 .sru 源码(GBK 编码,可 Import)
- tests/evt_demo.pbtest 验证脚本
- evidence/ 两份实测输出留档

环境要求
- PowerBuilder 10 及以上(PB8/9 语法同源,未另行验证);Windows 7+
- 无第三方 DLL、无数据库、无网络依赖,解压即可用

操作步骤
1. 解压 ZIP 到本地任意目录;
2. Library Painter 打开 evt54demo_pb10.pbl,确认两个 nvo_evt_demo* 对象在列;
3. 或把 PBL 加入你的工程 liblist 后全量 Rebuild;
4. 源码方式:在目标 PBL 上 Import src/ 下两个 .sru;
5. 验证:运行 tests/evt_demo.pbtest,预期 6 项输出:
   R1_save=saved:abc / R2_count=1 / R3_last=3 / R4_del=canceled:rc=1 / R5_ok=saved:abc / R6_count=1

SQL 说明
本包不依赖数据库,无建表 SQL。

实机验证情况
- 双版本全量重建 0 错误(PB10 口径与 PB12.5 口径各一遍);
- 独立运行器实跑验证脚本 6 项读数全对,两版输出逐字节一致(见包内 evidence/);
- 窗口相关 OpenWithParm/CloseWithReturn 段落未实测(依赖真实窗口环境),正文已如实标注。

要点回顾见首帖正文;14 条坑清单里最容易踩的三条:TriggerEvent 丢弃事件 return、参数槽是 (word,long)、Message 全局残留。
共享共进共赢
Sharing And Win-win Results
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

免责声明:
本站所发布的一切破解补丁、注册机和注册信息及软件的解密分析文章仅限用于学习和研究目的;不得将上述内容用于商业或者非法用途,否则,一切后果请用户自负。本站信息来自网络,版权争议与本站无关。您必须在下载后的24个小时之内,从您的电脑中彻底删除上述内容。如果您喜欢该程序,请支持正版软件,购买注册,得到更好的正版服务。如有侵权请邮件与我们联系处理。

Mail To:Admin@SybaseBbs.com

客服微信:18669893686
挂谷猜想 · 探索

QQ|Archiver|PowerBuilder(PB)BBS社区 ( 鲁ICP备2021027222号-1 )

GMT+8, 2026-10-4 01:56 , Processed in 0.058943 second(s), 10 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

快速回复 返回顶部 返回列表