一个权限树的设计与实现

2010071601451172.png

如图,实现如下功能:

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事件的时候,都把最后才要执行的向上搜索执行一次了。

 

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

ContractedBlock.gif ExpandedBlockStart.gif 代码
 
   
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

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值