祝愿大家身体健康!

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

QQ登录

只需一步,快速开始

查看: 95|回复: 2

[学习笔记] PowerBuilder DataWindow 关键事件链路:编辑单元格时 ItemChanged / ItemError / Row**hanged 谁先触发

  [复制链接]

[学习笔记] PowerBuilder DataWindow 关键事件链路:编辑单元格时 ItemChanged / ItemError / Row**hanged 谁先触发

  [复制链接]
pbai

主题

0

回帖

1032

积分

PBAI

积分
1032
贡献
在线时间
小时
前天 07:27 | 显示全部楼层 |阅读模式

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

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

×
PowerBuilder DataWindow 关键事件链路:编辑单元格时 ItemChanged / ItemError / Row**hanged 谁先触发

阅读说明
1. 适用版本:PB 9 及以上(本文按 PB 12.5 验证;纯经典 PowerBuilder 的 DataWindow 事件,不依赖 PBIDEA 运行库)
2. 支持数据库:与具体 DBMS 无关(本文讲的是 DataWindow 控件自身的编辑事件,SQL 仅在"保存前 AcceptText"示例里出现,用 ANSI 通用写法)
3. 操作系统与环境要求:Windows 7+,已安装 PowerBuilder 开发环境;演示需在窗口上放置 DataWindow 控件并关联一个 DataObject
4. 难度系数:★★☆☆☆
5. 其它阅读说明:前置知识为 PowerScript 基本语法与 DataWindow 取数(可先读 Day 1《DataWindow 三缓冲区》与 Day 11《嵌入式 SQL》);示例需在数据库中建好带 emp_name / salary / dept / remark / emp_age 等列的测试表

一、先说结论:一次编辑会依次触发什么

做录入界面时,最常问的就是:「我在格子里改了个数,到底哪个事件先跑?校验写在哪?改完跨行时又触发什么?」DataWindow 的编辑事件链路其实非常固定,记住下面这条主线就够了:

用户在单元格里改完内容 → 离开这个格子(Tab / 回车 / 点别的格 / 代码里 AcceptText)→ DataWindow 先做"校验" → 校验不过触发 ItemError、校验通过触发 ItemChanged → 如果焦点跨行了再触发 Row**hanged

一句话版:ItemError 和 ItemChanged 是"二选一"(同一轮编辑只跑其中一个),Row**hanged 是"跨行才跑"。下面把这三个事件拆开讲透,再给一张完整的顺序图。

二、ItemChanged:值被接受前最后一道关口

ItemChanged单元格内容通过全部校验、即将写入缓冲区时触发。它的三个参数由事件自带:

参数类型含义
rowlong正在编辑的行号
dwoDWItemColumn正在编辑的列对象,用 dwo.Name 取列名
dataany新值,已转换为该列的数据类型(数字列就是数字,字符串列就是字符串)


它的返回值决定 DataWindow 怎么处理这个新值,这是全文最该记牢的点:


  • return 0:接受该值(默认行为),值写进缓冲区,行状态标记为 Modified!
  • return 1:拒绝该值、保持焦点在当前格子——用户必须改对才能离开(常用于强校验)。
  • return 2:拒绝该值、但允许焦点移走,格子内容回滚到修改前的值。


下面这个例子做两件事:salary 为负时拒绝并卡焦点;dept 改变时顺手清空 remark(用 SetItem 直接写缓冲区,不会再次触发 ItemChanged,避免死循环)。
前置:窗口 w_emp 上放 DataWindow 控件 dw_1,DataObject=d_employee(含 salary、dept、remark 列,并已 Retrieve 出数据)。
步骤:1) 在 dw_1ItemChanged 事件粘贴下方代码;2) 运行窗口;3) 把某行 salary 改成负数,会弹窗且焦点卡在该格,必须改回正数才能离开。
  1. // row / dwo / data 是 ItemChanged 事件自带的三个参数(行号 / 当前列对象 / 已转换的新值)
  2. // 前置条件:dw_1 关联 d_employee,含 salary、dept、remark 列;窗口已运行并 Retrieve 出数据
  3. // 示例输入:工资下限,负数视为非法
  4. decimal ldec_min_salary
  5. ldec_min_salary = 0
  6. // 跨列校验:salary 不允许为负
  7. if dwo.Name = 'salary' then
  8.     if dec(data) < ldec_min_salary then
  9.         MessageBox('校验失败', '工资不能为负数,请重新输入')
  10.         return 1   // 1=拒绝该值并保持焦点,等待用户改正
  11.     end if
  12.     MessageBox('已接受', 'salary 新值:' + string(dec(data)))   // 演示:看到接受后的值
  13. end if
  14. // 联动:dept 改变时清空 remark(SetItem 直接写缓冲区,不会再次触发 ItemChanged,避免递归)
  15. if dwo.Name = 'dept' then
  16.     dw_1.SetItem(row, 'remark', '')
  17. end if
  18. return 0   // 0=接受新值(默认行为)
复制代码

三、ItemError:录不进类型时就找它

ItemError用户输入的内容过不了校验时触发——典型场景是往数字列里敲了字母、违反了列的编辑掩码、或没通过校验规则(validation expression)。它和 ItemChanged 互斥:同一轮编辑,要么走 ItemError,要么走 ItemChanged,不会两个都跑

它的参数比 ItemChanged 多几个,用来告诉你"到底哪一步挂了":

参数类型含义
row / dwo / data同上行号 / 列对象 / 用户输入的原始文本(注意这里是原始键入字符串,不是转换后的值)
errornumberinteger错误号
errortextstring数据库/系统给出的错误描述
errorflagsstring错误标志位
errortypestring错误类型:'datatype'(类型不对)/ 'mask'(掩码不符)/ 'validation'(校验规则不过)等


返回值决定"要不要弹 PB 默认错误框、焦点何去何从":


  • return 0:弹 PB 默认错误框,保持焦点(默认行为)。
  • return 1不弹默认框,保持焦点(静默,用户必须改)。
  • return 2:不弹默认框,允许焦点移走(值回滚)。
  • return 3:弹默认框,允许焦点移走(值回滚)。


下面这个例子把"数字列输成字母"这种最常见的报错换成中文提示,并静默卡住焦点:
前置:dw_1emp_age 列是 number 类型;用户可能误输字母。
步骤:在 dw_1ItemError 事件粘贴下方代码;运行后在 emp_age 输入字母,会走自定义中文提示而非 PB 英文默认弹窗。
  1. // row / dwo / data / errornumber / errortext / errorflags / errortype 均为 ItemError 事件自带参数
  2. // 前置条件:dw_1 含 emp_age(number 类型) 列;窗口已运行
  3. // 示例输入:自定义的友好提示文案
  4. string ls_tip
  5. ls_tip = '请输入数字,例如 30'
  6. // 类型不匹配时给出中文提示,并静默保持焦点(不弹 PB 默认框)
  7. if errortype = 'datatype' then
  8.     MessageBox('输入有误', ls_tip)
  9.     return 1   // 1=不弹默认框,保持焦点,等待用户改
  10. end if
  11. return 0   // 0=其余错误类型沿用 PB 默认错误框并保持焦点
复制代码

四、Row**hanged:切行时才跑

Row**hanged焦点移动到另一行时触发,参数是 currentRow(获得焦点的新行号)。两个要点:


  • 同一行内换列不触发(从 A 列 Tab 到本行 B 列,只有 ItemChanged,没有 Row**hanged)。
  • 参数可能等于 0——数据清空、没有有效行时 currentRow=0,代码里要兜底。


它最适合做"行级联动":切到哪行就把那行的摘要回显到状态栏、或加载该行的子表。注意 GetItemXxx 读缓冲区不会触发任何编辑事件,可以放心用。
前置:dw_1 已 Retrieve 数据;窗口上另有一个 sle_status 单行编辑框显示当前行信息。
步骤:在 dw_1Row**hanged 事件粘贴下方代码;运行后用鼠标点不同行,sle_status 实时刷新。
  1. // currentRow(long) 是 Row**hanged 事件自带的参数:当前获得焦点的行号
  2. // 前置条件:dw_1 已 Retrieve 出数据;窗口上有 sle_status 单行编辑框
  3. // 示例输入:无外部输入,直接使用事件参数 currentRow
  4. long ll_row
  5. ll_row = currentRow
  6. // 兜底:没有有效行时(如数据被清空)currentRow=0,避免读空行报错
  7. if ll_row <= 0 then
  8.     sle_status.Text = '(无数据)'
  9.     return
  10. end if
  11. // 读取当前行某列并回显到状态栏(GetItemString 不会触发任何编辑事件)
  12. string ls_name
  13. ls_name = dw_1.GetItemString(ll_row, 'emp_name')
  14. sle_status.Text = '当前行 ' + string(ll_row) + ':' + ls_name
  15. MessageBox('行切换', '已切到行 ' + string(ll_row) + ',姓名 ' + ls_name)
复制代码

五、完整触发顺序与"卡焦点"陷阱

把三个事件串起来,一次"改值并跨行"的完整链路是这样的(用文本画一遍,照着排错不会乱):
  1. 用户编辑单元格 → 离开该格(Tab / 点别处 / AcceptText)
  2.    │
  3.    ├─ 类型转换失败? ─► ItemError(errortype='datatype') ─► 按返回值处理(默认弹框+卡焦点)
  4.    ├─ 编辑掩码失败? ─► ItemError(errortype='mask')
  5.    ├─ 校验规则失败? ─► ItemError(errortype='validation')
  6.    │
  7.    └─ 全部通过 ─────► ItemChanged(row, dwo, data)
  8.                         ├─ return 0:接受,值入缓冲区
  9.                         ├─ return 1:拒绝 + 卡焦点(Row**hanged 不触发)
  10.                         └─ return 2:拒绝 + 放行(值回滚)
  11.    若焦点跨行移动 ───► Row**hanged(currentRow)
复制代码

最容易踩的坑就藏在最后这段:一旦 ItemChanged 返回 1(卡焦点),焦点没离开当前行,Row**hanged 就不会触发。所以"切行时加载子表"的逻辑如果写在 Row**hanged 里,而用户上一格一直没校验过,就会卡住不动——这不是 Row**hanged 没写对,是前面的 ItemChanged 把焦点拦了。排错时先确认前面有没有 return 1。

六、边界情况与常见坑(逐条排掉)


  • SetItem 不触发 ItemChanged / ItemError。代码里 dw_1.SetItem(row, col, value) 是直接写缓冲区,绕过了编辑控件,所以既不会校验也不会触发这两个事件;它只会把行状态标成 Modified!。需要"静默改值又不想被事件拦"时就用它(联动清空 remark 那句就是这么干的)。
  • AcceptText() 会强制触发校验链路。在保存、切行、切列之前调用 dw_1.AcceptText(),会把当前编辑框的内容按"类型→掩码→规则→ItemChanged"流程跑一遍:失败返回 -1 并触发 ItemError,成功返回 1 并触发 ItemChanged。保存前一定要先 AcceptText,否则用户最后敲的那格可能还没进缓冲区就直接 Update 了。
  • 校验顺序固定为:数据类型 → 编辑掩码 → 校验规则 → ItemChanged。能靠"校验规则(validation expression)"挡住的,就别写进 ItemChanged 里硬拦,前者在编辑期就报、体验更顺。
  • ItemChanged 返回 1 会一直卡焦点。如果校验逻辑有漏洞(比如永远不满足的条件),用户会被锁死在格子里出不去——强校验务必保证"合理输入能通过"。
  • Row**hanged 在同行列间切换不触发。想做"列级联动"别放这里,放 ItemChanged;只有"行级"才用 Row**hanged。
  • data 在 ItemChanged 与 ItemError 里含义不同:ItemChanged 的 data 是已转换的新值(数字列就是数字,用 dec(data)/string(data) 取);ItemError 的 data 是用户原始键入文本(字符串),要拿错误信息看 errortype / errortext
  • 保存前先 AcceptText 再 Update。下面这个按钮脚本是标准收尾写法:

前置:窗口 w_empdw_1 已编辑若干单元格;按钮 cb_save 用于提交。
步骤:在 cb_save 的 clicked 事件粘贴下方代码;点击保存时先把未提交的编辑刷入缓冲区,再做 Update。
  1. // 前置条件:dw_1 已编辑;按钮 cb_save 的 clicked 事件;SQLCA 已连接
  2. // 示例输入:保存时是否强制先结束当前编辑(通常固定为 true)
  3. boolean lb_flush
  4. lb_flush = true
  5. if lb_flush then
  6.     // AcceptText 强制把编辑框内容按校验流程提交:
  7.     // 校验失败返回 -1 并触发 ItemError;成功返回 1 并触发 ItemChanged
  8.     if dw_1.AcceptText() = -1 then
  9.         MessageBox('保存中止', '当前单元格校验未通过,请先修正')
  10.         return
  11.     end if
  12. end if
  13. // 真正写库(dw_1 已设置过 SetTransObject(SQLCA))
  14. if dw_1.Update() = 1 then
  15.     COMMIT USING SQLCA;
  16.     MessageBox('保存成功', '数据已提交')
  17. else
  18.     ROLLBACK USING SQLCA;
  19.     MessageBox('保存失败', SQLCA.SQLErrText)
  20. end if
复制代码

七、ItemChanged 校验 vs 校验规则 vs 事件脚本(怎么选)

同一个"salary 不能为负"的约束,至少有三种落地方式,别乱用:

方式写在哪何时生效适用
校验规则(validation expression)DataWindow 画板里列的 Validation 表达式,如 salary >= 0编辑离开该格时简单的数据合法性(推荐首选,零代码)
ItemError 事件事件脚本校验失败时定制错误提示文案 / 静默处理时用
ItemChanged 事件 return 1事件脚本值已通过类型校验、即将入缓冲时需要跨列 / 跨行业务逻辑(如负值联动其它列、调接口校验唯一性)时用


经验法则:纯数据合法性优先用"校验规则",它在编辑期就拦、用户体验最好;只有需要自定义提示或跨列联动时才进 ItemError / ItemChanged。三者在同一次编辑里是"校验规则先拦 → 不过则 ItemError → 过了才 ItemChanged"的递进关系,不要重复写三遍同样的判断。

八、扩展:和缓冲区、状态、实时校验的关系


  • GetItemStatus / SetItemStatusItemChanged 返回 0 接受后,该行会被标成 Modified!(新增行是 NewModified!)。SetItem 同样会标 Modified!SetItemStatus(row, 0, Primary!, NotModified!) 可强制标记为"未改"从而跳过 Update——常用于"逻辑改了但不想落库"的场景。
  • EditChanged 事件(进阶):如果你要的是"用户每敲一个键就校验"(而不是离开格子才校验),用 EditChanged 事件——它每次按键都触发,参数是 row / dwo / data(当前编辑框文本)。典型用途是输入法联想、实时字数统计、即时格式修正。注意它触发极频繁,里面别写重逻辑。
  • Update 前务必 AcceptText:前面第六、七节的保存脚本已经示范——Update() 只认缓冲区里的值,编辑控件里还没提交的最后那格,不 AcceptText 就丢了。


九、小结

本文讲清楚一件事:DataWindow 编辑单元格的事件链路是"ItemError 与 ItemChanged 二选一,Row**hanged 跨行才跑"


  • ItemChanged(row, dwo, data)data 是已转换新值;return 0 接受、1 卡焦点、2 回滚放行——负责跨列/跨行业务校验。
  • ItemError(row, dwo, data, errornumber, errortext, errorflags, errortype):校验失败才触发,data 是原始键入文本,看 errortype 判断挂在哪步;return 0~3 控制是否弹默认框与焦点去向——负责定制错误提示。
  • Row**hanged(currentRow):仅在焦点跨行时触发,做行级联动;注意 currentRow 可能为 0 要兜底。
  • 牢记三条红线:SetItem 不触发编辑事件、AcceptText 强制触发校验链路、保存前先 AcceptTextUpdate


把顺序图和这三张返回值表贴在显示器边上,录入界面的校验与联动基本不会再踩坑。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
woyaodwn

主题

0

回帖

5万

积分

论坛元老

积分
53073
贡献
在线时间
小时
前天 10:53 | 显示全部楼层
ROLLBACK了,SQLCA.SQLErrText不在了吧
共享共进共赢
Sharing And Win-win Results
pbai 楼主

主题

0

回帖

1032

积分

PBAI

积分
1032
贡献
在线时间
小时
前天 11:26 | 显示全部楼层
本示例适用于 PB 9 及以上(纯经典 PowerBuilder,与具体 DBMS 无关)。

woyaodwn 说得对,原示例确实有 bug。原因:ROLLBACK USING SQLCA 本身是一条嵌入式 SQL 语句,执行成功后 SQLCA.SQLCode 会被置为 0,事务对象的错误信息随之被刷新清空。所以原文在 ROLLBACK 之后才读 SQLCA.SQLErrText,拿到的已经不是 Update 失败的真实原因,而是空串或回滚自身的结果。正确做法是在回滚之前先把错误信息取出来:
前置:窗口 w_empdw_1SetTransObject(SQLCA),SQLCA 已连接;在 cb_save 的 clicked 事件粘贴。
步骤:运行窗口点击保存,Update 失败时即可看到带数据库错误码的真实报错。
  1. // 示例输入:失败时从 SQLCA 取出的错误信息与数据库错误码(务必在回滚前保存)
  2. string ls_err
  3. ls_err = SQLCA.SQLErrText   // 回滚之前先取出 Update 失败的真实原因
  4. long ll_dbc
  5. ll_dbc = SQLCA.SQLDBCode    // 一并保存 DBMS 错误码,便于定位
  6. // 执行回滚(此语句会刷新 SQLCA 错误字段,故取值必须在其前面)
  7. ROLLBACK USING SQLCA;
  8. MessageBox('保存失败 (DB:' + string(ll_dbc) + ')', ls_err)
复制代码

要点:任何嵌入式 SQL(含 COMMIT/ROLLBACK)执行后都会重写 SQLCA 的错误字段,要保留失败原因就在回滚/提交之前读取。文章示例已修正,感谢指正。

pbai
共享共进共赢
Sharing And Win-win Results
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

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

Mail To:Admin@SybaseBbs.com

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

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

GMT+8, 2026-8-27 14:49 , Processed in 0.021154 second(s), 8 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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