在我们一般的应用系统里面,由于系统是面向不同类型的用户,我们所看到的菜单会越来越多,多一点的甚至上百个,但是我们实际工作接触的菜单可能就是那么几个,那么对于这种庞大的菜单体系,寻找起来非常不便。因此对菜单的个性化配置就显得尤为重要,本篇随笔就是基于这样的理念,提供用户对可见菜单进行一个动态配置,只选自己喜欢、常用的菜单显示出来即可,菜单的配置存储在数据库里面,在不同的客户端体验都是一样。本篇随笔主要介绍实现这样的功能的一个完整思路,部分代码逻辑可供参考。
在我们有些软件里面,我们可能在界面上顶部放置菜单,也可能在界面的左侧放置树形列表菜单,这种情况都有可能,本篇摘取其中之一,左侧菜单进行一个介绍菜单的配置处理。
例如我们在左侧根据用户权限展示相关的菜单信息,动态生成整个列表展示,大致的界面效果如下所示。
然后在功能列表上提供一个右键的菜单进行菜单的刷新、配置管理,如下界面所示。
通过配置功能,我们让用户进入一个配置管理界面,在其中配置显示自己感兴趣的菜单,然后进行保存即可,保存后同时刷新界面的功能菜单显示。
以上几个界面效果就是为了介绍整个菜单配置管理的一般过程,之所以把界面效果放在前面介绍,就是能够让我们有一个类似原型设计方式的感性认识,了解了相关的处理过程,我们就可以着手通过编码的方式来实现这个处理逻辑了。
上面介绍了大概的界面效果,有了参考,我们可以把它的实现思路通过代码实现出来。
首先我们需要了解,用户配置可以通过XML保存在本地,也可以通过数据库存储保存在服务器,后者在分布式的客户端的时候,可以处处一样,这样就不会造成体验上的差异,因此我们这里采用存储在数据库的方案。
这个存储我们沿用我之前介绍过的配置管理组件(SettingsProvider.net),我在随笔《Winform开发框架之参数配置管理功能实现-基于SettingsProvider.net的构建》中对它的使用进行了详细的介绍。
这个配置管理组件SettingsProvider.net使用起来也是比较方便的,可以选择存储在本地的对象,也可以选择存储在数据库的存储对象。
首先我们先定义一个存储的参数类,这个是使用这个组件所必须的存储对象信息,如下代码所示。
/// <summary> /// 用来控制人员管理显示菜单的参数配置 /// </summary> public class UserMenuParameter { [DefaultValue("")] [Description("用户ID")] public string UserID { get; set; } [Description("用户设置可见的菜单")] public Dictionary<string, bool> VisibleDict { get; set; } }
需要获取或存储这个对象信息的时候,我们初始化几个管理类,如下代码所示。
//参数存储所需的相关对象 private SettingsProvider settings; private ISettingsStorage store; private UserMenuParameter parameter;
然后在配置管理界面窗体里面,初始化这几个对象,如下代码所示。
// PortableStorage: 在运行程序目录创建一个setting的文件记录参数数据 // DatabaseStorage:在数据库TB_UserParameter表存储用户配置参数 store = new DatabaseStorage(LoginUserInfo.ID); settings = new SettingsProvider(store); parameter = settings.GetSettings<UserMenuParameter>();
这样我们就可以根据用户的ID,获取对应记录的信息并转换为相关的对象了,如果我们需要把修改的信息写会到存储介质里面,代码如下所示。
try { parameter = settings.GetSettings<UserMenuParameter>(); parameter.VisibleDict = dict; parameter.UserID = LoginUserInfo.ID; settings.SaveSettings<UserMenuParameter>(parameter); ProcessDataSaved(sender, e);//触发外部事件 this.DialogResult = System.Windows.Forms.DialogResult.OK; } catch (Exception ex) { LogHelper.Error(ex); MessageDxUtil.ShowError(ex.Message); return; }
解决了参数的获取及存储功能后,我们需要编写一个界面来管理用户的菜单配置,也就是我们前面介绍的菜单配置管理界面。
我们这个界面的定义代码如下所示。
其中参数的数据存储就是应用了前面介绍的代码,这里需要根据用户的配置项初始化树形菜单的显示处理,通过InitTree的函数实现菜单的显示。
在显示菜单前,我们先介绍一下功能菜单显示的规则,仅当参数存在对应记录,并且该记录显式设置不可见,菜单才不可见,否则默认菜单是可以看到的。
这样确保了,在参数没有配置前,所有的菜单对当前用户是可见的,只有用户设置为不不可见,该菜单才不显示为不可见。
/// <summary> /// 获取菜单是否可见。 /// 仅当参数存在对应记录,并且该记录显式设置不可见,菜单才不可见,否则默认菜单是可以看到的。 /// </summary> /// <param name="id">菜单ID</param> /// <returns></returns> private bool GetVisibleMenu(string id) { bool result = true; if (parameter != null) { var dict = parameter.VisibleDict; if(dict != null && dict.ContainsKey(id)) { result = dict[id]; } } return result; }
显示菜单的相关处理逻辑,就是根据上面的判断,然后确定是否勾选记录,如下代码所示。
存储用户勾选的记录的时候,我们需要遍历整个树节点,判断勾选了那些选项,然后把它保存数据库即可。
/// <summary> /// 递归获取选中的树节点集合 /// </summary> /// <param name="node">树节点</param> /// <param name="dict">字典集合</param> /// <returns></returns> private Dictionary<string, bool> GetTreeSelection(TreeNode node, Dictionary<string, bool> dict) { if (node.Tag != null) { var check = node.Checked; var menuId = string.Concat(node.Tag); if(!dict.ContainsKey(menuId)) { dict.Add(menuId, check); } } foreach (TreeNode child in node.Nodes) { GetTreeSelection(child, dict); } return dict; }
参数的保存操作如下所示。
/// <summary> /// 保存用户配置信息 /// </summary> private void btnOK_Click(object sender, EventArgs e) { //获取用户勾选的树列表,存放在字典集合里面 var dict = new Dictionary<string, bool>(); foreach(TreeNode node in this.treeView1.Nodes) { GetTreeSelection(node, dict); } try { //重新获取参数信息,并设置新值后保存 parameter = settings.GetSettings<UserMenuParameter>(); parameter.VisibleDict = dict; parameter.UserID = LoginUserInfo.ID; settings.SaveSettings<UserMenuParameter>(parameter); ProcessDataSaved(sender, e);//触发外部事件 this.DialogResult = System.Windows.Forms.DialogResult.OK; } catch (Exception ex) { LogHelper.Error(ex); MessageDxUtil.ShowError(ex.Message); return; } }
以上处理完成后,我们在主界面的工具栏右键菜单添加一个菜单项,用来进入配置界面的,如下逻辑代码所示。
private void tool_MenuSetting_ItemClick(object sender, DevExpress.XtraBars.ItemClickEventArgs e) { MenuSetting(); } /// <summary> /// 配置菜单项 /// </summary> private void MenuSetting() { FrmMenuSetting dlg = new FrmMenuSetting(); dlg.OnDataSaved += (s, arg) => { //用户保存参数后,提示用户更新树形列表 InitToolbar(); }; dlg.ShowDialog(); }
这样界面配置参数并保存后,界面的树形菜单会及时得到更新处理。
另外,我们主界面的树形列表,也要根据配置参数的信息作相关的调整,如果用户配置了不显示某个菜单,那么主界面也要根据配置参数控制显示。
以上就是整个菜单列表的动态个性化配置管理的整体思路和实现步骤代码,主要的界面考量还是以用户的视觉来考虑界面的布局和功能,如果在几百个菜单项中寻找几个常用的菜单,每次是一个比较耗时无聊的操作,因此提供一个个性化的界面,根据工作情况的不同,显示一些和自己相关的功能即可。
例如有些情况下,我们的菜单显示,希望通过工具栏的方式进行控制显示,如下界面效果所示。
那么配置维护界面还是差不多,只是我们控制工具栏的显示逻辑有所不同而已,对于RibbonPage及其功能菜单的动态生成处理如下所示。
本篇随笔主要还是希望读者借鉴配置存储和菜单个性化管理的思路,具体的逻辑会因用户界面的不同,使用的控件不同而有所差异,不过总体思路是一致的即可。
例如有些参数的配置管理,可以统一使用一个配置管理界面进行维护,如我之前的随笔介绍的界面功能一样。
希望本篇随笔对你有所启发,感谢耐心阅读。