马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?站点注册
×
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),窗口按钮里调它,或者干脆在一个测试用的按钮里直接贴。
- // 目的:一条语法串建出两列内存表,插两行再回读
- string ls_syn, ls_out
- datastore lds
- long ll_rc
- // 第 1 步:拼语法串 —— table 段的 column 决定"能存什么"
- ls_syn = 'release 10; datawindow(units=0 processing=1) table('
- ls_syn = ls_syn + 'column=(type=char(20) updatewhereclause=yes name=emp_name dbname="emp_name") '
- ls_syn = ls_syn + 'column=(type=decimal(18) updatewhereclause=yes name=salary dbname="salary"))'
- // 第 2 步:建对象。Create 返回 1 才是成功
- lds = create datastore
- ll_rc = lds.Create(ls_syn)
- if ll_rc <> 1 then
- destroy lds
- MessageBox('建表失败', 'rc=' + String(ll_rc))
- return
- end if
- // 第 3 步:当内存表用。InsertRow(0) 表示追加到末尾
- lds.InsertRow(0)
- lds.SetItem(1, 'emp_name', 'Zhang')
- lds.SetItem(1, 'salary', 12000)
- lds.InsertRow(0)
- lds.SetItem(2, 'emp_name', 'Li')
- lds.SetItem(2, 'salary', 9800)
- ls_out = 'cols=' + lds.Describe('DataWindow.Column.Count')
- ls_out = ls_out + ' rows=' + String(lds.RowCount())
- ls_out = ls_out + ' 第2行=' + lds.GetItemString(2, 'emp_name') + '/' + String(lds.GetItemNumber(2, 'salary'))
- destroy lds
- MessageBox('结果', ls_out)
复制代码
实测读数:rc=1 cols=2 rows=2 第2行=Li/9800。两版一致。
注意字符串的拼法:PB 的单引号字符串字面量不能跨行,写
会直接编译报 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 段。
- // 目的:按"名称:类型"清单拼语法串,列数和列名都不写死
- string ls_spec, ls_seg, ls_name, ls_type, ls_syn, ls_out
- long ll_pos, ll_cnt
- datastore lds
- long ll_rc
- // 示例输入:要建的列清单(名称:类型,竖线分隔)
- ls_spec = 'emp_name:char(20)|salary:decimal(18)|hired:date'
- ls_syn = 'release 10; datawindow(units=0 processing=1) table('
- ll_cnt = 0
- do while Len(ls_spec) > 0
- ll_pos = Pos(ls_spec, '|')
- if ll_pos = 0 then
- ls_seg = ls_spec
- ls_spec = ''
- else
- ls_seg = Left(ls_spec, ll_pos - 1)
- ls_spec = Mid(ls_spec, ll_pos + 1)
- end if
- ll_cnt = ll_cnt + 1
- ls_name = Left(ls_seg, Pos(ls_seg, ':') - 1)
- ls_type = Mid(ls_seg, Pos(ls_seg, ':') + 1)
- if ll_cnt > 1 then ls_syn = ls_syn + ' '
- ls_syn = ls_syn + 'column=(type=' + ls_type + ' updatewhereclause=yes name=' + ls_name
- ls_syn = ls_syn + ' dbname="' + ls_name + '")'
- loop
- ls_syn = ls_syn + ')'
- lds = create datastore
- ll_rc = lds.Create(ls_syn)
- ls_out = 'rc=' + String(ll_rc) + ' cols=' + lds.Describe('DataWindow.Column.Count')
- ls_out = ls_out + ' obj=' + lds.Describe('DataWindow.Objects')
- ls_out = ls_out + ' 第2列类型=' + lds.Describe('#2.ColType')
- destroy lds
- 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=... 连在一起,结果就是第十节那种挂起,没有任何提示。
七、灌数、排序、过滤:一条龙
动态建出来的表和画笔里画的表,在这些操作上没有区别:
- // 目的:ImportString 灌数 + 排序 + 过滤
- string ls_syn, ls_data, ls_out
- datastore lds
- long ll_n
- ls_syn = 'release 10; datawindow(units=0 processing=1) table('
- ls_syn = ls_syn + 'column=(type=char(20) updatewhereclause=yes name=col1 dbname="col1") '
- ls_syn = ls_syn + 'column=(type=number updatewhereclause=yes name=num1 dbname="num1"))'
- // 示例输入:制表符分列、换行分行(列顺序必须与 table 段一致)
- ls_data = 'C' + Char(9) + '3' + Char(10)
- ls_data = ls_data + 'A' + Char(9) + '1' + Char(10)
- ls_data = ls_data + 'B' + Char(9) + '2' + Char(10)
- lds = create datastore
- lds.Create(ls_syn)
- ll_n = lds.ImportString(ls_data)
- lds.SetSort('num1 A')
- lds.Sort()
- lds.SetFilter('num1 > 1')
- lds.Filter()
- ls_out = '导入=' + String(ll_n) + ' 剩=' + String(lds.RowCount())
- ls_out = ls_out + ' 滤掉=' + String(lds.FilteredCount())
- ls_out = ls_out + ' 首行=' + lds.GetItemString(1, 'col1')
- destroy lds
- 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 就够。
- // 目的:Create 之前的骨架校验,挡住缺块/明显不合法的串
- string ls_syn, ls_out
- boolean lb_ok
- // 示例输入 1:一条正常串
- ls_syn = 'release 10; datawindow(units=0) table(column=(type=char(5) name=c1 dbname="c1"))'
- lb_ok = true
- if Pos(Lower(ls_syn), 'release ') = 0 then lb_ok = false
- if Pos(Lower(ls_syn), 'datawindow(') = 0 then lb_ok = false
- if Pos(Lower(ls_syn), 'table(') = 0 then lb_ok = false
- if Pos(Lower(ls_syn), 'column=(') = 0 then lb_ok = false
- ls_out = '正常串=' + String(lb_ok)
- // 示例输入 2:一条烂串
- ls_syn = 'garbage text'
- lb_ok = true
- if Pos(Lower(ls_syn), 'release ') = 0 then lb_ok = false
- if Pos(Lower(ls_syn), 'datawindow(') = 0 then lb_ok = false
- if Pos(Lower(ls_syn), 'table(') = 0 then lb_ok = false
- if Pos(Lower(ls_syn), 'column=(') = 0 then lb_ok = false
- ls_out = ls_out + ' 烂串=' + String(lb_ok)
- if lb_ok then
- MessageBox('校验', ls_out)
- else
- MessageBox('校验', ls_out + ' —— 烂串已被挡住,不要去 Create')
- 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 次 SetItem | 15 → 15(差值 0) | | 20000 行 × 2 次 SetItem | 16 → 94(差值 78) |
2000 行测不出差值,不是因为没有开销,而是 Cpu() 的粒度约 16 毫秒(时钟滴答),小样本全部落在同一个滴答里。要看数,样本至少上万行。
按 78ms / 4 万次写入折算,单次 SetItem 约 2 微秒,动态建表的那点解析开销(毫秒级)完全可以忽略。真正的瓶颈在逐行写:几千行以上就该走 ImportString。
十五、PB12.5 差异
双轨(PB10 与 PB12.5)都跑了同一批用例,读数逐条比对,差异只有一处,但这一处会咬人:
Describe('DataWindow.Syntax') 回读的版本头是「当前引擎版本」,不是「你写进去的版本」,串长也随之不同。
同一段 release 10; 的串,Create 之后回读:
| 引擎 | 回读串长 | 版本头 | | PB10 | 657 字符 | release 10; | | PB12.5 | 1119 字符 | 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),逻辑一行没动。
- 未实测、仅静态推断的一处,正文已标注:桌面环境下坏语法串「弹框后返回负值」的行为(本轮验证环境无界面,只能观察到挂起)。
|