在PowerBuilder中实现动态菜单的方法
曾经在一些书上看到用C语言实现动态菜单的方法 需要调用大量的API函数 但是这里我想换一种方法 借助PowerBuilder提供的属性和递归算法实现动态菜单的创建过程 需要指出的是 这里讨论的动态菜单是已经在外部数据源中定义好菜单结构 而菜单对象没有有任何菜单项 需要由程序生成各个定义好的菜单项
一 定义菜单数据结构
实现动态菜单 首先设计合理的菜单数据结构 其数据源可以是任何DBMS 甚至可以是TXT文本文件(只要能建立好合理的分层结构)
菜单如同一个树形控件 有着分层的顺序结构 所以在定义数据结构时 应当选择一种能够形象地表示父子 兄弟关系的模型 而能够最好反映菜单结构的控件就是树形控件 treeview 并且定义按照二位递进的数据结构形式 即 以级别确定层数 以序号确定兄弟关系 以二位递进确定父子关系 例如 如图所示的菜单的对应数据结构如下
这样的菜单结构 在建立菜单结构时 非常适合用递归的算法 那么我们可以按照树的遍历算法建立一个树形结构的菜单对象
接下来 定义菜单数据结构 菜单数据结构应当包含以下基本元素 菜单名 菜单类型 菜单序号 菜单项文本 菜单项id 菜单项的执行代码 菜单显示风格 如下表说明
二 动态创建菜单
流程图
流程说明
如上图 整个建立菜单的过程分成两部分 初始化菜单和设置菜单属性 初始化菜单即是以递归的算法从数据源中读取菜单数据 每读一个菜单项建立一个菜单项对象 利用powerbuilder中create方法一级一级建立菜单 首先定义一个菜单实例对象 这里的菜单是指主菜单 而不是弹出菜单 由于而者的区别 对于弹出菜单的处理在后面介绍 菜单建立的核心原理很简单 只有四句 创建菜单对象 挂接菜单项目 先隐藏后显示菜单对象 如下
integer ai_item_serial_no //序号 作为递归的函数传入参数 menu am_obj //菜单对象 作为递归的函数传入参数 m_menu_item lam_root //菜单对象 m_menu_item是预先定义的一 //个菜单对象 该对象没有一个菜单项 //创建菜单对象 lam_root item[ai_item_serial_no] = create m_menu_item //将新建的菜单对象 挂接到已有菜单对象上 am_obj = lam_root item[ai_item_serial_no] //下面两句用于显示建立好的菜单 lam_root Hide() //隐藏菜单对象 lam_root Show() //显示菜单对象
将上面的语句放在一个递归过程中 就可以建立起整个的菜单结构
在建立菜单的过程中需要得到菜单的itemid 该属性是用来捕获菜单响应动作的唯一标示 只有知道的菜单的itemid 才知道是触发了哪个菜单项的事件
得到菜单项itemid的方法 在不同系统经过反复测试之后 发现一个规律 父项菜单的itemid是从 开始依次递增 子项菜单的itemid是从 开始依次递增 由此按照递归算法 生成每层每个菜单项的itemid 并存入数据库中
设置菜单显示风格 是在菜单建立后设置三种显示风格 文字风格 图片风格 文字图片混合的显示方式 为了提高效率 在设置每个菜单风格时 不对所有父项菜单 不可视菜单项和没有定义显示图片的菜单项进行设置 因为文字风格是默认风格不必更改 这部分程序员主要用到三个API函数
Getsubmenu 用于得到指定菜单项的句柄 SetMenuItemBitmaps 用于设置文字显示风格或设置图片风格 两种情况的区别在于该函数的最后两位若为 则是去掉菜单项上的位图 最后两位若是图片句柄 则是在菜单项上添加位图 ModifyMenu 用于设置图片显示风格
经过反复测试 发现如果指定的显示图片名为 ***bmp 等不合法名称 则显示出的效果是一个分割符
在整个菜单建立过程 需要重点设计的是程序算法 数据存取的方式和出错控制
) 程序算法主要指递归算法 一般递归有两种算法 即FOR循环的方法和DO…while循环方法 两者都是循环算法 但是效率不同 建议用户根据自己的能力选择方法 切忌不能写成死循环 For循环的方式比较简单直观 循环控制遍历的次数 循环内再调用本身 实现递归调用 DO…while循环方法主要在循环内判断叶子或枝子(即父亲节点) 对叶子和枝子进行分别处理 内部也要调用本身 实现递归调用
) 选择合理安全的数据存取方式 对于稳定建立菜单也很重要 定义一个datastore(数据存储)对象 在初始化菜单时候 将从数据库中提取的所有数据存入该datastore对象 然后不再对数据库进行任何操作 直到需要结束时将变更的菜单数据(如 itemid)以datastore的update形式提交数据库 在此之前所有需要从datastore得到的数据 用过滤的方式得到 即用setfilter()和filter()函数 一定要注意的是 按照结对编程的规则 在过滤并使用完datastore中数据后 一定再写一对过滤条件为空字符串的过滤 如下 setfilter( 条件 ) filter() ……处理过程…… setfilter( ) filter()
这样也可以将数据及时还原到初始状态 以便下一个模块调用
利用datastore 既可以保持在菜单建立期间的数据安全 不受数据库影响 又可以提高效率 省去对数据库的反复读写操作
) 因为菜单的重要性 使得出错控制在菜单建立尤为重要 我们在递归建立菜单时 要考虑尽可能多的潜在错误 谁也不能保证数据库中的菜单结构数据不出错 虽然正确定义不是建立模块的事 尤其菜单的二位递进的分层数据结构 若有一处错误 可能导致整个建立过程失败 更糟糕的会发生程序异常退出 所以程序在设计出错处理时 应当考虑是终止进程 还是跳过错误的环节继续进行 我建议 在设计程序时应但兼具一定的冗余度和纠错能力 即遇到错误的数据能够根据环境修正为正确的值 对于可以忽略的一些小问题 为提高效率不作处理
需要指出的是 经过反复测试 发现对于菜单的属性 如果是字符类型则不能赋空值 如果没有应当是空字符串 如果是整数类型也不能赋空值 如果没有应当是某个缺省整数 否则程序会报异常错误 然后退出
由此可见 反复测试是非常重要的 不仅能发现语法错误和确保算法的正确 更能找出许多我们难以推断的错误
三 对弹出菜单的特殊处理
由于弹出菜单的对象定义和调用方式与主菜单的不同 需要进行一些特殊处理 首先定义一个菜单实例对象 该对象需有且只有一个根菜单 所有弹出的菜单项都挂接在根菜单项后 要在窗口的鼠标右键事件中 调用弹出菜单 而主菜单则在窗口初始化事件调用 调用弹出菜单之前需要知道弹出点的X Y坐标 然后用popmenu()函数显示出来
四 菜单响应事件的处理
由于菜单的响应事件在数据结构中定义好了 在建立菜单之后 用户点击某菜单时候 只需要获得菜单的句柄 就知道是触发了哪个菜单 而后在数据库中找到对应的事件定义 就可以开始执行动作了 重要的是句柄如何得到 菜单的句柄就是itemid 在菜单所在窗口定义一个自定义事件 ue_mouse_clicked EVENT_ID pbm_menuselect 此事件中有两个参数可用 itemid和flag itemid即被触发菜单对象的句柄 flag是对应于windows消息号的标志 当此标志不等于 时就是触发了菜单事件 所以我们可以定义一个实例变量 保存itemid 就可以调用菜单事件了 还有一个关键问题 何时触发事件ue_mouse_clicked呢 在用户定义的菜单实例对象的clicked事件中写以下代码 if Isvalid(iw_win) then message StringParm = this is_ItemID //将itemid作为消息传递 iw_win postEvent( ue_menuitemclicked ) //触发窗口事件 处理消息 end if iw_win 定义的窗口实例变量 is_ItemID 用以保存菜单itemid的字符串类型的实例变量 postEvent 是把响应处理放在菜单事件的最后 以免妨碍之前定义的动作
lishixinzhi/Article/program/SQL/201311/16396