曾經在一些書上看到用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是把響應處理放在菜單事件的最後以免妨礙之前定義的動作
到此整個菜單以及菜單的各種屬性定義響應設計已經完成有興趣的讀者可以與我聯系探討更好的方法
From:http://tw.wingwit.com/Article/program/SQL/201311/16396.html