Winform界面中实现菜单列表的动态个性化配置管理
在我们一般的应用系统里面,由于系统是面向不同类型的用户,我们所看到的菜单会越来越多,多一点的甚至上百个,但是我们实际工作接触的菜单可能就是那么几个,那么对于这种庞大的菜单体系,寻找起来非常不便。因此对菜单的个性化配置就显得尤为重要,本篇随笔就是基于这样的理念,提供用户对可见菜单进行一个动态配置,只选自己喜欢、常用的菜单显示出来即可,菜单的配置存储在数据库里面,在不同的客户端体验都是一样。本篇随笔主要介绍实现这样的功能的一个完整思路,部分代码逻辑可供参考。
1、 菜单列表的动态个性化配置的过程
在我们有些软件里面,我们可能在界面上顶部放置菜单,也可能在界面的左侧放置树形列表菜单,这种情况都有可能,本篇摘取其中之一,左侧菜单进行一个介绍菜单的配置处理。
例如我们在左侧根据用户权限展示相关的菜单信息,动态生成整个列表展示,大致的界面效果如下所示。

然后在功能列表上提供一个右键的菜单进行菜单的刷新、配置管理,如下界面所示。

通过配置功能,我们让用户进入一个配置管理界面,在其中配置显示自己感兴趣的菜单,然后进行保存即可,保存后同时刷新界面的功能菜单显示。

以上几个界面效果就是为了介绍整个菜单配置管理的一般过程,之所以把界面效果放在前面介绍,就是能够让我们有一个类似原型设计方式的感性认识,了解了相关的处理过程,我们就可以着手通过编码的方式来实现这个处理逻辑了。
2、菜单动态个性化配置的功能实现
上面介绍了大概的界面效果,有了参考,我们可以把它的实现思路通过代码实现出来。
1)参数的数据存储
首先我们需要了解,用户配置可以通过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;
}
2)配置管理界面的实现
解决了参数的获取及存储功能后,我们需要编写一个界面来管理用户的菜单配置,也就是我们前面介绍的菜单配置管理界面。

我们这个界面的定义代码如下所示。

其中参数的数据存储就是应用了前面介绍的代码,这里需要根据用户的配置项初始化树形菜单的显示处理,通过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;
}
}
3)主界面的相关处理
以上处理完成后,我们在主界面的工具栏右键菜单添加一个菜单项,用来进入配置界面的,如下逻辑代码所示。
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();
}
这样界面配置参数并保存后,界面的树形菜单会及时得到更新处理。
另外,我们主界面的树形列表,也要根据配置参数的信息作相关的调整,如果用户配置了不显示某个菜单,那么主界面也要根据配置参数控制显示。

3、总结
以上就是整个菜单列表的动态个性化配置管理的整体思路和实现步骤代码,主要的界面考量还是以用户的视觉来考虑界面的布局和功能,如果在几百个菜单项中寻找几个常用的菜单,每次是一个比较耗时无聊的操作,因此提供一个个性化的界面,根据工作情况的不同,显示一些和自己相关的功能即可。
例如有些情况下,我们的菜单显示,希望通过工具栏的方式进行控制显示,如下界面效果所示。

那么配置维护界面还是差不多,只是我们控制工具栏的显示逻辑有所不同而已,对于RibbonPage及其功能菜单的动态生成处理如下所示。

本篇随笔主要还是希望读者借鉴配置存储和菜单个性化管理的思路,具体的逻辑会因用户界面的不同,使用的控件不同而有所差异,不过总体思路是一致的即可。
例如有些参数的配置管理,可以统一使用一个配置管理界面进行维护,如我之前的随笔介绍的界面功能一样。

希望本篇随笔对你有所启发,感谢耐心阅读。
Winform界面中实现菜单列表的动态个性化配置管理的更多相关文章
- 在Winform界面中实现对多文档窗体的参数传值
在Winform界面中窗体我们一般使用多文档进行展示,也就是类似一般的选项卡的方式可以加载多个窗体界面.一般来说,我们如果打开新的窗体,给窗体传递参数是很容易的事情,但是在框架层面,一般的窗体是通过动 ...
- 在Winform系统界面中对进展阶段的动态展示和处理
在我们做客户关系管理系统的Winform界面的时候,需要对进展阶段这个属性进行一个方便的动态切换和标记处理,如我们根据不同的进展阶段显示不同的相关信息,也可以随时保存当前的阶段信息.其实也是一个比较常 ...
- Winform界面中实现通用工具栏按钮的事件处理
在一个给客户做的项目中,界面要求修改增加通用工具栏按钮的事件处理,也就是在主界面中放置几个固定的功能操作按钮,打开不同的页面的时候,实现对应页面的功能处理,这种和我标准的界面处理方式有所不同,标准的列 ...
- Winform界面中主从表编辑界面的快速处理
在Winform开发中,我们往往除了常规的单表信息录入外,有时候设计到多个主从表的数据显示.编辑等界面,单表的信息一般就是控件和对象实体一一对应,然后调用API保存即可,主从表就需要另外特殊处理,本随 ...
- C# Winform 界面中各控件随着窗口大小变化
在做一项工程中,由于不确定目标平台的分辨率,而正常使用要求铺满整个屏幕,所以界面中的各个控件必须能够适应窗口的变化. 首先想到的就是控件的百分比布局,但是再尝试写了几个控件的Location和Size ...
- 在Winform界面中使用DevExpress的TreeList实现节点过滤查询的两种方式
在我较早的一篇随笔<在DevExpress程序中使用TeeList控件以及节点查询的处理>中,介绍了在树形列表TreeList控件上面,利用SearchControl实现节点的模糊查询过滤 ...
- 原创+部分引用啦:C# Winform界面中的分隔线问题
C sharp 中Winform中的控件很多,一个小小的问题居然会绕上一个小弯子,做界面的时候, 你需要在界面上弄一条分隔线,把相关的功能分隔开来,结果原来在其它 IDE编辑器里很容易实现的这个功能, ...
- Android4.3模拟器界面中右侧菜单按钮无法使用问题解决办法
开发环境:笔记本电脑Windows2008+MyEclipse 10+Android4.3 问题描述:运行或者调试Android项目时,发现模拟器中右侧Menu按钮无法点击,截图如下: 查看在Andr ...
- WinForm界面中快捷键设置
这是对整个界面的快捷键的设置,比如查询,保存. 1 protected override bool ProcessCmdKey(ref Message msg, Keys keyData) { if ...
随机推荐
- js系列教程2-对象、构造函数、对象属性全解
全栈工程师开发手册 (作者:栾鹏) 快捷链接: js系列教程1-数组操作全解 js系列教程2-对象和属性全解 js系列教程3-字符串和正则全解 js系列教程4-函数与参数全解 js系列教程5-容器和算 ...
- 【学习】条码扫描器:QuaggaJS
QuaggaJS是条形码扫描器完全用JavaScript编写,支持实时对各类条码进行定位和解码,如EAN和CODE128.该库还能够使用getUserMedia获得直接访问用户的摄像头流.为了充分利用 ...
- EF框架搭建小总结--ModelFirst模型优先
前言:去年刚工作的时候,也是刚刚正式接触.net,当时了解了EF以及三种开发模式,Database First.Model First .Code First.公司用的开发模式是Database Fi ...
- Xamarin Forms 进度条控件
本文翻译:http://xamlnative.com/2016/04/14/xamarin-forms-a-simple-circular-progress-control/ 里面都是胡说的,如果看不 ...
- python 基础一
Python安装 Python --:安装时可以勾选 PATH环境变量,安装后无需再设置环境变量; Python 交互下 exit().quit()----可以退出交互环境; Pyhton 安装库与卸 ...
- Chrome等浏览器下出现net::ERR_BLOCKED_BY_CLIENT的解决办法
当我们在做开发时,调试页面图片会出现部分图片无法正常显示,并且确认图片的地址正确: 按F12 Debug查看报错原因,提示net::ERR_BLOCKED_BY_CLIENT错误,但当我们点击图片地址 ...
- session和cookie作用原理,区别
Cookie概念 在浏览某些 网站 时,这些网站会把 一些数据存在 客户端 , 用于使用网站 等跟踪用户,实现用户自定义 功能. 是否设置过期时间: 如果不设置 过期时间,则表示这个 Cookie生命 ...
- LeetCode 111. Minimum Depth of Binary Tree (二叉树最小的深度)
Given a binary tree, find its minimum depth. The minimum depth is the number of nodes along the shor ...
- 视频加载logo
最近工作需要,收集了一些视频卡顿或加载时的透明PNG图片.
- 给资源文件添加指纹(Gulp版)
至于为什么要费尽心思地给文件添加指纹,请参看前端静态资源缓存控制策略.这次要达到的小目标就是生成的资源文件能够被客户端缓存,而在文件内容变化后,能够请求到最新的文件. 需要用到的 gulp 插件是 g ...