曾经在一些书上看到用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_rootitem[ai_item_serial_no] = create m_menu_item
//将新建的菜单对象挂接到已有菜单对象上
am_obj = lam_rootitem[ai_item_serial_no]
//下面两句用于显示建立好的菜单
lam_rootHide()//隐藏菜单对象
lam_rootShow()//显示菜单对象
将上面的语句放在一个递归过程中就可以建立起整个的菜单结构
在建立菜单的过程中需要得到菜单的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既可以保持在菜单建立期间的数据安全不受数据库影响又可以提高效率省去对数据库的反复读写操作
)因为菜单的重要性使得出错控制在菜单建立尤为重要我们在递归建立菜单时要考虑尽可能多的潜在错误谁也不能保证数据库中的菜单结构数据不出错虽然正确定义不是建立模块的事尤其菜单的二位递进的分层数据结构若有一处错误可能导致整个建立过程失败更糟糕的会发生程序异常退出所以程序在设计出错处理时应当考虑是终止进程还是跳过错误的环节继续进行我建议在设计程序时应但兼具一定的冗余度和纠错能力即遇到错误的数据能够根据环境修正为正确的值对于可以忽略的一些小问题为提高效率不作处理
需要指出的是经过反复测试发现对于菜单的属性如果是字符类型则不能赋空值如果没有应当是空字符串如果是整数类型也不能赋空值如果没有应当是某个缺省整数否则程序会报异常错误然后退出
由此可见反复测试是非常重要的不仅能发现语法错误和确保算法的正确更能找出许多我们难以推断的错误
三对弹出菜单的特殊处理
由于弹出菜单的对象定义和调用方式与主菜单的不同需要进行一些特殊处理首先定义一个菜单实例对象该对象需有且只有一个根菜单所有弹出的菜单项都挂接在根菜单项后要在窗口的鼠标右键事件中调用弹出菜单而主菜单则在窗口初始化事件调用调用弹出菜单之前需要知道弹出点的XY坐标然后用popmenu()函数显示出来
四菜单响应事件的处理
由于菜单的响应事件在数据结构中定义好了在建立菜单之后用户点击某菜单时候只需要获得菜单的句柄就知道是触发了哪个菜单而后在数据库中找到对应的事件定义就可以开始执行动作了重要的是句柄如何得到菜单的句柄就是itemid在菜单所在窗口定义一个自定义事件ue_mouse_clickedEVENT_IDpbm_menuselect此事件中有两个参数可用itemid和flagitemid即被触发菜单对象的句柄flag是对应于windows消息号的标志当此标志不等于时就是触发了菜单事件所以我们可以定义一个实例变量保存itemid就可以调用菜单事件了还有一个关键问题何时触发事件ue_mouse_clicked呢在用户定义的菜单实例对象的clicked事件中写以下代码
if Isvalid(iw_win) then
messageStringParm = thisis_ItemID//将itemid作为消息传递
iw_winpostEvent( ue_menuitemclicked )//触发窗口事件处理消息
end if
iw_win定义的窗口实例变量
is_ItemID用以保存菜单itemid的字符串类型的实例变量
postEvent是把响应处理放在菜单事件的最后以免妨碍之前定义的动作
到此整个菜单以及菜单的各种属性定义响应设计已经完成有兴趣的读者可以与我联系探讨更好的方法