最近在做一个关于绩效考核问卷填报的项目(注:这是关于人力资源项目中的一个模块),在发布考核问卷前要设定好填报人员的范围,即这个问卷需要由哪些人来填报,因此也就用到了TreeView控件来展现公司组织结构中的员工。效果如下图所示:
本来以为可以轻松搞定,没想到还是遇到了几个问题,下面我把遇到的几个问题描述一下,并写出我是如何解决的。
问题一、TreeNodeCheckChanged事件无法正常响应的问题
功能描述:勾选父节点时,希望将父节点本身和其下所有子节点全选或全清。
问题描述: TreeNodeCheckChanged事件压根就没有响应。
问题原因:MSDN原文如下:当 TreeView 控件的复选框在两次向服务器发送之间更改状态时,会引发TreeNodeCheckChanged 事件。尽管 TreeNodeCheckChanged 事件在回发时激发,但更改复选框不会导致回发。
也就是说,更改复选框并不能导致回发,而此事件激发的条件是只有回发页面时才被激发,这样说是不是很饶舌,呵呵。
解决办法:我们自己来制造一个页面回发。这样不就可以激发TreeNodeCheckChanged事件了吗,说做就做。
第一步:写一个javascript方法,在TreeView的客户端单击事件中调用。
<script language="javascript" type="text/javascript"> function postBackByObject() { var o = window.event.srcElement; if (o.tagName == "INPUT" && o.type == "checkbox") { //回发到树形控件,即只部分刷新TreeView __doPostBack("<%= tvRange.ClientID%>", ""); } } </script>
第二步:加入客户端单击事件:onclick="javascript:postBackByObject()"
并加入服务器端的TreeNodeCheckChanged事件。
TreeView控件的HTML如下:
<asp:TreeView ID="tvRange" runat="server" ShowCheckBoxes="All" ShowLines="true"
onclick="javascript:postBackByObject()"
ontreenodecheckchanged="tvRange_TreeNodeCheckChanged">
</asp:TreeView>
第三步:在TreeNodeCheckChanged中调用全选/全清函数。
protected void tvRange_TreeNodeCheckChanged(object sender, TreeNodeEventArgs e) { this.CheckTreeNode(e.Node, e.Node.Checked); } /// <summary> /// 勾选节点(全选/全清) /// </summary> /// <param name="node"></param> /// <param name="bChecked"></param> private void CheckTreeNode(TreeNode node, bool bChecked) { node.Checked = bChecked; foreach (TreeNode childNode in node.ChildNodes) { childNode.Checked = bChecked; CheckTreeNode(childNode, bChecked); } }
好了,一切大功告成,让我再来理一下思路。我们知道,激发TreeNodeCheckChanged的条件是页面回发,那么我们通过TreeView的单击事件就人为制造一次页面回发,这时页面回发后就会激发TreeNodeCheckChanged事件,接下来自然就会调用CheckTreeNode函数。此方法很笨,响应速度有点慢,至于慢的原因自己想想。
问题二、TreeNodeCollection莫名地删除节点的问题
这个问题是由于我对TreeNodeCollection认知不足引起的。下面我来描述一下事情的经过。用户勾选好填报人员的范围后,我想把选中的岗位节点保存到数据库中,那么自然而然地就会要写个函数来获取这些被选中的岗位节点,函数最初写法如下:
/// <summary> /// 获取选中的岗位节点 /// </summary> /// <returns></returns> private TreeNodeCollection GetCheckedPositionNodes() { TreeNodeCollection checkedNodes = new TreeNodeCollection(); foreach (TreeNode node in this.tvRange.CheckedNodes) { /*PositionEmps是岗位员工关系表,意思是只要关系表中 有对应的选中节点,我就会把它记录在选中节点的集合中*/ if (this.PositionEmps.Select(string.Format("OUID={0}", node.Value)).Length > 0) checkedNodes.Add(node); } return checkedNodes; }
看出问题所在了吗?
调试过程中,发现我往TreeNodeCollection中添加的这些岗位节点不翼而飞,重新刷新一下TreeView,这些节点就没了,真的很莫名奇妙。于是带着这个问题查了下关于TreeNodeCollection的MSDN文档。
关于TreeNodeCollection类的解释原文如下:
TreeNodeCollection 类用于存储和管理 TreeView 控件中的 TreeNode 对象的集合。TreeView 控件在其两个属性中使用 TreeNodeCollection 类。在 Nodes 属性中存储其根节点,在 CheckedNodes 属性中存储其选定的节点。TreeNodeCollection 集合也用于 ChildNodes 属性来存储子节点(如果有的话)。
关于TreeNodeCollection的构造函数有两个:
即:(1)public TreeNodeCollection()
此构造函数用于创建根节点集合
(2)public TreeNodeCollection(TreeNode owner)
此构造函数用于创建指定父节点的非根节点集合。
如上信息告诉我TreeNodeCollection类是给“根节点Nodes”、“选中节点CheckedNodes”和“子节点ChildNodes(如果有的话)”专用的。可是这并不能解释我往TreeNodeCollection实例添加的选中节点不翼而飞的原因,于是我用Reflector工具看了一下TreeNodeCollection的Add方法,方法如下:
public void Add(TreeNode child) { this.AddAt(this.Count, child); } public void AddAt(int index, TreeNode child) { if (child == null) { throw new ArgumentNullException("child"); } if (this._updateParent) { if ((child.Owner != null) && (child.Parent == null)) { child.Owner.Nodes.Remove(child); } if (child.Parent != null) { child.Parent.ChildNodes.Remove(child); } if (this._owner != null) { child.SetParent(this._owner); child.SetOwner(this._owner.Owner); } } this._list.Insert(index, child); this._version++; if (this._isTrackingViewState) { ((IStateManager) child).TrackViewState(); child.SetDirty(); } this.Log.Add(new TreeNodeCollection.LogItem(TreeNodeCollection.LogItemType.Insert, index, this._isTrackingViewState)); }
看到这里我才有所顿悟,原来添加指定父节点的TreeNode时被清了,即如下这句话导致的。
if(child.Parent != null){child.Parent.ChildNodes.Remove(child); }
没办法,只好将获取选中的岗位节点的函数改为如下方法:
/// <summary> /// 获取选中的岗位节点 /// </summary> /// <returns></returns> private List<TreeNode> GetCheckedPositionNodes() { List<TreeNode> checkedNodes = new List<TreeNode>(); foreach (TreeNode node in this.tvRange.CheckedNodes) { if (this.PositionEmps.Select(string.Format("OUID={0}", node.Value)).Length > 0) checkedNodes.Add(node); } return checkedNodes; }
经调试,一切恢复正常。看到了吗?我将
TreeNodeCollection checkedNodes = new TreeNodeCollection();改为
List<TreeNode> checkedNodes = new List<TreeNode>();就没事了。