在PowerBuilder中实现动态菜单的方法

 

一、定义菜单数据结构

  实现动态菜单,首先设计合理的菜单数据结构,其数据源可以是任何DBMS,甚至可以是TXT文本文件(只要能建立好合理的分层结构)。

  菜单如同一个树形控件,有着分层的顺序结构,所以在定义数据结构时,应当选择一种能够形象地表示父子、兄弟关系的模型。而能够最好反映菜单结构的控件就是树形控件:treeview,并且定义按照二位递进的数据结构形式,即:以级别确定层数,以序号确定兄弟关系,以二位递进确定父子关系。例如,如图所示的菜单的对应数据结构如下。

  这样的菜单结构,在建立菜单结构时,非常适合用递归的算法,那么我们可以按照树的遍历算法建立一个树形结构的菜单对象。

  接下来,定义菜单数据结构。菜单数据结构应当包含以下基本元素:菜单名、菜单类型、菜单序号、菜单项文本、菜单项id、菜单项的执行代码、菜单显示风格。如下表说明:

二、动态创建菜单

  1、流程图

  2、流程说明:

  如上图,整个建立菜单的过程分成两部分:初始化菜单和设置菜单属性。初始化菜单即是以递归的算法从数据源中读取菜单数据,每读一个菜单项建立一个菜单项对象,利用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是从0开始依次递增1,子项菜单的itemid是从10000开始依次递增1。由此按照递归算法,生成每层每个菜单项的itemid,并存入数据库中。

  设置菜单显示风格,是在菜单建立后设置三种显示风格:文字风格、图片风格、文字图片混合的显示方式。为了提高效率,在设置每个菜单风格时,不对所有父项菜单、不可视菜单项和没有定义显示图片的菜单项进行设置,因为文字风格是默认风格不必更改。这部分程序员主要用到三个API函数:

  Getsubmenu:用于得到指定菜单项的句柄。
  SetMenuItemBitmaps:用于设置文字显示风格或设置图片风格,两种情况的区别在于该函数的最后两位若为0,则是去掉菜单项上的位图;最后两位若是图片句柄,则是在菜单项上添加位图。
  ModifyMenu:用于设置图片显示风格。

  经过反复测试,发现如果指定的显示图片名为“***bmp”等不合法名称,则显示出的效果是一个分割符。

  3、在整个菜单建立过程,需要重点设计的是程序算法、数据存取的方式和出错控制。

  1)、程序算法主要指递归算法,一般递归有两种算法,即FOR循环的方法和DO…while循环方法。两者都是循环算法,但是效率不同。建议用户根据自己的能力选择方法,切忌不能写成死循环。For循环的方式比较简单直观,循环控制遍历的次数,循环内再调用本身,实现递归调用。DO…while循环方法主要在循环内判断叶子或枝子(即父亲节点),对叶子和枝子进行分别处理,内部也要调用本身,实现递归调用。

  2)、选择合理安全的数据存取方式,对于稳定建立菜单也很重要。定义一个datastore(数据存储)对象,在初始化菜单时候,将从数据库中提取的所有数据存入该datastore对象,然后不再对数据库进行任何操作,直到需要结束时将变更的菜单数据(如,itemid)以datastore的update形式提交数据库。在此之前所有需要从datastore得到的数据,用过滤的方式得到,即用setfilter()和filter()函数,一定要注意的是:按照结对编程的规则,在过滤并使用完datastore中数据后,一定再写一对过滤条件为空字符串的过滤,如下:
  setfilter(“条件1”)
  filter()
  ……处理过程……
  setfilter(“”)
  filter()

  这样也可以将数据及时还原到初始状态,以便下一个模块调用。

  利用datastore,既可以保持在菜单建立期间的数据安全,不受数据库影响;又可以提高效率,省去对数据库的反复读写操作。

  3)、因为菜单的重要性,使得出错控制在菜单建立尤为重要。我们在递归建立菜单时,要考虑尽可能多的潜在错误,谁也不能保证数据库中的菜单结构数据不出错,虽然正确定义不是建立模块的事。尤其菜单的二位递进的分层数据结构,若有一处错误,可能导致整个建立过程失败,更糟糕的会发生程序异常退出。所以程序在设计出错处理时,应当考虑是终止进程,还是跳过错误的环节继续进行。我建议,在设计程序时应但兼具一定的冗余度和纠错能力。即遇到错误的数据能够根据环境修正为正确的值,对于可以忽略的一些小问题,为提高效率不作处理。

  需要指出的是,经过反复测试,发现对于菜单的属性,如果是字符类型则不能赋空值,如果没有应当是空字符串,如果是整数类型也不能赋空值,如果没有应当是某个缺省整数。否则程序会报异常错误,然后退出。

  由此可见,反复测试是非常重要的,不仅能发现语法错误和确保算法的正确,更能找出许多我们难以推断的错误。

三、对弹出菜单的特殊处理

  由于弹出菜单的对象定义和调用方式与主菜单的不同,需要进行一些特殊处理。首先定义一个菜单实例对象,该对象需有且只有一个根菜单,所有弹出的菜单项都挂接在根菜单项后。要在窗口的鼠标右键事件中,调用弹出菜单,而主菜单则在窗口初始化事件调用。调用弹出菜单之前需要知道弹出点的X、Y坐标,然后用popmenu()函数显示出来。

四、菜单响应事件的处理

  由于菜单的响应事件在数据结构中定义好了,在建立菜单之后,用户点击某菜单时候,只需要获得菜单的句柄,就知道是触发了哪个菜单。而后在数据库中找到对应的事件定义,就可以开始执行动作了。重要的是句柄如何得到,菜单的句柄就是itemid,在菜单所在窗口定义一个自定义事件:ue_mouse_clicked,EVENT_ID:pbm_menuselect。此事件中有两个参数可用:itemid和flag,itemid即被触发菜单对象的句柄,flag是对应于windows消息号的标志,当此标志不等于65535时就是触发了菜单事件。所以我们可以定义一个实例变量,保存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:是把响应处理放在菜单事件的最后,以免妨碍之前定义的动作。

你可能感兴趣的:(在PowerBuilder中实现动态菜单的方法)