马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?站点注册
×
PowerBuilder DataWindow 下拉数据窗口(DDDW):取值分离与级联联动
阅读说明
1. 适用版本:PowerBuilder 9.0 – 12.5(含 PBIDEA 兼容);DDDW 编辑风格自 PB 早期版本即稳定支持,写法无版本差异
2. 支持数据库:示例基于 SQL Server(SQLCA 事务);DDDW 子数据窗口支持 PB 支持的任何数据库(事务对象可替换);「外部数据源 DDDW」示例不依赖数据库
3. 操作系统与环境要求:Windows 7+;本主题为标准 DataWindow 编辑风格,无需额外运行库;示例需在窗口放 DataWindow 控件 dw_1 并关联主 DataObject(d_employee),另建两个子 DataWindow 对象 d_country、d_province(或 External 源 d_prov_ext)
4. 难度系数:★★★☆☆(三星,需理解 DataWindow 事件链路与缓冲区,但概念直观)
5. 其它阅读说明:建议先读《PowerBuilder DataWindow 运行时动态改属性:Modify 与 Describe》与《PowerBuilder DataWindow 关键事件链路:ItemChanged/ItemError/RowFocusChanged》;示例用到三张表 t_country(country_id,country_name)、t_province(prov_id,prov_name,country_id)、t_employee(emp_id,emp_name,country,province),或改用 External 字典
下拉数据窗口(DropDownDataWindow,简称 DDDW)是 DataWindow 的一种编辑风格:某个列在录入时,不直接敲键盘,而是展开一张"子 DataWindow"让你选。它和普通下拉框最大的不同是存的是编号、显示的是名称——这正是联动(省→市→区)能成立的根基。本文把一件事讲透:DDDW 的显示值/数据值是怎么分离的,以及父列一变、子列怎么跟着刷。
一、DDDW 是什么:一张"会下拉"的子 DataWindow
DDDW 不是控件,而是列(column)的一种编辑风格(Edit Style)。被设为 DDDW 的列,在得到焦点时会弹出一个下拉面板,面板里是一张完整的 DataWindow(称为下拉子窗口 / DropDownDataWindow)。
关键事实:下拉子窗口是一张真正的 DataWindow,它有 DataObject、能 SetTransObject、能 Retrieve、能 SetFilter,只是"寄居"在父 DataWindow 的某一列里。父 DataWindow 通过 GetChild("列名", 子窗口变量) 拿到它的引用。理解这一点,后面所有运行时操作就顺了。
二、核心机制:显示值(DisplayColumn)与数据值(DataColumn)分离
这是 DDDW 最该吃透的一点,也是新手最容易踩坑的地方。
- DataColumn(数据列):下拉里选中某行后,实际写进父 DataWindow 这一列的值。通常是编号(如 prov_id=1)。
- DisplayColumn(显示列):下拉里给用户看的那一列(如 prov_name=北京)。
二者在设计期分别指定。它们的关系是"选中行的 DataColumn 值入库,DisplayColumn 文本仅用于显示"。如果两张表设计成"数据列和显示列是同一列"(比如都选 prov_name),那么入库的就是名称本身——多数业务里你要的是编号,所以一般 DataColumn 选编号列、DisplayColumn 选名称列。
记忆口诀:库里躺编号,眼里看名字;改的是显示,存的还是数据。
三、设计期怎么配(三步)
在 DataWindow 画板里,选中要设为下拉的列 → Edit 页签 → Style 选 DropDownDataWindow,然后:
- DataWindow:选子数据窗口对象(如 d_province)。
- Display Column / Data Column:分别选"显示用列"和"入库用列"(如 prov_name / prov_id)。
- Width / Height:设下拉面板宽度(务必 ≥ 显示列宽度,否则名称被截断);AutoRetrieve 勾上则父 DataWindow Retrieve 时子窗口自动跟着 Retrieve(简单场景可勾,级联场景建议关掉自己控制,见第四节)。
勾了 AutoRetrieve 并不代表"连库"就自动好了——子窗口仍然需要事务对象,只是 Retrieve 时机被托管。级联联动场景下我们通常不勾 AutoRetrieve,改为在代码里精确控制,避免每次父检索都把全部字典拉一遍。
四、运行时必须做的事:GetChild + SetTransObject + Retrieve
新手最常见的"下拉是空的"就栽在这里:子窗口不会继承父 DataWindow 的事务对象,必须自己 SetTransObject 再 Retrieve。否则子窗口连不上库,下拉自然是空白。
前置:窗口 w_emp 已放 DataWindow 控件 dw_1,DataObject = d_employee;d_employee 的 province 列编辑风格为 DDDW,子数据窗口 = d_province(含 prov_id、prov_name、country_id 三列)。
步骤:在 dw_1 的 retrieveend 事件(或窗口 open)中贴入下方代码,这样下拉展开时子窗口已有数据;本例先一次性取回全部省份,级联过滤放到 itemchanged 里做。 - // 取出 province 列对应的下拉子数据窗口
- DataWindowChild ldwc_prov
- integer li_rc
- li_rc = dw_1.GetChild("province", ldwc_prov)
- if li_rc = -1 then
- MessageBox("错误", "无法取得 province 的下拉子窗口")
- return
- end if
- // 子窗口是独立 DataWindow,必须自己连库(不继承父 DW 的事务)
- ldwc_prov.SetTransObject(SQLCA)
- // 一次性取回全部省份(级联过滤在 itemchanged 事件中做)
- long ll_rows
- ll_rows = ldwc_prov.Retrieve()
- MessageBox("省份下拉", "已载入省份数: " + string(ll_rows))
复制代码
注意 GetChild 返回 -1 表示取不到(列不是 DDDW 或名字拼错),一定要判返回值,否则后面 ldwc_prov.xxx 会空引用崩溃。
五、级联联动:父列变,子列跟着刷
联动的本质:父列(如 country)一变,就把子窗口(province)按新的父值过滤。标准做法是把过滤逻辑封装成一个对象函数,在 itemchanged 里"国家列变动时"调用——这样既能复用,输入也清晰。
前置:d_employee 的 country 列也是 DDDW(子窗口 d_country,含 country_id/country_name),province 列 DDDW(子窗口 d_province,含 country_id 外键)。二者通过 country_id 关联。
步骤:在 dw_1 上新建对象函数 of_filter_province(long al_country),再在 itemchanged 中判断 dwo.Name = "country" 时调用 this.of_filter_province(Long(data))。 - // 对象函数 of_filter_province(long al_country):按国家过滤省份下拉
- // 示例输入:国家数据值(来自 country 列的 DataColumn 值)
- long al_country
- al_country = 1 // 示例:中国(country_id = 1)
- DataWindowChild ldwc_prov
- integer li_rc
- li_rc = dw_1.GetChild("province", ldwc_prov)
- if li_rc = -1 then
- MessageBox("错误", "未取得 province 子窗口")
- return -1
- end if
- ldwc_prov.SetTransObject(SQLCA)
- ldwc_prov.Retrieve() // 先取全部
- // 只显示该国家下的省份(字符串条件里数值列直接拼接,无需引号)
- ldwc_prov.SetFilter("country_id = " + string(al_country))
- ldwc_prov.Filter()
- MessageBox("联动结果", "国家 " + string(al_country) + " 下省份已过滤")
- return 1
复制代码
itemchanged 里只需一行触发(事件参数 dwo 是发生变化的列、data 是新值):
- if dwo.Name = "country" then this.of_filter_province(Long(data))
复制代码关键提醒:父列变了,子列里原来选的旧省份可能已不属于新国家。务必在过滤后顺手清掉子列值(dw_1.SetItem(row, "province", ""),或弹确认),否则库里会留下"中国 / 东京"这种脏数据。
过滤用 SetFilter + Filter() 还是"带检索参数的 Retrieve",取决于字典大小:小字典(几百条)全量 Retrieve 再 Filter 最简单;大字典建议给子窗口加检索参数、直接 Retrieve(al_country) 只取需要的部分,省内存也快。
六、取值:你拿到的是数据值还是显示值?
联动之后要落库或回显,必须分清两件事:
- dw_1.GetItemString(row, "province") 拿到的是数据值(prov_id,入库的就是它)。
- 显示值(prov_name)不在父 DataWindow 里,要去子窗口按数据值反查。
前置:dw_1 已 Retrieve 且用户已选好省份。
步骤:在按钮 cb_read 的 clicked 事件贴入,演示"入库值"与"界面显示值"的区别。 - // 示例输入:当前定位行(读者运行前先点中一行)
- long ll_row
- ll_row = dw_1.GetRow()
- if ll_row <= 0 then
- MessageBox("提示", "请先定位到一行")
- return
- end if
- // 取数据值:写入数据库的就是这一列(通常是编号)
- string ls_data_val
- ls_data_val = dw_1.GetItemString(ll_row, "province")
- // 取显示值:去子窗口里按数据值反查显示列
- string ls_disp_val
- DataWindowChild ldwc_prov
- dw_1.GetChild("province", ldwc_prov)
- long ll_cr
- ll_cr = ldwc_prov.Find("prov_id = " + ls_data_val, 1, ldwc_prov.RowCount())
- if ll_cr > 0 then
- ls_disp_val = ldwc_prov.GetItemString(ll_cr, "prov_name")
- else
- ls_disp_val = "(未匹配)"
- end if
- MessageBox("取值对比", "数据值(入库)=" + ls_data_val + "~r~n显示值(界面)=" + ls_disp_val)
复制代码
记住:显示值只是"查出来给人看的",真正要存、要判断、要传给后台的,永远是数据值。 很多人写联动时拿显示值去查库,结果对不上——根因就是忘了这一层分离。
七、外部数据源 DDDW:不查库的本地下拉
省份、性别、状态这类固定字典,不必每次都查库。可以让子窗口用 External 源(无 SQL),程序里 ImportString 灌数据,再挂到父列的 DDDW 上。好处是零数据库压力、离线可用。
前置:建一个 External 源 DataWindow d_prov_ext(列 prov_id、prov_name),把主 DW 的 province 列 DDDW 指向它。
步骤:窗口 open 时 ImportString 灌入固定字典,下拉即可用。 - // 示例输入:固定省份字典(制表符分隔:prov_id<Tab>prov_name,行间回车)
- string ls_data
- ls_data = "1~t北京~r~n2~t上海~r~n3~t广东"
- DataWindowChild ldwc_ext
- dw_1.GetChild("province", ldwc_ext)
- long ll_n
- ll_n = ldwc_ext.ImportString(ls_data)
- MessageBox("外部字典", "已导入省份条数: " + string(ll_n))
复制代码
External 源子窗口不需要 SetTransObject,因为它根本没有 SQL;ImportString 的行列分隔符是制表符和回车换行(~t / ~r~n),顺序必须与子窗口列定义一致。
八、边界情况与常见坑(必看)
- 下拉是空的:九成是子窗口没 SetTransObject + Retrieve。DDDW 子窗口不继承父 DW 事务,这是头号坑。
- 级联不刷新:父列变了子列还是老数据——忘了对子窗口 SetFilter + Filter() 或重新 Retrieve。AutoRetrieve 只在父 DW 检索时触发一次,之后父列再变不会自动重拉。
- 入库的是名字不是编号:DataColumn / DisplayColumn 设反了。去画板核对——DataColumn 应是编号列。
- GetChild 不判返回值:列名拼错或非 DDDW 时返回 -1,直接用变量必崩,务必 if li_rc = -1 then ...。
- 过滤条件引号陷阱:数值列直接拼(country_id = 1),字符列才加单引号(prov_name = '北京');拼错会 Filter() 静默失败、子窗口变空白。
- 下拉宽度不够:面板 Width 小于显示列,名称被截断像"北…"。设计期把 Width 调到能放下显示列。
- 父列改了没清子列:留下跨国家的脏组合(如 中国/东京)。联动时主动 SetItem(row,"province","")。
- 重复 Retrieve 拖慢:大字典每次父检索都全量拉——改检索参数 Retrieve(父值) 只取需要的,或 External 源一次性灌。
- 多层联动(省→市→区):每一层都是"父列 itemchanged → 过滤下一层子窗口",逐层串起来即可;注意清值的顺序要从被改层的下一层开始往后清。
九、与 DropDownListBox / EditMask 怎么选
| 方案 | 数据源 | 能联动 | 适合 | | DDDW | 子 DataWindow(可查库/External) | ✅ 天然支持 | 字典来自表、需要省→市联动、要显示名存编号 | | DropDownListBox | 代码 AddItem 写死 | ❌ 需自己写 | 极少量固定选项、又不在 DataWindow 内 | | EditMask + Validation | 无下拉 | ❌ | 仅格式化输入(电话/日期),不需要选择 |
一句话:选项来自数据库、要联动、要"显示名存编号",闭眼选 DDDW;纯写死的少量选项,DataWindow 里也可以用 DDDW + External 源,不必退回 DropDownListBox。
十、进阶:多层联动与动态改 SQL
- 多层联动:省→市→区就是三层 DDDW,每层在父列 itemchanged 里过滤下一层子窗口,并清掉其后各层的旧值,按第八节第 9 条处理即可。
- 检索参数替代 Filter:给子窗口加检索参数(如 country_id),联动时 ldwc.Retrieve(al_country) 直接只取该国的省,省去全量 Retrieve + Filter,大字典更优。
- 动态 SQL(高级):若子窗口条件非常灵活,可用 ldwc.SetSQLSelect("新SQL") 改子窗口 SQL 再 Retrieve——注意改完要 SetTransObject 并处理好事务;多数联动场景用 Filter/检索参数就够了,不必动 SQL。
- 联动与 Update:DDDW 列入库的是 DataColumn 值,Update 时和普列无异;只要子窗口的 DataColumn 对应父表字段,保存链路(AcceptText + Update + COMMIT)完全不用改。
DDDW 把"选择"和"存储"解耦,是 PB 做联动录入最顺手的武器。把显示值/数据值分离、子窗口自行连库、父列变动即过滤这三点吃透,省→市→区乃至多层字典联动都能稳稳拿下。 |