我在之前很多文章里面,介紹過Winform主界面的開發,基本上都是標准的界面,在頂部放置工具欄,中間區域則放置多文檔的內容,但是在頂部菜單比較多的時候,就需要把菜單分為幾級處理,如可以在頂部菜單放置一二級菜單,這種方式在一般功能點不算太多的情況下,呈現的界面效果較為直觀、也較為美觀。不過隨着一些系統功能的增多,這種方式可能就會顯得工具欄比較擁擠,那么我們是否可以在左側放置一個樹形列表,這樣通過樹形列表的收縮折疊,就可以放置非常多的菜單功能了。
1、菜單的樹形列表展示
一般情況下,樹形列表的顯示可以分為多個節點,節點可以收縮也可以展開,當然節點是有不同的圖標的了。這樣就可以把很多功能點整合在一個樹列表里面了,樹的節點也可以分為很多級別,很多層次

如果我們想按照業務的范疇來區分,也可以分為多個模塊展示,類似選項卡的方式,一個模塊的功能菜單列表集合在一起展示,如下所示。

上面這樣的折疊展示,有利於業務范疇的區分,並且可以讓樹菜單菜單不會很大,是一種比較好的界面組織方式。
2、菜單的動態配置管理
上面介紹了樹形菜單的展示,以及如何組織菜單的內容,做好這些,就為我們奠定了界面菜單組織的雛形了。
那么問題來了,我們一般是需要根據系統創建很多菜單的,如果是能通過配置的方式,這樣才能較好的管理這些菜單,而且可以動態給菜單指定權限,實現不同角色用戶的權限控制。
那么我們就需要在系統里面引入一個菜單管理模塊,實現菜單的配置管理功能,方便我們后面的動態創建菜單操作。

通過菜單的配置,我們可以指定菜單的圖標,是否可見,是否展開,權限控制點,以及菜單觸發點擊后,處理的窗體對象等信息,有了這些基礎信息,我們就很方便把菜單在樹形列表里面進行合適、美觀的展示了。
3、菜單動態構建的實現
前面介紹了,如何在數據庫里面對菜單數據進行了存儲,這樣我們就可以在系統主界面里面,動態的構建屬性列表進行菜單的展示操作了。
首先,我們需要在設計時刻對主界面的布局進行一定的設計,放置一些初始化的樹形列表,方便查看效果。至於里面的內容,我們可以根據數據庫的菜單配置,動態從數據庫里面獲取菜單信息,在左側樹形列表里面進行構建。

我們可以通過一個輔助類進行菜單的動態創建,如下所示。
private void InitToolbar() { TreeMenuHelper helper = new TreeMenuHelper(this, this.nvBarMenu, this.imageList1); helper.Init(); }
也就是輔助類,傳入當前窗體,以及左側的導航控件等參數后,我們在輔助類里面封裝對應的動態構建菜單的邏輯處理。
首先我們動態創建的開始,先要清空原來控件展示的菜單內容,並重新從數據庫里面獲取,如下代碼所示。
//清空所有導航控件的內容 barControl.Controls.Clear(); barControl.Groups.Clear(); barControl.Items.Clear(); this.imageList.Images.Clear(); //限定顯示幾個導航選項卡 barControl.NavigationPaneMaxVisibleGroups = 3; //約定菜單共有3級,第一級為大的類別,第二級為小模塊分組,第三級為具體的菜單 List<MenuNodeInfo> menuList = BLLFactory<SysMenu>.Instance.GetTree(Portal.gc.SystemType); if (menuList.Count == 0) return;
然后我們會對菜單進行遍歷,並判斷是否具有對應的權限點,如果沒有對應的權限,那么對應菜單的子菜單也不會進一步展示。
//遞歸遍歷所有的菜單,進行分級展示 foreach (MenuNodeInfo firstInfo in menuList) { //如果沒有菜單的權限,則跳過 if (!Portal.gc.HasFunction(firstInfo.FunctionId)) continue;
創建菜單的時候,我們注意到整個菜單項是動態構建的,因此我們需要根據NavBarControl的控件屬性,動態構建對應的選項卡NavBarGroup、展示容器NavBarGroup、樹形對象TreeView、樹形節點TreeNode等內容,如下代碼所示。
TreeView treeView = new TreeView(); treeView.Dock = DockStyle.Fill; treeView.ImageList = this.imageList; treeView.ItemHeight = 30;//設置高度,顯示更美觀 NavBarGroupControlContainer container = new NavBarGroupControlContainer(); container.Size = new System.Drawing.Size(213, 412); container.Controls.Add(treeView); barControl.Controls.Add(container); //加載圖標 this.imageList.Images.Add(LoadIcon(firstInfo.Icon)); int index = this.imageList.Images.Count - 1;//最后一個序號 NavBarGroup group = new NavBarGroup(); group.Caption = firstInfo.Name; group.ControlContainer = container; group.Expanded = true; group.GroupClientHeight = 410; group.GroupStyle = NavBarGroupStyle.ControlContainer; group.LargeImageIndex = index; group.SmallImageIndex = index; barControl.Groups.Add(group); //創建一級列表 TreeNode pNode = new TreeNode(); pNode.Text = firstInfo.Name; pNode.Tag = firstInfo.WinformType; pNode.ImageIndex = index; pNode.SelectedImageIndex = index; treeView.Nodes.Add(pNode); //遞歸創建子列表 AddTreeItems(pNode, firstInfo.Children);
通過遞歸的方式,我們就很容易遞歸構建了所有層次的樹形菜單,並進行合適的展示了。
菜單的單擊事件,我們通過一個函數代碼實現對它進行處理就可以了。
//處理樹形菜單的點擊操作,如果TAG存在,則解析並加載對應的頁面到多文檔里面 treeView.AfterSelect += (sender, e) => { string tag = e.Node.Tag as string; if (!string.IsNullOrEmpty(tag)) { LoadPlugInForm(tag); } };
這里面就是對它的AfterSelect 事件進行處理,實現我們動態加載窗體對象到多文檔界面的處理了。
其中加載窗體是根據菜單配置的選項,動態構建界面出來的,具體分析代碼如下所示。
/// <summary> /// 加載插件窗體 /// </summary> private void LoadPlugInForm(string typeName) { try { string[] itemArray = typeName.Split(new char[] { ',', ';' }); string type = itemArray[0].Trim(); string filePath = itemArray[1].Trim();//必須是相對路徑 //判斷是否配置了顯示模式,默認窗體為Show非模式顯示 string showDialog = (itemArray.Length > 2) ? itemArray[2].ToLower() : ""; bool isShowDialog = (showDialog == "1") || (showDialog == "dialog"); string dllFullPath = Path.Combine(Application.StartupPath, filePath); Assembly tempAssembly = System.Reflection.Assembly.LoadFrom(dllFullPath); if (tempAssembly != null) { Type objType = tempAssembly.GetType(type); if (objType != null) { LoadMdiForm(this.mainForm, objType, isShowDialog); } } } catch (Exception ex) { LogTextHelper.Error(string.Format("加載模塊【{0}】失敗,請檢查書寫是否正確。", typeName), ex); } }
加載多文檔的操作,就是在集合里面判斷是否存在,如果沒有存在就創建,否則就激活顯示即可,具體處理如下所示。
/// <summary> /// 唯一加載某個類型的窗體,如果存在則顯示,否則創建。 /// </summary> /// <param name="mainDialog">主窗體對象</param> /// <param name="formType">待顯示的窗體類型</param> /// <returns></returns> public Form LoadMdiForm(Form mainDialog, Type formType, bool isShowDialog) { Form tableForm = null; bool bFound = false; if (!isShowDialog) //如果是模態窗口,跳過 { foreach (Form form in mainDialog.MdiChildren) { if (form.GetType() == formType) { bFound = true; tableForm = form; break; } } } //沒有在多文檔中找到或者是模態窗口,需要初始化屬性 if (!bFound || isShowDialog) { tableForm = (Form)Activator.CreateInstance(formType); //如果窗體集成了IFunction接口(第一次創建需要設置) IFunction function = tableForm as IFunction; if (function != null) { //初始化權限控制信息 function.InitFunction(Portal.gc.LoginUserInfo, Portal.gc.FunctionDict); //記錄程序的相關信息 function.AppInfo = new AppInfo(Portal.gc.AppUnit, Portal.gc.AppName, Portal.gc.AppWholeName, Portal.gc.SystemType); } } if (isShowDialog) { tableForm.ShowDialog(); } else { tableForm.MdiParent = mainDialog; tableForm.Show(); } tableForm.BringToFront(); tableForm.Activate(); return tableForm; }
4、系統界面的總體效果
最后,為了更好理解整個動態菜單的界面效果,貼出幾個做好的界面展示圖,供參考學習。
1)標准界面的處理方式

2)樹形列表界面的處理方式

打開多文檔頁面后如下所示。

