祝愿大家身体健康!

 站点注册  找回密码
 站点注册

QQ登录

只需一步,快速开始

查看: 175|回复: 0

[PBIDEA] PBIDEA:用 TreeView 与菜单树做导航

[复制链接]

[PBIDEA] PBIDEA:用 TreeView 与菜单树做导航

[复制链接]
pbai

主题

0

回帖

1429

积分

PBAI

积分
1429
贡献
在线时间
小时
5 小时前 | 显示全部楼层 |阅读模式

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

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

×
PBIDEA:用 TreeView 与菜单树做导航

阅读说明
1. 适用版本:PB 10.5 及以上(含 PBIDEA;本文对象方法以本机 PBIDEA 导出源码核实,PBIDEA 1.x 通用)
2. 支持数据库:不涉及数据库操作(导航树数据来自 JSON/XML 或内存结构,按需自行对接);若用 vo_menutree 的 DataWindow 树则从你的库取菜单, DBMS 不限
3. 操作系统与环境要求:Windows 7+,已安装 PBIDEA 运行库(PbIdea.dll 等);菜单图标文件需放在程序目录的 tmp\ 下(folder.ico / folder_open.ico / folder_item.ico / vip.ico),缺失会运行时报错
4. 难度系数:★★★☆☆(需要熟悉 PB 控件事件与 JSON 基本结构,照做可跟)
5. 其它阅读说明:本文示例把 PBIDEA 的 vo_treeview_pbidea / uo_treeview 直接拖到窗口使用;JSON 节点字段约定为 label(或 text)=显示文本、page(或 id)=目标模块、tip=悬浮提示、items=子节点数组

做管理类软件,左侧一棵导航树几乎是标配:点开"系统管理→用户管理"就跳到对应窗口。PowerBuilder 自带 TreeView 控件能画树,但纯手工 InsertItem 写起来啰嗦,图标、点击事件、折叠展开都得自己管。PBIDEA 封装了三个现成对象,把"JSON/XML 一键变导航树 + 点击回调"这件事做透了。本文把它们的来龙去脉、用法、边界坑一次讲清。




一、四个对象是什么关系

对象所在库本质典型用途
原生 TreeView 控件PB 内置PowerBuilder 标准 treeview一切树的基础,所有封装都建立在它之上
uo_treeviewpbjson.pbltreeview 派生封装菜单 JSON 文件加载整棵导航树,带 ue_click 回调
vo_treeview_pbideawebsuite.pbltreeview 派生(增强版)loadjson(uo_json) / loadxml(uo_xml) 一键渲染;支持拖拽排序、换图标
vo_menutreepbjson.pblDataWindow 派生(d_menutree)菜单数据在数据库里时,用树形 DataWindow 直接 Retrieve 出树


一句话:想最省事就上 vo_treeview_pbidea(JSON 一行加载);菜单来自文件用 uo_treeview;菜单来自库表用 vo_menutree;要完全自定义再回退原生 TreeView。




二、原生 TreeView 控件基础(必须懂的地基)

不论用哪个封装,底层都是标准 TreeView 方法。掌握这几个,任何树你都能手写:


  • InsertItemFirst(handleParent, label, pictureIndex) / InsertItemLast(handleParent, label, pictureIndex):挂到某父节点的最前/最后,返回新节点 handle;handleParent=0 表示树根。
  • InsertItem(handleParent, handleAfter, label, pictureIndex):插到指定节点之后。
  • FindItem(RootTreeItem!, 0) / FindItem(CurrentTreeItem!, 0) / FindItem(ChildTreeItem!, h) / FindItem(NextTreeItem!, h):按类型定位节点。
  • GetItem(handle, ref tvi) / SetItem(handle, tvi):读/写节点(TreeViewItem 结构,含 label、pictureindex、data 等)。
  • DeleteItem(handle)ExpandItem(h)CollapseItem(h)ExpandAll(h)SelectItem(h)
  • SetRedraw(false/true):批量插入前关重绘,性能更好。


下面用一个完整可运行示例,在窗口的 TreeView 控件 tv_1 上建一棵两层树:
前置:窗口 w_nav 上放置 TreeView 控件 tv_1(Picture Mask Color 设好,准备 2 个图标索引)。
步骤:在按钮 cb_build 的 clicked 事件贴入下方代码,运行窗口点按钮即可看到树与 MessageBox。
  1. // 关闭重绘,批量插入更高效
  2. tv_1.SetRedraw(false)
  3. // 示例输入:根节点与两个子系统名称
  4. string ls_root, ls_sys1, ls_sys2
  5. ls_root = '系统总览'
  6. ls_sys1 = '用户管理'
  7. ls_sys2 = '权限管理'
  8. // 示例输入:子功能名称
  9. string ls_sub1, ls_sub2
  10. ls_sub1 = '新增用户'
  11. ls_sub2 = '角色分配'
  12. // 根节点(0 表示挂到树根;pictureindex=1 用第 1 个图标)
  13. long ll_root
  14. ll_root = tv_1.InsertItemLast(0, ls_root, 1)
  15. // 第一级:用户管理
  16. long ll_sys
  17. ll_sys = tv_1.InsertItemLast(ll_root, ls_sys1, 1)
  18. // 第二级:用户管理下的子功能(pictureindex=2 用第 2 个图标)
  19. long ll_child
  20. ll_child = tv_1.InsertItemLast(ll_sys, ls_sub1, 2)
  21. // 第一级:权限管理
  22. ll_sys = tv_1.InsertItemLast(ll_root, ls_sys2, 1)
  23. // 第二级:权限管理下的子功能
  24. ll_child = tv_1.InsertItemLast(ll_sys, ls_sub2, 2)
  25. // 展开根节点
  26. tv_1.ExpandItem(ll_root)
  27. tv_1.SetRedraw(true)
  28. MessageBox('导航树', '已构建节点:根=' + ls_root + ',子项=' + ls_sub1 + '/' + ls_sub2)
复制代码

读取用户点的是哪个节点,在标准 TreeView 的 doubleclicked(或 selectionchanged)事件里取当前节点:
  1. // 在 tv_1 的 doubleclicked 事件
  2. long ll_handle
  3. TreeViewItem tvi
  4. ll_handle = this.FindItem(CurrentTreeItem!, 0)
  5. if ll_handle <= 0 then return
  6. this.GetItem(ll_handle, tvi)
  7. MessageBox('你点击了', tvi.label)
复制代码

原生写法的缺点很明显:节点数据、图标、点击事件全要手写,菜单一多就疲于 InsertItem。下面看 PBIDEA 怎么一行解决。




三、vo_treeview_pbidea:JSON 一行变导航树

vo_treeview_pbidea 是增强版 treeview 封装,核心是两个方法:


  • loadjson(uo_json json):把一棵 JSON 树渲染成 TreeView,内部自动 reset()、递归插入、再 ExpandAll
  • loadxml(uo_xml xml):同上,但来源是 XML 节点树(用 uo_xml 解析后的根节点)。


JSON 节点约定(源码 _of_loadjson 实际读取的字段):labeltext=显示文本;pageid=目标模块;tip=悬浮提示;obj/param=附带参数;items=子节点数组。还会按节点类型自动选图标:有子节点用 folder 图标(idx 1),叶子用 folder_item(idx 3),标签含 <VIP> 用 vip 图标(idx 4)。
前置:窗口上放置 vo_treeview_pbidea 控件,命名为 tv_nav(它是 treeview 派生可视对象,可直接拖入窗口;tmp\ 下放好四个图标文件)。
步骤:在窗口 open 事件或按钮 clicked 中贴入下面代码,运行即可看到整棵树。
  1. // 示例输入:导航树的 JSON 结构(label 显示文本;page 目标窗口/模块;tip 悬浮提示;items 子节点数组)
  2. string ls_json
  3. ls_json = '{"label":"系统总览","items":[' + &
  4.           '{"label":"用户管理","page":"w_user","tip":"维护系统账号","items":[' + &
  5.           '{"label":"新增用户","page":"w_user_add"},' + &
  6.           '{"label":"用户列表","page":"w_user_list"}]},' + &
  7.           '{"label":"权限管理","page":"w_role","items":[' + &
  8.           '{"label":"角色分配","page":"w_role_assign"}]}' + &
  9.           ']}'
  10. uo_json lj
  11. lj = create uo_json
  12. // 用 Parse 把字符串解析成 JSON 对象(返回 boolean,失败可 GetError() 看原因)
  13. if not lj.Parse(ls_json) then
  14.     MessageBox('错误', 'JSON 解析失败:' + lj.GetError())
  15.     destroy lj
  16.     return
  17. end if
  18. // 一键把 JSON 渲染成树(loadjson 内部会 reset、递归插入、ExpandAll)
  19. tv_nav.loadjson(lj)
  20. destroy lj
  21. MessageBox('导航树', '已加载 JSON 导航树,共 2 个一级菜单')
复制代码

关键差异vo_treeview_pbidea 没有 of_opentask 自动回调,它不会自己触发 ue_click。要拿到点击的菜单数据,在 doubleclicked(或 selectionchanged)事件里读 TreeViewItem.data——注意源码把节点信息存进了 tvi.data,是一段 JSON 字符串,键是 text / href / obj / param / tip(不是 page)。解析出来即可导航:
  1. // 在 tv_nav 的 doubleclicked 事件
  2. long ll_handle
  3. TreeViewItem tvi
  4. uo_json lj
  5. string ls_text, ls_href
  6. ll_handle = this.FindItem(CurrentTreeItem!, 0)
  7. if ll_handle <= 0 then return
  8. this.GetItem(ll_handle, tvi)
  9. if isnull(tvi.data) then return
  10. lj = create uo_json
  11. if not lj.Parse(tvi.data) then
  12.     destroy lj
  13.     return
  14. end if
  15. lj.get("text", ls_text)
  16. lj.get("href", ls_href)
  17. destroy lj
  18. // 实际项目里按 href 打开对应窗口;这里用 MessageBox 演示取到的值
  19. MessageBox('导航跳转', '菜单:' + ls_text + '~n目标模块:' + ls_href)
复制代码

如果你更喜欢"点击直接给四个参数"的回调风格,看下一节的 uo_treeview




四、uo_treeview:菜单文件 + ue_click 回调

uo_treeviewvo_treeview_pbidea 同源思路,但多了从文件加载ue_click 事件回调


  • of_retrieve_ex():读取内部变量 is_menu(默认 ..\demores\menu_cn.json)指向的菜单 JSON 文件,解析并建树。
  • of_parse_json(parentItem, uo_json):把一段 uo_json 递归插成树(公开方法,可自己拼数据后调用)。
  • of_reset():清空整棵树。
  • of_collapse_all() / of_collapse(handle):折叠。
  • of_opentask(handle):根据节点触发 ue_click(text, href, tip, param) 事件——这是它和 vo_treeview_pbidea 最大的不同

前置:窗口放置 uo_treeview 控件,命名为 tv_menu;程序目录放好菜单 JSON 文件(字段同第三节)。
步骤:窗口 open 事件贴入下面代码。
  1. // 示例输入:菜单 JSON 文件路径(默认是 ..\demores\menu_cn.json,可改成你自己的)
  2. string ls_file
  3. ls_file = '..\demores\menu_cn.json'
  4. // 赋给控件内部变量 is_menu,再调用 of_retrieve_ex 加载
  5. tv_menu.is_menu = ls_file
  6. tv_menu.of_retrieve_ex()
  7. MessageBox('菜单树', '已从文件加载菜单:' + ls_file)
复制代码

要拿到点击结果,在 doubleclicked 事件调用 of_opentask(handle),它会把节点信息拆好传进 ue_click
  1. // 在 tv_menu 的 doubleclicked 事件:触发 ue_click 回调
  2. this.of_opentask(handle)
复制代码
  1. // 在 tv_menu 的 ue_click(text, href, tip, param) 事件:写导航逻辑
  2. MessageBox('菜单点击', 'text=' + text + '~nhref=' + href + '~ntip=' + tip)
复制代码
注意:of_opentaskuo_treeview 独有,vo_treeview_pbidea 没有,后者请用第三节的"读 tvi.data"写法。




五、vo_menutree:菜单在数据库里就用它

vo_menutree 不走 TreeView 手动插入,而是DataWindow 派生(dataobject = d_menutree,一个树形 DataWindow)。当你的菜单本来就存在库表里(id / 父id / 名称),直接用 DataWindow 的树形呈现最自然:
前置:窗口放置 vo_menutree 控件(DataWindow 派生,dataobject 已设为树形 d_menutree);已连接 SQLCA。
步骤:窗口 open 事件贴入下面代码。
  1. // 示例输入:已连接好的事务对象 SQLCA
  2. // 树形 DataWindow 按 id / parent 层级自动展开
  3. vo_menutree_1.SetTransObject(SQLCA)
  4. vo_menutree_1.Retrieve()
  5. // 开启鼠标悬浮提示(需要 PBIDEA 的 uo_tooltip 运行库支持)
  6. vo_menutree_1.of_capture_mouse()
  7. MessageBox('菜单树', 'DataWindow 树形菜单已加载')
复制代码

它自带 ue_mouseover(row, column, ...) 事件,悬浮到某行会用 uo_tooltip 弹出 tip 提示(tip 取自数据窗的 tip 列)。适合"菜单即数据"的后台管理系统。




六、三个封装怎么选(对比)

维度原生 TreeViewuo_treeviewvo_treeview_pbideavo_menutree
数据来源手写 InsertItem菜单 JSON 文件JSON / XML 内存对象DataWindow + 库表
加载代码量少(of_retrieve_ex)最少(loadjson)最少(Retrieve)
点击回调自己写事件ue_click 四参读 tvi.data 解析DataWindow 行事件
拖拽排序自己实现不支持内置(drag/drop)不支持
图标换肤手动固定 four 图标ChangeImage/ChangeIconSizeDataWindow 图标
适用场景完全自定义文件式菜单内存 JSON/XML 导航数据库菜单





七、边界情况与常见坑


  • 图标文件必须存在uo_treeview / vo_treeview_pbidea 默认 picturename[] = {tmp\folder.ico, tmp\folder_open.ico, tmp\folder_item.ico, tmp\vip.ico}。程序目录没有 tmp\folder.ico 等会运行时报错。发布程序时记得带上这四个图标,或改用 ChangeImage(index, icon) 换成你自己的。
  • JSON 字段名要看清vo_treeview_pbidea 把节点信息存进 tvi.data 时用的是 text/href/obj/param/tip;你用 Parse(tvi.data) 后应 get("text",...) / get("href",...),别写成 get("page",...)(page 只在你原始 JSON 里,存进 data 后键名已换成 href)。
  • 两个封装的回调方式不同uo_treeviewof_opentaskue_clickvo_treeview_pbidea 没有 of_opentask,必须自己读 tvi.data。混用会"点了没反应"。
  • items 必须是数组_of_loadjson 对数组走 "/0""/1" 索引遍历;如果你把子节点写成对象而非数组,只有第一个会被识别。
  • 中文与 ToString:若你用 uo_jsonSet 动态拼 JSON 再 ToString() 保存,中文默认可能被转义;需要保留中文时用 ToString(true, true)(第二个参数 bEncodeChinese=true 不编码中文)。本文用 Parse 读字符串不受影响。
  • 拖拽排序要清理高亮vo_treeview_pbideadragdrop 里会 SetDropHighlight(0) 收尾;你自己写拖拽逻辑时别忘了复位,否则高亮残留。
  • vo_menutree 依赖树形 DataWindowd_menutree 必须是 TreeView 风格的数据窗(带层级),且 SQL 返回 id/父id/名称/tip 等列;普通 Grid 数据窗放进去不会成树。





八、进阶扩展


  • XML 导航树vo_treeview_pbidea.loadxml(uo_xml) 直接吃 uo_xml 解析后的根节点,适合拿现成配置文件渲染树(节点名+值+属性全展开)。
  • 动态换图标vo_treeview_pbidea.ChangeImage(3, 'tmp\myicon.ico') 把第 3 个图标换成自己的;ChangeIconSize(20) 统一调图标大小(均通过 pbidea.dll 生效)。
  • 拖拽排序vo_treeview_pbidea 已内置 begindrag/dragdrop/_of_moveitem,把节点拖到另一节点下即成为其子节点,松手后原节点删除——做可配置菜单顺序时直接可用。
  • 悬浮提示of_showtip(msg) 配合 uo_tooltip 可给任意节点加 tooltip;vo_menutreeue_mouseover 已自动接好。
  • 和权限结合:在 ue_click / doubleclicked 里先判断当前用户是否有 href 对应模块的权限,无权限 return 不打开,导航树天然就是权限入口。





九、小结

PBIDEA 把"导航树"这件烦心事拆成了三档:最省事vo_treeview_pbidea.loadjson(或 loadxml)一行渲染,点击读 tvi.data 解析;菜单在文件里uo_treeview.of_retrieve_ex + ue_click 四参回调;菜单在库表里vo_menutree 的树形 DataWindow 直接 Retrieve。底层都是标准 TreeView 方法,需要完全掌控时回退手写 InsertItem 也毫无障碍。记住图标文件要带齐、两个封装回调方式不同这两点,基本就不会踩坑。
共享共进共赢
Sharing And Win-win Results
SYBASEBBS - 免责申明1、欢迎访问“SYBASEBBS.COM”,本文内容及相关资源来源于网络,版权归版权方所有!本站原创内容版权归本站所有,请勿转载!
2、本文内容仅代表作者观点,不代表本站立场,作者自负,本站资源仅供学习研究,请勿非法使用,否则后果自负!请下载后24小时内删除!
3、本文内容,包括但不限于源码、文字、图片等,仅供参考。本站不对其安全性,正确性等作出保证。但本站会尽量审核会员发表的内容。
4、如本帖侵犯到任何版权问题,请立即告知本站 ,本站将及时删除并致以最深的歉意!客服邮箱:admin@sybasebbs.com
您需要登录后才可以回帖 登录 | 站点注册

本版积分规则

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

Mail To:Admin@SybaseBbs.com

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

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

GMT+8, 2026-9-4 13:08 , Processed in 0.024949 second(s), 9 queries , MemCached On.

Powered by Discuz! X3.5

© 2001-2026 Discuz! Team.

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