一个权限树的设计与实现

如图,实现如下功能:

1,加载自动读出当前权限,自动展开(基本功能)

2,有一个选择/取消全部的功能(之前设计成独立的按钮,最后改成一个根目录的形式,如上图)

3,任何父级与子级的全选关系动态关联,具体如下:

  3.1,选中父级,则子级全部选中,取消父级,则子级全部取消选择;

  3.2,选中父级的情况下,取消一个子级,或更多,父集的勾去动取消,(全选同样,只要有一个项没有选,全选自动取消)

  3.3,在子级未全部选中的情况下,一个个勾选,一旦子级全选,则父级自动变成选中

因为这是一个限制用户能使用哪些菜单的设计,所以我们设计的就是菜单表:

权限用逗号分隔的字符串表示,如3,4,5,17,19,22,对应了以这些值为id的菜单项

而菜单表的设计为:

主键ID,Int, 不自增

菜单名,varchar

菜单说明,varchar

父级id,int

是否最终目录,leaf,int----即只有树叶,才是不可能有下级枝、干的。

权限表只存leaf值为1的选项。

窗体加载的时候,把权限读成一个list,List生成后,开始递归循环每一级菜单;

一旦该菜单的Id在权限的list里面存在(用contains方法),则把其node的状态设为选中;

核心逻辑:所有的动态判断勾选状态的事件,全部关联到节点的AfterCheck事件,在这个事件中,我们智能判断其下级所有目录的勾选情况,其上级所有目录的勾选情况,

核心递归逻辑走完,原则上,这个设计就完成了,所有的逻辑都放在这个逻辑里,比如全选,只要使第一级菜单全部选中/取消选中,这个“选中”的动作,则会自动触发aftercheck事件,自然就进入递归流程,不要显式写代码递归。

所以核心方法有下面几个

1,加载treeview后,写一个判断权限的方法(getCheckStatus),递归完成,每一个有权限的菜单,设一

次node的checked为true,

  因为初始化的时候是从根目录往叶目录走,所以此次递归没有“设子菜单勾选情况”的逻辑,因为子菜单的状态还有待同数据库比对。

  因此每一个有权限的node被check后,只需判断上级目录是否应该勾选。

  正因为向上搜索和向下搜索要分开,并且有时候只执行一个方向,因此设计了两把锁:nodeParentLock, nodeChildLock,来控制是否向上和向下搜索。

    注:加载treeview之前,先生成一个“选择全部”的节点,然后所有的节点由它发散。

 2,得到父菜单状态的方法(getParentCheckStatus),

    传入当前node,判断是否有父节点,如果没有,则什么也不做。

  有父节点,则判断该父节点下勾选的个数和其子节点个数是否相等,如相等,则触发勾选事件,此时自动触发递归;

  如果不相等,并且之前的状态是勾选,则仍然触发check事件,由勾选变为不勾选,同样进入递归,判断父级的父级。

  而如果之前本来就没有勾选,说明从这个菜单往上,父级都是不可能勾选的,因此不处理。

    注:在设node.checked=true/false之前,要把向下搜索功能锁住,以免死循环,这点非常重要。在check事件结束之后,才把锁打开。

3,得到子菜单状态的方法(getChildCheckStatus),

  传入当前node,判断是否叶节点,或者虽然不是叶节点,但该菜单下暂时没有子目录。

  如果不是,则还有子目录,就循环子目录,把状态不同的更新为相同的,此时又会进入递归(只要有check事件,一定会进入递归),同样,check之前要把向上的锁锁住,避免死循环,事件完成后,务必记得解锁。

  如果是叶节点或空节点,说明不需要继续得到子菜单,此时直接把向上搜索的开关打开,相当于,这个叶菜单在执行完“向下搜索”的任务后,因为向上的开关开了,就会立即从这个位置一直返到根目录,判断每一级是否该勾选。

  于是有人问了,为什么向上搜索的时候到了根目录,不要也这么往叶目录走一遍呢?

  我当初就是这么设计的,实验的时候才发现,你一旦主目录的值一变,你再往回走的话,就会把所有子菜单都变成主菜单的勾选状态(相当于执行了一次全选!),所以这是向上和向下唯一不同的地方,就是向上到顶后,就停在那,向下到底后,在最后一个菜单处,继续往回再跑一圈。这是个细节,怎么把握“到了最后一个目录,代码还没执行完,此时把向上的锁打开”,你仔细看看代码吧

4,勾选事件发生,这里只有一个要注意的地方,就是上面说了,冒泡到顶后,向下的开关没打开,那么你再点任何一级目录的时候,那不是不会往下搜索了么?

    为了解决这个问题,我就这么判断,既然被我锁住了,就是不应该操作的,什么时候才要强制向下搜呢

?对了,鼠标点击!只有鼠标再次点一下checkbox,才会产生把下级都勾上的要求,这就明晰了,用

  if (e.Action == TreeViewAction.ByMouse),可以直接把鼠标事件筛选出来,然后再把向下的开关打开!这样,在你进行了自定义的根据勾选来增删权限制后,只要依次调用上述两个方法就行了。

注意,顺序不能错,先向下搜索,再向上搜索,在向下搜索的最后一个事件里,打开向上的开关,这样就不会在每次调用到aftercheck事件的时候,都把最后才要执行的向上搜索执行一次了。

说得比较混乱,看代码吧,是项目里的原生文件,懒得把不相关的摘掉的,很晚了。

代码
1 using System;
2  using System.Collections.Generic;
3  using System.ComponentModel;
4  using System.Data;
5  using System.Drawing;
6  using System.Linq;
7  using System.Text;
8  using System.Windows.Forms;
9
10  namespace Wgi.MDSIN.WinApp.Forms
11 {
12 public partial class frmMenuAuth : Form
13 {
14
15
16 //业务操作类变量
17   private IBLL.ISystemManage bll = BLLFactory.CreateSystemManageClass();
18 //所有的供应商分类数据
19   private List<Wgi.EntRole.Model.wgi_system_module> allcate;
20 //员工id
21   private int empid;
22 private List<string> menuright;
23 private bool nodeParentLock = true;//此锁为false,表示不需要更新父级状态
24 private bool nodeChildLock = false;//表示不需要更新子级状态
25
26 public frmMenuAuth(int empid)
27 {
28 InitializeComponent();
29 initData(empid);
30 }
31
32 private void initData(int empid)
33 {
34 this.empid = empid;
35 menuright = new Helper.rights().menuRights;
36
37 //加载菜单
38 LoadAllCategory();
39 //确定权限
40 getCheckStatus(trvType.Nodes);
41
42 }
43
44 private void LoadAllCategory()
45 {
46 //清除所有的节点
47 trvType.Nodes.Clear();
48 TreeNode root = new TreeNode("选择全部");
49 root.Tag = new Wgi.EntRole.Model.wgi_system_module();
50 trvType.Nodes.Add(root);
51
52 //搜索用model
53 Wgi.EntRole.Model.wgi_system_module m = new Wgi.EntRole.Model.wgi_system_module();
54
55 //取出所有的分类数据
56 allcate = bll.MenuGetModelList(null);
57 //取出父级分类数据
58 var mods = from p in allcate where p.parent_id.Value == 0 select p;
59
60 TreeNode node = null;
61 foreach (Wgi.EntRole.Model.wgi_system_module mod in mods)
62 {
63 node = new TreeNode(mod.mod_name);
64 node.Tag = mod;
65 node.ToolTipText = mod.remark;
66
67 root.Nodes.Add(node);
68
69 LoadSubCategory(mod.mod_id, node);
70 }
71 trvType.ExpandAll();
72 }
73
74 //加载子菜单
75 private void LoadSubCategory(int parent_id, TreeNode node)
76 {
77 TreeNode subnode = null;
78 var mods = from p in allcate where p.parent_id.Value == parent_id select p;
79 foreach (Wgi.EntRole.Model.wgi_system_module mod in mods)
80 {
81 subnode = new TreeNode(mod.mod_name);
82 subnode.Tag = mod;
83 subnode.ToolTipText = mod.remark;
84 node.Nodes.Add(subnode);
85 //递归调用
86 LoadSubCategory(mod.mod_id, subnode);
87 }
88 }
89
90
91 //渲染权限
92 private void getCheckStatus(TreeNodeCollection tnc)
93 {
94 if (tnc.Count == 0)
95 {
96 return;
97 }
98 Wgi.EntRole.Model.wgi_system_module m = new Wgi.EntRole.Model.wgi_system_module();
99 //判断权限
100 foreach (TreeNode n in tnc)
101 {
102 m = n.Tag as Wgi.EntRole.Model.wgi_system_module;
103 nodeChildLock = false;//首次加载,不需要向下更新,故每次循环前锁掉
104 if (m.leaf.HasValue && m.leaf.Equals(1))//叶节点
105 {
106 if (menuright.Contains(m.mod_id.ToString()))
107 {
108 n.Checked = true;
109 }
110 }
111 else//父节点,进入递归
112 {
113 getCheckStatus(n.Nodes);//非叶页点不代表一定有子节点,所以递归进入的时候要判断
114 }
115 }
116 }
117
118 //更新父节点
119 private void getParentCheckStatus(TreeNode n)
120 {
121 Wgi.EntRole.Model.wgi_system_module m = n.Tag as Wgi.EntRole.Model.wgi_system_module;
122 if (n.Parent != null)
123 {
124 TreeNode pn = n.Parent;
125 int i = 0;
126 foreach (TreeNode item in pn.Nodes)
127 {
128 if (item.Checked)
129 {
130 i++;
131 }
132 }
133 if (i == pn.Nodes.Count)
134 {
135 nodeChildLock = false;//向上找的时候不需要向下更新
136 pn.Checked = true;
137 nodeChildLock = true;//即时解锁
138 }
139 else
140 {
141 if (pn.Checked)//之前已经是未选择的状态,则不变
142 {
143 nodeChildLock = false;
144 pn.Checked = false;
145 nodeChildLock = true;
146 }
147 }
148 }
149 }
150
151 //更新子节点
152 private void getChildNodeCheckStatus(TreeNode node)
153 {
154 if(node.Nodes.Count == 0)//叶节点或无子节点的树节点
155 {
156 //if (node.Parent != null)
157 //{
158 // if (node.Index == node.Parent.Nodes.Count - 1)
159 // {
160 // nodeParentLock = true;//解除父节点更新的锁
161 // }
162 //}
163 //else
164 //{
165 nodeParentLock = true;//只有一个节点
166 //}
167 return;
168 }
169 bool check = node.Checked;
170 foreach (TreeNode n in node.Nodes)
171 {
172 nodeParentLock = false;//向下找的时候不需要向上更新
173 if (n.Checked != check)//状态不同才更新
174 {
175 n.Checked = check;
176 }//没必要像更新父节点一样即时解锁,因为只有在最后一个子节点检查过后,才向上搜寻,所以在上面单独判断了
177 nodeParentLock = true;
178 }
179 }
180
181
182 //check事件
183 private void trvType_AfterCheck(object sender, TreeViewEventArgs e)
184 {
185 if (e.Action == TreeViewAction.ByMouse)
186 {
187 nodeChildLock = true;//因为向上到根目录后不解锁,所以判断是鼠标点下的时候解锁
188 }
189 Wgi.EntRole.Model.wgi_system_module m = new Wgi.EntRole.Model.wgi_system_module();
190 TreeNode n = e.Node;
191 m = n.Tag as Wgi.EntRole.Model.wgi_system_module;
192
193 if (m.leaf.HasValue && m.leaf.Equals(1))//叶节点才更新权限
194 {
195 if (n.Checked)
196 {
197 if (!menuright.Contains(m.mod_id.ToString()))
198 {
199 menuright.Add(m.mod_id.ToString());
200 }
201 }
202 else
203 {
204 if (menuright.Contains(m.mod_id.ToString()))
205 {
206 menuright.Remove(m.mod_id.ToString());
207 }
208 }
209 }
210
211 //判断子节点的状态
212 if (nodeChildLock)
213 {
214 getChildNodeCheckStatus(n);
215 }
216 //判断父节点的状态
217 if (nodeParentLock)//更新状态被锁,则无需更新父节点
218 {
219 getParentCheckStatus(n);
220 }
221 }
222
223 //提交
224 private void btnQuery_Click(object sender, EventArgs e)
225 {
226 Wgi.EntRole.Model.wgi_employee model = bll.EmployeeGetModel(empid);
227 string rights = "";
228 if (menuright.Count > 0)
229 {
230 rights = string.Join(",", menuright.ToArray());
231 }
232 model.menu_right = rights;
233 bll.EmployeeEdit(model);
234
235 MessageBox.Show("权限更新成功!");
236 this.DialogResult = DialogResult.OK;
237 this.Close();
238 }
239
240 }
241 }
242
原文地址:https://www.cnblogs.com/walkerwang/p/1778517.html