大河

  博客园 :: 首页 :: 博问 :: 闪存 :: 新随笔 :: 联系 :: :: 管理 ::

     最近在做一个关于绩效考核问卷填报的项目(注:这是关于人力资源项目中的一个模块),在发布考核问卷前要设定好填报人员的范围,即这个问卷需要由哪些人来填报,因此也就用到了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>();就没事了。

 

posted on 2010-11-15 10:40  大河  阅读(923)  评论(0编辑  收藏  举报