祝愿大家身体健康!

 找回密码
 站点注册
查看: 156|回复: 1

[学习笔记] PowerBuilder 动态 DataWindow 全解:一条语法串建出内存表、列定义与 16 条实测坑(PB10 基准 · PB12.5 实测)

[复制链接][复制链接]
发表于 17 小时前 | 显示全部楼层 |阅读模式

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

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

×
PowerBuilder 动态 DataWindow 全解:一条语法串建出内存表、列定义与 16 条实测坑(PB10 基准 · PB12.5 实测)

阅读说明
1. 适用版本:基准版本 PB10,PB 12.5 双轨实测;两版编译 0 错、运行读数逐条比对见末节「实机验证情况」
2. 支持数据库:本文全程不连数据库 —— 语法串手写、数据在内存里造。需要从库里取数时再配 SetTransObject 加 Retrieve,那是另一条路
3. 操作系统与环境要求:Windows 7 及以上,PB 10 或更高版本;不需要额外运行库、不需要第三方 DLL
4. 难度系数:★★★☆☆(三星。要接受「DataWindow 的数据层与显示层是两套东西」这个前提,剩下的都是拼串)
5. 其它阅读说明:建议先看站内《PowerBuilder DataWindow 运行时动态改属性:Modify 与 Describe》,本文大量依赖 Describe 的属性名;只想「从 SQL 生成数据窗口」的请看《PowerBuilder SyntaxFromSQL 全解》,那一篇要连库,本篇不用

动态 DataWindow 这件事,PB 圈里一直有两种人:一种是把 .srd 拖进窗口、列在画笔里定死的人,另一种是被需求逼着在运行时拼语法串的人。后一种人通常会经历三个阶段——第一次觉得神奇,第二次被一堆 incorrect syntax 磨到怀疑人生,第三次把语法串写成模板函数,然后在生产上被一条烂串卡住进程。

这一篇只讲清楚一件事:一条手写的 DataWindow 语法串到底由哪些块组成,Create 之后这个对象能干什么、不能干什么。不涉及数据库取数,也不重复 SyntaxFromSQL 的活儿(那是「让 PB 帮你生成串」,本篇是「你自己拼串」)。

一、什么时候真的需要动态建表

三个场景,都绕不开:


  • 列不固定。用户在界面上勾选要看的字段,或者对接的接口每次返回的字段不一样。画笔里定死的 .srd 满足不了。
  • 把一段内存数据变成「表」。你想要的是排序、过滤、分组、导出 Excel、打印预览这些能力,但数据来自多个来源(接口、文本、计算结果),不想为它建临时表。
  • 临时汇总/对账。跑完一批计算,把结果摊成二维表格给人看,看完就丢。


这三种需求的共同点是:我不需要数据库,我只需要一个能装行、能按列取值的容器,而且它得自带 DataWindow 的那套本事。DataStore 就是干这个的,而它的 DataObject 可以不给、可以在运行时 Create。

二、语法串的四个块

一条能用的语法串,长度从一百多字符到几千字符不等,但结构永远是四段:

块写法作用不写会怎样
版本头release 10;告诉引擎按哪个版本的格式解析缺了直接失败或挂起
全局属性datawindow(units=0 processing=1 ...)单位、处理方式、颜色、打印参数可省一部分,引擎补默认值
数据定义table(column=(type=... name=... dbname=...) ...)决定这个表能存哪些列、什么类型缺 table 块 → 没有列,且实测会挂起
显示定义column(band=detail id=1 ... visible="1")、text(band=header ...)决定这些列长什么样、能不能看见数据照存照取,但 Visible=0,界面上什么都没有


第三段和第四段是两套东西,这是本篇最要紧的一句话。table 里的 column= 是「表结构」,column(...) 是「贴在报表上的控件」。前者决定 SetItem 能不能塞进去,后者决定人眼能不能看见。

顺带把一个常见误解拆掉:Describe('DataWindow.Column.Count') 数的是前者(表结构里的列),不是显示控件数。所以在只有 table 段的串上,它会老老实实返回列数,而 Visible 一律是 0。

三、最小可用:两列内存表

下面这段可以直接跑,逻辑就是我们后面所有示例的骨架。前置条件只有一条:把代码放进一个 nonvisualobject 的方法里(示例方法名 of_make),窗口按钮里调它,或者干脆在一个测试用的按钮里直接贴。
  1. // 目的:一条语法串建出两列内存表,插两行再回读
  2. string ls_syn, ls_out
  3. datastore lds
  4. long ll_rc
  5. // 第 1 步:拼语法串 —— table 段的 column 决定"能存什么"
  6. ls_syn = 'release 10; datawindow(units=0 processing=1) table('
  7. ls_syn = ls_syn + 'column=(type=char(20) updatewhereclause=yes name=emp_name dbname="emp_name") '
  8. ls_syn = ls_syn + 'column=(type=decimal(18) updatewhereclause=yes name=salary dbname="salary"))'
  9. // 第 2 步:建对象。Create 返回 1 才是成功
  10. lds = create datastore
  11. ll_rc = lds.Create(ls_syn)
  12. if ll_rc <> 1 then
  13.         destroy lds
  14.         MessageBox('建表失败', 'rc=' + String(ll_rc))
  15.         return
  16. end if
  17. // 第 3 步:当内存表用。InsertRow(0) 表示追加到末尾
  18. lds.InsertRow(0)
  19. lds.SetItem(1, 'emp_name', 'Zhang')
  20. lds.SetItem(1, 'salary', 12000)
  21. lds.InsertRow(0)
  22. lds.SetItem(2, 'emp_name', 'Li')
  23. lds.SetItem(2, 'salary', 9800)
  24. ls_out = 'cols=' + lds.Describe('DataWindow.Column.Count')
  25. ls_out = ls_out + ' rows=' + String(lds.RowCount())
  26. ls_out = ls_out + ' 第2行=' + lds.GetItemString(2, 'emp_name') + '/' + String(lds.GetItemNumber(2, 'salary'))
  27. destroy lds
  28. MessageBox('结果', ls_out)
复制代码

实测读数:rc=1 cols=2 rows=2 第2行=Li/9800。两版一致。

注意字符串的拼法:PB 的单引号字符串字面量不能跨行,写
  1. ls_syn = '第一段' &
  2.         '第二段'
复制代码

会直接编译报 C0031: Syntax error(我第一次就是这么写的,被 ORCA 当场打回)。要拼长串,只能像我上面那样一段一段 ls_syn = ls_syn + '...'。

四、能存什么:列类型实测清单

type= 后面能写什么,PB 的帮助文档写得比较散。我把常用类型逐个建了一遍(每个类型单独一个用例、单独一次运行,避免互相带偏),结果如下:

写法结果#1.ColType 回读
char(20)成功char(20)
number成功number
long成功long
decimal(18)成功decimal(18)
decimal(2)成功decimal(2)
date成功date
datetime成功datetime
time成功time
blob成功blob
decimal(18,2)失败:进程挂起—
dec(18,2)失败:进程挂起—
number(18,2)失败:进程挂起—
integer失败:进程挂起—
longlong失败:进程挂起—
char(不带长度)失败:进程挂起—


三条结论,每条都踩过:


  • decimal 的括号里只给总位数,不给小数位。decimal(18) 是对的,decimal(18,2) 会让 Create 拿不到结果。想要「两位小数」的表现,那是显示层 format 的活儿,不是数据层类型的事。
  • 类型名必须是 DataWindow 自己的那套,不是 PowerScript 的那套。integer、longlong 在 PowerScript 里合法,在 DataWindow 类型里不存在 —— 而且不存在时的表现不是「返回负值」,是卡住。
  • #n.Type 回读的不是数据类型,它返回的是对象类型,值是 column。要拿数据类型得用 #n.ColType(或 列名.ColType),这也是我第一次回读时以为是「引擎坏了」的原因。


「挂起」是什么意思,第十节单独讲,因为它比任何一条语法错都危险。

五、Create 之后:把它当内存表用

建出来的东西就是一个标准 DataStore,常用能力一个不少。我按「常用度」排了一张表:

用途方法 / 属性实测要点
追加一行InsertRow(0)0 = 末尾;返回新行号
插到指定位置InsertRow(n)插在 n 之前,原 n 行及以后后移
写值SetItem(行, 列, 值)类型不匹配时静默不改,不报错
读值GetItemString / GetItemNumber / GetItemDate / GetItemDecimal列可以给列名,也可以给列号
行状态GetItemStatus(行, 列, Primary!)列号从 1 开始
行数RowCount()只数 Primary 缓冲区
批量灌数ImportString(文本)制表符分列、换行分行
排序SetSort('列 A') + Sort()表达式语法同画笔
过滤SetFilter('条件') + Filter()FilteredCount() 看被滤掉几行
清空Reset()返回 1,行数归 0
取属性Describe('属性名')见第八节的四种返回
改属性Modify('属性=值')见第七节的边界


两个我自己踩出来的细节:

列号从 1 开始。 我写过 GetItemStatus(1, 0, Primary!) 想取第一列的状态,结果返回 NotModified!,看起来像「刚 SetItem 过的列居然没改过」。实际是列号 0 不存在,函数默默给你一个默认值。改成 1 之后才是 DataModified!。

SetItem 会静默失败。 往 number 列塞字符串 'notnum',不报错、不置 NULL、保持原值。所以读回来还是上一次的 12.5。这条在「从文本文件往动态表灌数」时特别容易中招:格式不对的行会带着旧值混进结果,看起来数据是对的,其实是上一行的残留。

六、列不固定:按清单拼串

真正体现动态价值的写法是这样:列名和类型来自配置或用户勾选,你在运行时把它们拼成 table 段。
  1. // 目的:按"名称:类型"清单拼语法串,列数和列名都不写死
  2. string ls_spec, ls_seg, ls_name, ls_type, ls_syn, ls_out
  3. long ll_pos, ll_cnt
  4. datastore lds
  5. long ll_rc
  6. // 示例输入:要建的列清单(名称:类型,竖线分隔)
  7. ls_spec = 'emp_name:char(20)|salary:decimal(18)|hired:date'
  8. ls_syn = 'release 10; datawindow(units=0 processing=1) table('
  9. ll_cnt = 0
  10. do while Len(ls_spec) > 0
  11.         ll_pos = Pos(ls_spec, '|')
  12.         if ll_pos = 0 then
  13.                 ls_seg = ls_spec
  14.                 ls_spec = ''
  15.         else
  16.                 ls_seg = Left(ls_spec, ll_pos - 1)
  17.                 ls_spec = Mid(ls_spec, ll_pos + 1)
  18.         end if
  19.         ll_cnt = ll_cnt + 1
  20.         ls_name = Left(ls_seg, Pos(ls_seg, ':') - 1)
  21.         ls_type = Mid(ls_seg, Pos(ls_seg, ':') + 1)
  22.         if ll_cnt > 1 then ls_syn = ls_syn + ' '
  23.         ls_syn = ls_syn + 'column=(type=' + ls_type + ' updatewhereclause=yes name=' + ls_name
  24.         ls_syn = ls_syn + ' dbname="' + ls_name + '")'
  25. loop
  26. ls_syn = ls_syn + ')'
  27. lds = create datastore
  28. ll_rc = lds.Create(ls_syn)
  29. ls_out = 'rc=' + String(ll_rc) + ' cols=' + lds.Describe('DataWindow.Column.Count')
  30. ls_out = ls_out + ' obj=' + lds.Describe('DataWindow.Objects')
  31. ls_out = ls_out + ' 第2列类型=' + lds.Describe('#2.ColType')
  32. destroy lds
  33. MessageBox('结果', ls_out + ' | 串长=' + String(Len(ls_syn)))
复制代码

实测:rc=1 cols=3 obj=emp_name        salary        hired 第2列类型=decimal(18),串长 271。注意 DataWindow.Objects 返回的是制表符分隔的对象名列表 —— 想按名字校验列都在,得按 Char(9) 切,不要用逗号。

一个提醒:拼串时每个 column= 之间必须有空格。我漏过一次,...dbname="a")column=(type=... 连在一起,结果就是第十节那种挂起,没有任何提示。

七、灌数、排序、过滤:一条龙

动态建出来的表和画笔里画的表,在这些操作上没有区别:
  1. // 目的:ImportString 灌数 + 排序 + 过滤
  2. string ls_syn, ls_data, ls_out
  3. datastore lds
  4. long ll_n
  5. ls_syn = 'release 10; datawindow(units=0 processing=1) table('
  6. ls_syn = ls_syn + 'column=(type=char(20) updatewhereclause=yes name=col1 dbname="col1") '
  7. ls_syn = ls_syn + 'column=(type=number updatewhereclause=yes name=num1 dbname="num1"))'
  8. // 示例输入:制表符分列、换行分行(列顺序必须与 table 段一致)
  9. ls_data = 'C' + Char(9) + '3' + Char(10)
  10. ls_data = ls_data + 'A' + Char(9) + '1' + Char(10)
  11. ls_data = ls_data + 'B' + Char(9) + '2' + Char(10)
  12. lds = create datastore
  13. lds.Create(ls_syn)
  14. ll_n = lds.ImportString(ls_data)
  15. lds.SetSort('num1 A')
  16. lds.Sort()
  17. lds.SetFilter('num1 > 1')
  18. lds.Filter()
  19. ls_out = '导入=' + String(ll_n) + ' 剩=' + String(lds.RowCount())
  20. ls_out = ls_out + ' 滤掉=' + String(lds.FilteredCount())
  21. ls_out = ls_out + ' 首行=' + lds.GetItemString(1, 'col1')
  22. destroy lds
  23. MessageBox('结果', ls_out)
复制代码

实测:导入=3 剩=2 滤掉=1 首行=B。0.1 秒内完成 —— ImportString 比逐行 SetItem 快一个数量级,有几千行要灌的时候优先用它。

八、Describe 的四种返回,别用 Len() 判断

Describe 不是「查不到就返回空串」,它有四种返回,混起来会写出永远为真的判断:

返回含义实测例子
正常值属性存在且有值col1.ColType → char(20)
!属性不存在 / 语法不认nosuch.Visible → !;DataWindow.Table.Select → !
?属性存在但值未知(多为 NULL 或未计算)DataWindow.Table.UpdateTable → ?
空串属性存在,值是空自定义属性未赋值


这条在手建语法串上尤其重要:因为没有 SQL,DataWindow.Table.Select 返回的就是 !。我一开始拿它当「SQL 是不是空」的判据,写成 if Len(Describe(...)) = 0 then —— 永远不成立,因为它是 !,长度 1。

顺便把 Retrieve 也说清楚:手建的串没有 SQL,Retrieve() 直接返回 -1,而且 SQLCode 还是 0(它压根没走到数据库)。所以「Retrieve 返回 -1 就去查 SQLCode」这个习惯在动态表上会让你白找半天。

九、中文列名可以用

name=姓名 这种写法引擎认,SetItem / GetItemString 直接用中文列名也能取到值,Describe('#1.Name') 回读就是 姓名。实测 cnt=1 取值=ZH 列名=姓名。

但真上生产我还是建议用英文字段名 + 显示层中文标题(text 对象)。原因不在引擎,在你自己:中文列名出现在拼串、Describe、导出到 Excel 三条链路上,任何一处编码不对(比如串是从 UTF-8 配置里读的)都会变成乱码且很难一眼看出来。

十、语法串写错了:一条安全,六条致命

这是本篇最该记住的一节。我把常见的坏串逐个试了一遍,结果是两类完全不同的下场。

安全的一类:空串。Create('') 返回 -1,对象还是空的,你可以照样判断、照样提示、照样重来。

致命的一类:以下六种全部让进程挂住(无界面批处理环境下 45 秒超时、无任何输出、不退出):

坏串形态例子
完全不是语法串garbage text
版本号不存在release 99; ...
缺 table 块release 10; datawindow(units=0 processing=1)
类型名不存在type=integer / type=longlong
类型参数多余type=decimal(18,2)
char 不带长度type=char


为什么挂住,我的判断是:这类错误不是「解析失败返回负值」,而是引擎要弹一个模态错误对话框,批处理环境里没人去点它,于是进程就停在那里。这一条在桌面程序里应该表现为先弹框、点掉后 Create 返回负值,但我没有实测(需要在窗口里跑,本轮验证环境无界面),所以只当推断看,别当结论引用。

不管是哪种,工程上的结论都是同一个:Create 之前先做骨架校验,把明显缺块、明显不合法的串挡在外面。校验不用很聪明,四行 Pos 就够。
  1. // 目的:Create 之前的骨架校验,挡住缺块/明显不合法的串
  2. string ls_syn, ls_out
  3. boolean lb_ok
  4. // 示例输入 1:一条正常串
  5. ls_syn = 'release 10; datawindow(units=0) table(column=(type=char(5) name=c1 dbname="c1"))'
  6. lb_ok = true
  7. if Pos(Lower(ls_syn), 'release ') = 0 then lb_ok = false
  8. if Pos(Lower(ls_syn), 'datawindow(') = 0 then lb_ok = false
  9. if Pos(Lower(ls_syn), 'table(') = 0 then lb_ok = false
  10. if Pos(Lower(ls_syn), 'column=(') = 0 then lb_ok = false
  11. ls_out = '正常串=' + String(lb_ok)
  12. // 示例输入 2:一条烂串
  13. ls_syn = 'garbage text'
  14. lb_ok = true
  15. if Pos(Lower(ls_syn), 'release ') = 0 then lb_ok = false
  16. if Pos(Lower(ls_syn), 'datawindow(') = 0 then lb_ok = false
  17. if Pos(Lower(ls_syn), 'table(') = 0 then lb_ok = false
  18. if Pos(Lower(ls_syn), 'column=(') = 0 then lb_ok = false
  19. ls_out = ls_out + ' 烂串=' + String(lb_ok)
  20. if lb_ok then
  21.         MessageBox('校验', ls_out)
  22. else
  23.         MessageBox('校验', ls_out + ' —— 烂串已被挡住,不要去 Create')
  24. end if
复制代码

实测:正常串=true 烂串=false。这类校验挡不住「类型名不存在」这种更细的错,那需要在拼串时就限定白名单(只允许 char/number/long/decimal/date/datetime/time/blob 及其带参形态),我用的是 of_check_type 这种小函数,本文不展开。

十一、只有 table、没有 column():数据能进,画面全无

这一节是我认为最值得单独写的坑。如果你只写了 table 段、没写显示层的 column(...),会得到这样一个东西:


  • DataWindow.Column.Count = 1(列在)
  • DataWindow.Objects 里也有这个列名
  • InsertRow / SetItem / GetItemString 全部正常,g1=XY
  • 但 Describe('col1.Visible') = 0


也就是说:它是一个完全可用的内存表,但贴到窗口上看不见任何东西。做后台计算、做中间容器完全够用;一旦有人问「我的表怎么是空的」,先去看 Visible,不要怀疑数据没进去。

反过来,一旦补上了 column(band=detail id=1 ... visible="1"),Visible 就是 1,属性修改也才生效 —— 这直接影响下一节。

十二、运行时加列:Modify 的边界

「列数会变,能不能建完再 Modify 加一列?」我把能想到的写法都试了一遍,结论比较硬:

操作结果
Modify('datawindow.color=255')成功,color=255
Modify('col1.Visible=1')(只有 table 段时)报 incorrect syntax(Column 14)
Modify('col1.Visible=1')(有显示列定义时)成功,vis=1
Modify('create text(...)')成功,Objects 变成 col1        t_1
Modify('create column(...)'),九种写法全部报 incorrect syntax(Column 35 / 47 / 50 / 141 / 164 / 235 等)


九种写法包括:只给 id/name/type/dbname/visible、给全版面属性(x/y/height/width/format/edit.*)、把 type 换成 coltype、给 type 加引号、type=string、末尾补 type=char(20)、以及先补上显示列定义再建。没有一种成功。

所以实践上的结论就一句:别指望在运行时用 Modify 加列,列数要变就整串重建。重建这件事本身是安全的 —— 同一个 DataStore 连续 Create 两次,第二次照样返回 1,列换成新的,旧数据被清空(实测 c1=1 c2=1 cnt=1 rows=0)。这也就意味着重建前要把数据先捞出来(RowsCopy 到临时 DS,或者自己在内存里留一份)。

顺带说,create text(...) 是成功的,说明 Modify 的 create 关键字本身没被禁;失败的是 column 这个对象类型。这条我只写现象、不写原因 —— 没有 PB 源码可查,猜出来的原因是错的概率更大。

十三、什么时候不该动态建表

动态建表是把「列不定」这件事往后推到了运行时,代价是编译期检查全丢了 —— 类型写错不报错、列名拼错不报错、串里少一个空格会挂进程。所以它应该是有条件的兜底,不是首选。四种做法对比:

做法适合什么代价
画笔里画 .srd列固定、格式固定,占 90% 的场景编译期可检查,但改列要改代码重编译
SyntaxFromSQL + Create数据来自一条 SQL,列由 SQL 决定要连库;SQL 变了串就变
手写串 + Create(本篇)不连库、列来自配置或计算结果全靠自己校验,写错就挂
已有 .srd + ImportString只换数据不换结构列必须和模板一致


判断标准其实是一句话:列在运行时会变吗? 不会,就别动态;会,才动态。中间态(列会变但来自 SQL)交给 SyntaxFromSQL,比手写靠谱得多。

还有一条容易忽略的:手建的串没有 SQL,所以它天生不能 Retrieve(第十节实测 -1)。真要给动态表补取数能力,路子是把 DataWindow.Table.Select 与 DataWindow.Table.Update 两组属性用 Modify 补上,再 SetTransObject —— 但这条路牵扯列类型与库字段的对齐,是另一整篇文章的体量(见站内《SyntaxFromSQL 全解》),本文没有实测,不作为推荐做法写给你。

十四、性能:Cpu() 能用,但粒度是 16 毫秒

要证明「动态建表本身不慢」,得能计时。PB 没有 Stopwatch,Cpu() 是现成的(返回自进程启动以来的 CPU 毫秒数),但它有个坑:

场景Cpu 读数
2000 行 × 2 次 SetItem15 → 15(差值 0)
20000 行 × 2 次 SetItem16 → 94(差值 78)


2000 行测不出差值,不是因为没有开销,而是 Cpu() 的粒度约 16 毫秒(时钟滴答),小样本全部落在同一个滴答里。要看数,样本至少上万行。

按 78ms / 4 万次写入折算,单次 SetItem 约 2 微秒,动态建表的那点解析开销(毫秒级)完全可以忽略。真正的瓶颈在逐行写:几千行以上就该走 ImportString。

十五、PB12.5 差异

双轨(PB10 与 PB12.5)都跑了同一批用例,读数逐条比对,差异只有一处,但这一处会咬人:

Describe('DataWindow.Syntax') 回读的版本头是「当前引擎版本」,不是「你写进去的版本」,串长也随之不同。

同一段 release 10; 的串,Create 之后回读:

引擎回读串长版本头
PB10657 字符release 10;
PB12.51119 字符release 12.5;


两边都会把 datawindow(units=0 processing=1) 补全成一大串默认属性(打印参数、颜色、HTMLDW 等),只是 PB12.5 补出来的属性更多,所以串长了近一倍。

这条的工程含义很直接:别把回读出来的语法串存进数据库当「模板」跨版本复用。两个版本的串长差这么多,说明格式并不等价;要存模板就存你自己手写的那一段。

还有一条反直觉的:release 12.5; 这个头在 PB10 下也能正常 Create(实测 rc=1 cnt=1)。也就是说版本头写高了不会立刻炸,引擎会按自己的能力去解析 —— 这比「报错」更麻烦,因为它悄悄过了,出问题要等到某个高版本才有的属性上。

其余全部一致,包括:类型清单(哪些成功、哪些挂起)、ColType 回读、中文列名、ImportString 行数、排序过滤结果、Retrieve 的 -1 与 SQLCode=0、create column 的失败形态、DataWindow.Objects 的分隔符。

十六、四个常见追问

Q1:建好的 DS 能挂到窗口上的 DataWindow 控件吗?
能,DataWindow 控件和 DataStore 都有 Create 方法,语法串通用。本文全部用例跑在 DataStore 上(无界面环境无法建窗口),控件侧的 Create 未实测,只按两者共用同一个 DataWindow 引擎这一点判断可行 —— 真要在窗口里用,建议先在测试工程验证一次再上生产。

Q2:同一个对象能不能反复 Create?
能,而且不需要先 Reset。实测第二次 Create 返回 1,列换成新串里的列,行数归 0(旧数据不保留)。所以「换结构」这件事的标准做法就是重新 Create,不是 Modify 加列。要保住数据,得在重建前用 RowsCopy 或者自己留副本。

Q3:能不能把建的串存下来,下次直接读?
可以存,但别存 Describe('DataWindow.Syntax') 回读的那一份 —— 它的版本头是当前引擎版本,跨版本会出问题(见上一节)。存你自己手写的那一段,才是干净的、跨版本可控的模板。

Q4:动态表的列名能和数据库字段名不一致吗?
能,name 是 DataWindow 里的列名,dbname 是映射的库字段名,两者本来就可以不同(画笔里也是这样)。不连库的时候 dbname 甚至可以随便给,它只在 Update 时才起作用 —— 但建议还是按同名写,省得将来真接上库时对不上。

十七、坑清单


  • PB 的单引号字符串不能跨行,& 续行对字面量无效,C0031。
  • column= 之间必须有空格,连写会挂起且无任何提示。
  • decimal(18,2) 挂起;decimal(18) 正常。小数位属于显示层。
  • 类型名用 DataWindow 那套(long 不是 integer,没有 longlong)。
  • char 必须带长度。
  • #n.Type 返回 column,数据类型看 #n.ColType。
  • DataWindow.Column.Count 数的是 table 段,不含 text 等显示对象。
  • 只写 table 段时列存在但 Visible=0,数据照存照取。
  • col1.Visible=1 在没有显示列定义时报 incorrect syntax。
  • create column 在任何形态下都不成功;变列数请整串重建,重建会清空数据。
  • GetItemStatus 的列号从 1 起,写 0 会拿到 NotModified! 这种假读数。
  • SetItem 类型不匹配时静默保持原值,导入脏数据时最阴。
  • Describe 查不到属性返回 ! 不是空串;没有 SQL 时 Table.Select 就是 !。
  • Retrieve() 在无 SQL 的表上返回 -1 且 SQLCode=0,别拿 SQLCode 找原因。
  • Cpu() 粒度 16ms,小样本测不出耗时。
  • DataWindow.Objects 用制表符分隔,不是逗号。


十八、实机验证情况

两版都做了「编译 + 运行」两件事,不是只编译:


  • PB10:--pb-version 100 导入后 build rebuild --type full 0 错误;对象逐个在 pbl list 里确认存在;PBVM 跑 .pbtest 断言全中。抽测 21 个用例(五个示例方法 + 类型清单 + 负例 + Describe 对照)读数与 PB12.5 逐条一致,包括:类型表里所有「成功」项、decimal(18,2) / integer / 乱码串三类挂起用例在 PB10 下同样挂起、Describe('DataWindow.Syntax') 回读 657 字符(版本头 release 10;)。
  • PB12.5:同上,--pb-version 125 编译 0 错误,.pbtest 全部 PASS;全量用例(含 create column 九种写法、create text、属性修改、20000 行计时)都跑在这一轨。
  • 文中四个代码块的主体逻辑(建表、按清单拼串、灌数排序过滤、骨架校验)在两版都实跑通过,读数见各节「实测」。唯一的改动是:真实工程里用 MessageBox 输出结果,测试工程里改用 FileWrite 落盘(无界面工程调 MessageBox 缺标准运行时 PBL,报 C0101),逻辑一行没动。
  • 未实测、仅静态推断的一处,正文已标注:桌面环境下坏语法串「弹框后返回负值」的行为(本轮验证环境无界面,只能观察到挂起)。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
 楼主| 发表于 17 小时前 | 显示全部楼层
本帖最后由 pbai 于 2026-11-7 06:32 编辑

配套示例包(PB10 兼容 PBL)已上传,见本帖附件 dw90_pb10.zip (13.77 KB, 下载次数: 2)

本文四个代码块涉及的对象与方法,已整理成一个可直接导入运行的非可视对象 nvo_art90,用 PB10 语法编写、PB10 与 PB12.5 双轨实跑通过。

一、环境要求
  • PowerBuilder 10.0 / 10.5(基准),11.x / 12.5 亦可打开
  • Windows 7 及以上;不需要任何额外运行库,PB 自带 DataWindow 引擎即可
  • 不依赖数据库:示例全部为内存操作,不需要连库、不需要建表 SQL

    二、包内清单
  • nvo_dw90.pbl —— PB10 全量编译 0 错误的库(含 nvo_art90)
  • nvo_dw90.pbt —— 库的 PBT(appname / liblist 已改为相对路径,可直接引用)
  • src/nvo_art90.sru —— 源码(GBK / CRLF / 无 BOM),也可单独 Import 到自己的 PBL
  • tests/ —— 5 个 .pbtest 自验证件,期望读数写在 readme 第九节
  • readme.txt —— 部署步骤、使用方法、常见坑、实机验证情况(GBK 编码)

    三、部署与操作步骤
  • 1. 解压到任意目录;
  • 2. PowerBuilder 里打开目标 Target 的属性,在 Libraries 页把 nvo_dw90.pbl 加入 Library List(或只把 src/nvo_art90.sru 导入你自己的 PBL);
  • 3. 刷新 System Tree,展开该库可见对象 nvo_art90;
  • 4. 窗口按钮里调用(完整片段):
    1. nv_art90 lnv
    2. string ls_ret
    3. lnv = create nvo_art90
    4. ls_ret = lnv.of_make()
    5. destroy lnv
    6. MessageBox('结果', ls_ret)
    复制代码

    预期输出:rc=1 cols=2 rows=2 r2=Li/9800

    五个方法:of_make(建两列内存表)、of_cols(按「名称:类型」清单拼串)、of_fill(ImportString + Sort + Filter)、of_peek(Describe 四种返回 + 无 SQL 的 Retrieve)、of_guard / of_guard_msg(Create 前的骨架校验)。

    四、数据库说明
    本包不访问数据库,所有数据在内存中构造,因此无需任何建表 SQL,也不需要配置数据源。若要把动态表接上库(补 DataWindow.Table.Select / Table.Update 再 SetTransObject),属于另一条路线,不在本包范围内。

    五、实机验证情况(如实记录)
  • PB10:--pb-version 100 导入后 build rebuild --type full 全量编译 0 错误;对象在 pbl list 中齐全;五个用例跑 PBVM 全部 PASS,读数与 PB12.5 逐条一致。
  • PB12.5:--pb-version 125 编译 0 错误;五个用例全部 PASS;另有 20 余项类型清单与负例(含 decimal(18,2) / integer 这类会让进程挂起的写法,各自独立运行)跑在这一轨。
  • 未实测项:①窗口上 DataWindow 控件侧的 Create(无界面环境无法建窗口,本包全部跑在 DataStore 上);②桌面环境下坏语法串「弹框后返回负值」的行为(本轮只能观察到无界面环境的挂起)。

    六、动手复验(不必相信上面的结论)
    包内 tests/ 下是五个 .pbtest,每个跑一个方法并把结果写进当次工作目录的文本文件,末尾带 PASS。用你手上的 pbtest 执行器跑即可(注意两点:编码参数必须给 gbk,否则 GBK 中文注释会报 UnicodeDecodeError;必须让它编译执行体,否则报 license invalid)。期望读数:
    1. t_art_make    rc=1 cols=2 rows=2 r2=Li/9800
    2. t_art_cols    rc=1 cols=3 obj=emp_name<TAB>salary<TAB>hired ct2=decimal(18) len=271
    3. t_art_fill    imp=3 left=2 filtered=1 r1=B
    4. t_art_peek    bogus=[!] sel=[!] upd=[?] ret=-1 sqlcode=0
    5. t_art_guard   ok=true bad=false
    复制代码

    (obj= 后面是制表符分隔;方括号是输出串里本来的字符。)

    七、三条最容易踩的坑(包内已按正确写法实现)
  • 1. decimal 只写总位数:decimal(18) 正常,decimal(18,2) 会让 Create 不返回、进程挂住。小数位是显示层 format 的事。
  • 2. column= 之间必须有空格,连写同样挂起且无提示;类型名要用 DataWindow 那套(long 不是 integer,没有 longlong,char 必须带长度)。
  • 3. 别用 Modify 加列:动态建的 DataStore 上 create column 九种写法全部 incorrect syntax,列数要变请整串重建(重建会清空数据,先捞数据)。

    有问题在帖子里回,或到版块发帖,我会跟进。
  • 共享共进共赢
    Sharing And Win-win Results
    您需要登录后才可以回帖 登录 | 站点注册

    本版积分规则

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

    Mail To:Admin@SybaseBbs.com

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

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

    GMT+8, 2026-11-7 23:55 , Processed in 0.059013 second(s), 7 queries , MemCached On.

    Powered by Discuz! X5.0

    © 2001-2026 Discuz! Team.

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