PowerBuilder DataWindow 运行时动态改属性:Modify 与 Describe
PowerBuilder DataWindow 运行时动态改属性:Modify 与 Describe做业务系统时经常遇到这类需求:某个字段在 A 角色下只读、B 角色下可编辑;金额超过阈值的单元格要标红;某几列在导出模式下要临时隐藏。为每种情况各画一个 DataWindow 显然不现实。PowerBuilder 的办法是在运行时改属性——用 Modify() 写、用 Describe() 读。本文只讲清楚一件事:怎么用 Modify / Describe 在运行时安全地改列宽、颜色和可见性。
一、属性表达式:一切的基础
DataWindow 里的每个东西(列、文本、计算域、band)都有一串可读写的属性,寻址格式统一是 对象名.属性[.子属性]。常见几类:
写法含义
emp_name.Width列 emp_name 的宽度(单位 PBU)
emp_name.Visible是否可见,'0' 隐藏、'1' 显示
emp_name.Protect是否保护(只读),'1' 只读
emp_salary.Background.Color背景色(长整型数值的字符串)
#3.Name第 3 列的列名(#n 按列序号寻址)
DataWindow.Column.Count一共有多少列
DataWindow.Header.Color表头 band 的背景色
DataWindow.Detail.Height明细 band 的高度
记住两点就不会写错:属性名不区分大小写;赋的值要用单引号包起来,因为整条语法本身是一个字符串。
二、Modify:把属性写进去
Modify() 接收一条属性语法字符串,返回空串表示成功,返回非空就是出错信息。
string ls_syntax, ls_err
// 把 emp_name 列宽改成 600 PBU
ls_syntax = "emp_name.Width = '600'"
ls_err = dw_1.Modify(ls_syntax)
if ls_err <> "" then
MessageBox("改属性失败", ls_err)
end if
一定要接返回值。这是 Modify 和点号写法(dw_1.Object.emp_name.Width = 600)最大的区别:点号写法出错会触发 DataWindow 的 Error 事件甚至弹运行时错误,而 Modify 只是把错误当字符串还给你,逻辑上更好控制。
一次改多个属性,用换行符 ~n 拼起来即可,比连续调用几次更快:
string ls_syntax, ls_err
ls_syntax = "emp_name.Width = '600'~n" + &
"emp_name.Protect = '1'~n" + &
"emp_salary.Visible = '0'"
ls_err = dw_1.Modify(ls_syntax)
三、改颜色:值是数字,别忘了 RGB
颜色属性存的是长整型数值,用 RGB() 算出来再转成字符串拼进语法:
long ll_red
string ls_syntax, ls_err
ll_red = RGB(255, 0, 0)
ls_syntax = "emp_salary.Background.Color = '" + string(ll_red) + "'"
ls_err = dw_1.Modify(ls_syntax)
// 表头整条 band 换底色
ls_syntax = "DataWindow.Header.Color = '" + string(RGB(230, 240, 250)) + "'"
ls_err = dw_1.Modify(ls_syntax)
更有意思的是条件表达式:属性值不一定是常量,可以是一段 DataWindow 表达式,格式为 '默认值~t表达式',中间用制表符 ~t 隔开。下面这句让工资超过 5000 的行自动标红,且随数据变化实时生效,不用逐行循环:
string ls_syntax, ls_err
ls_syntax = "emp_salary.Background.Color = " + &
"'0~tif(emp_salary > 5000, RGB(255, 0, 0), RGB(255, 255, 255))'"
ls_err = dw_1.Modify(ls_syntax)
这是运行时动态改属性最值得掌握的一招:把「逐行判断」交给 DataWindow 引擎,代码量和刷新性能都远好过自己遍历。
四、Describe:先读再改,才不会瞎改
Describe() 反过来读属性,返回字符串。两个特殊返回值必须认识:
[*]"?":属性不存在,或对这个对象不适用(列名写错时最常见)。
[*]"!":取到的值不一致(一次问了多个对象时会这样)。
string ls_width, ls_type, ls_coltype
ls_width = dw_1.Describe("emp_name.Width") // 例如 "600"
ls_type = dw_1.Describe("emp_name.Type") // "column"
ls_coltype = dw_1.Describe("emp_name.ColType") // 例如 "char(40)"
if ls_width = "?" then
MessageBox("提示", "没有这个对象或属性,检查列名拼写")
end if
配合 DataWindow.Column.Count 和 #n.Name,可以写出完全不依赖具体列名的通用代码。注意循环变量要先声明再用,PowerBuilder 的 for 不支持在语句里内联声明:
long ll_cols, i
string ls_name, ls_syntax, ls_err
ll_cols = long(dw_1.Describe("DataWindow.Column.Count"))
for i = 1 to ll_cols
ls_name = dw_1.Describe("#" + string(i) + ".Name")
// 把所有列统一改成只读
ls_syntax = ls_name + ".Protect = '1'"
ls_err = dw_1.Modify(ls_syntax)
next
想知道一个 DataWindow 上到底有哪些对象(含文本、计算域、线条),问 DataWindow.Objects,返回的是用制表符分隔的一长串名字:
string ls_objs
ls_objs = dw_1.Describe("DataWindow.Objects")
// 形如 "emp_id~temp_name~tt_title~tc_total"
五、一个可运行的完整例子:按角色切换只读
把下面代码放在窗口的 open 事件或权限初始化函数里,就能用一份 DataWindow 应付两种角色。改属性前后加 SetRedraw,避免逐条刷新造成的闪烁:
// 参数:ab_readonly = true 表示当前角色只读
boolean lb_readonly
long ll_cols, i
string ls_name, ls_syntax, ls_err, ls_protect, ls_bkcolor
lb_readonly = true
if lb_readonly then
ls_protect = "1"
ls_bkcolor = string(RGB(240, 240, 240))
else
ls_protect = "0"
ls_bkcolor = string(RGB(255, 255, 255))
end if
dw_1.SetRedraw(false)
ll_cols = long(dw_1.Describe("DataWindow.Column.Count"))
for i = 1 to ll_cols
ls_name = dw_1.Describe("#" + string(i) + ".Name")
if ls_name <> "?" then
ls_syntax = ls_name + ".Protect = '" + ls_protect + "'~n" + &
ls_name + ".Background.Color = '" + ls_bkcolor + "'"
ls_err = dw_1.Modify(ls_syntax)
if ls_err <> "" then
// 某些对象没有 Background 属性时会报错,记录后继续
ls_err = ""
end if
end if
next
// 只读模式顺手隐藏操作列
if lb_readonly then
ls_err = dw_1.Modify("b_delete.Visible = '0'")
end if
dw_1.SetRedraw(true)
六、几个容易踩的坑
[*]改动只在内存里:Modify 的效果只作用于当前运行的这个实例,程序关掉就没了,不会写回 PBL 里的 DataWindow 对象。要永久生效请在 DataWindow 画板里改。
[*]列名和对象名不是一回事:emp_name 是列,标签文字往往叫 emp_name_t。改标题颜色要改文本对象,不是改列。
[*]单引号必须成对:"emp_name.Width = 600" 少了单引号会返回错误信息。养成 ' + string(值) + ' 的拼法。
[*]~t 是分隔符不是装饰:条件表达式的 '默认值~t表达式' 里那个制表符不能省,省了整条语法就无效。
[*]批量改前先 SetRedraw(false):几十列逐条 Modify 会明显闪屏,包起来能快好几倍。
[*]返回值要判断:Describe 拿到 "?" 就说明寻址写错了,别把它当成正常值继续参与计算。
七、小结
运行时改属性其实就两个动作:Describe() 问清楚现状,Modify() 写下去并检查返回值。宽度、可见性、只读、颜色这些全都能改;遇到「按条件变色」这类需求,优先用 '默认值~t表达式' 交给 DataWindow 引擎判断,而不是自己逐行循环。把这两个函数用熟,一个 DataWindow 就能适配多角色、多场景,省下的是一堆几乎重复的数据窗口对象。 补充:原文漏掉的 ~(波浪号)转义陷阱
原文把 ~t、~n 当成「分隔符」讲了,但有一个更隐蔽、也更容易在生产环境翻车的点没展开:在 DataWindow 的属性表达式里,~ 本身就是转义字符,不是普通字符。
一、为什么需要 ~~
DataWindow 在解析 Modify/Describe 的语法字符串时,按如下规则解释 ~:
写法含义
~t制表符 Tab
~n换行
~r回车
~0空字符(Char(0))
~'一个单引号
~~一个字面波浪号
所以当你想在显示值里放一个真正的 ~(比如编号 id~001、提示「姓名~别名」、或 LIKE 里的转义),只写一个 ~,它会被当成转义序列的开头,后面的字符被「吃掉」,轻则显示乱码、重则整条语法失效。
反例:
// 本意:让文本对象显示 "id~001"
// 实际:~0 被当成空字符,结果只剩 "id" + 一个不可见字符,显示乱码
dw_1.Modify("t_title.Text = 'id~001'")
正例:
// DataWindow 层需要 ~~ 才表示一个字面 ~
dw_1.Modify("t_title.Text = 'id~~001'")
二、最坑的一层:PowerBuilder 字符串字面量也用 ~ 转义
很多新手在这里栽跟头。上面那行 Modify("...~~...") 之所以只写 ~~ 就够,是因为它写在 PowerBuilder 源码的字符串字面量里——而 PB 自己的字符串转义符也是 ~:
[*]PB 里 "~~" 这个字面量,运行时实际字符串是 一个 ~。
[*]但我们前面说 DataWindow 层需要 ~~(两个)才表示一个字面 ~。
于是,当你直接把 ~~ 写进 PB 源码的字符串字面量时,PB 编译器先把 ~~ 折成一个 ~ 再交给 DataWindow,DataWindow 拿到的是一个 ~,又会去当转义——结果还是错的。
正确写法要在 PB 源码里翻倍成四个波浪号:
// PB 字面量里 ~~~~(四个) → 运行时字符串含 ~~(两个) → DataWindow 解析为 ~(一个)
dw_1.Modify("t_title.Text = 'id~~~~001'")
// 最终 DataWindow 收到的表达式是'id~~001'→显示为id~001
记忆口诀:DataWindow 一层要 ~~,包进 PB 字符串字面量再翻倍成 ~~~~。
三、动态拼接时更要先「翻倍」
如果是从数据库 / 配置文件读出来的文本(里面可能含 ~),直接拼进 Modify 也会触发转义。稳妥做法是先把每个 ~ 替换成 ~~,再拼:
string ls_raw, ls_safe
long ll_pos
ls_raw = "A~B" // 来自外部,含一个波浪号
ls_safe = ls_raw
ll_pos = Pos(ls_safe, "~")
do while ll_pos > 0
// 把这一处 1 个字符替换成 2 个波浪号
ls_safe = Replace(ls_safe, ll_pos, 1, "~~")
// 跳过刚插入的 2 个,避免死循环
ll_pos = Pos(ls_safe, "~", ll_pos + 2)
loop
dw_1.Modify("t_tip.Text = '" + ls_safe + "'")
四、顺带:单引号也要转义
想在显示值里放单引号,DataWindow 层用 ~',同理包进 PB 字面量要写成 ~~':
// 显示it's ok(含一个单引号)
dw_1.Modify("t.Text = 'it~~'s ok'")
小结
原文讲了「分隔符 ~t/~n 怎么用」,补一句:~ 是 DataWindow 的转义符,字面波浪号要 ~~,写进 PB 字符串字面量要 ~~~~;动态内容先 ~~ 化再拼。 这一个小细节,能省掉一堆「本地能跑、上线显示乱码」的排查时间。
页:
[1]