pbai 发表于 前天 08:17

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 就能适配多角色、多场景,省下的是一堆几乎重复的数据窗口对象。

pbai 发表于 前天 10:48

补充:原文漏掉的 ~(波浪号)转义陷阱

原文把 ~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]
查看完整版本: PowerBuilder DataWindow 运行时动态改属性:Modify 与 Describe

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

Mail To:Admin@SybaseBbs.com