最近项目中常要画动态的Table,由于HTML表格中纵向合并单元格使用的是rowspan属性,一旦遇到纵向合并单元格的时候就会特别显得特别麻烦。其实我们项目中所画的Table大多都是些树,如果以类似TreeView添加节点的方式来构建Table对象,最后调用重写的ToString方法把整个表格呈现出来应该效果不错,避免了在代码中充斥着大量的td、tr等字符串,影响了代码的可读性及易于维护性。于是我简单得作了个类库分享给大家,希望能够对大家有。
重写树节点INode的ToString方法使表格有不同的表现方式。
部分代码如下:public interface INode : System.Collections.Generic.IEnumerable<INode>
{
    INode Parent { get;set;} //取得父结点
    INodeList Childs { get; } //取得下级节点
    INodeList Leafs { get;} //取得以该节点为根的子数的叶子节点
    bool IsLeaf { get;} //是否为叶子结点    int Tier { get;} //取得该节点在树中的所处层数(从0开始计数)
    int Depth { get;} //取得以该节点为根的子数的深度(本层为0)    IAttributeDictionary Attributes { get;set;} //该节点的属性集合    object Content { get;set;} //节点中的内容    #region children operation    void AddChild(INode child);
    bool RemoveChild(INode child);
    void ClearChildren();    #endregion    string ToString(); //将节点和其属性以及内容表示为网页可显示的字符串
}public interface ITree : INode
{
    //ITree FullFill(); //返回该树的"满数"
    ITree FullFill<T>() where T : INode, new(); //泛型版本(用类型T来填充)    new string ToString(); //1循环子节点 2调用INode的[前序遍历] 3调用INode.ToString(); 4每行开始<tr>、结束(遍历到叶子节点)加上</tr> 5加上<table></table>
}    /// <summary>
    /// Node 的摘要说明
    /// </summary>
    public abstract class BaseNode : INode
    {
        protected INode parent; //父节点
        protected IList<INode> childs = new List<INode>(); //子节点的"内部表现"        public BaseNode()
        { }        public BaseNode(INode parent)
        {
            parent.AddChild(this);
            this.parent = parent;
        }        //private void initial()
        //{        //}        #region INode 成员        /// <summary>
        /// 取得父结点
        /// </summary>
        public INode Parent
        {
            get { return this.parent; }
            set
            {
                value.AddChild(this); //设定父节点的同时,在父节点的子列表中加入该结点
                this.parent = value;
            }
        }        /// <summary>
        /// 取得下级节点--子结点的外部表现(只读)
        /// </summary>
        public INodeList Childs
        {
            get { return new BaseNodeList(childs); }
        }        /// <summary>
        /// 取得以该节点为根的子数的叶子节点
        /// </summary>
        public INodeList Leafs
        {
            get
            {
                IList<INode> leafs = new List<INode>();
                foreach (INode node in this)
                {
                    //判断是否为叶子结点
                    if (node.Childs.Count == 0)
                        leafs.Add(node);
                }
                return new BaseNodeList(leafs);
            }
        }        /// <summary>
        /// 是否是叶子结点
        /// </summary>
        public bool IsLeaf
        {
            get
            {
                if (this.childs.Count == 0)
                    return true;
                return false;
            }
        }        /// <summary>
        /// 取得该节点在树中的所处层数(从0开始计数)
        /// </summary>
        public int Tier
        {
            get { return this.getTier(this); }
        }        /// <summary>
        /// 取得以该节点为根的子数的深度(本层为0)
        /// </summary>
        public int Depth
        {
            get { return this.getDepth(this); }
        }        #region 抽象方法        /// <summary>
        /// 属性列表(根据其ToString方法来显示)
        /// </summary>
        public abstract IAttributeDictionary Attributes { get;set;}        /// <summary>
        /// 结点内容(可以是任何对象,最终根据INode.ToString方法来显示)
        /// </summary>
        public abstract object Content { get;set;}        /// <summary>
        /// 必须重写ToString方法,用于呈现该INode
        /// </summary>
        /// <returns></returns>
        public abstract override string ToString();        #endregion        #region 操作子结点        /// <summary>
        /// 添加一个子结点
        /// </summary>
        /// <param name="item"></param>
        public void AddChild(INode item)
        {
            INode oldParent = item.Parent; //原父结点
            if (oldParent == null || oldParent.RemoveChild(item)) //从原父节点的子结点中移除该结点
            {
                childs.Add(item);
                //item.Parent = this;  //该句会导致无限递归错误!
                ((BaseNode)item).parent = this;
            }
        }        /// <summary>
        /// 移除一个子结点
        /// </summary>
        /// <param name="item"></param>
        /// <returns></returns>
        public bool RemoveChild(INode item)
        {
            if (this.childs.Remove(item))
            {
                item.Parent = null;
                return true;
            }
            return false;
        }        /// <summary>
        /// 清空子结点
        /// </summary>
        public void ClearChildren()
        {
            foreach (INode node in childs)
            {
                childs.Remove(node);
            }
        }        #endregion        #endregion        #region IEnumerable<INode> 成员        /// <summary>
        /// 先序遍历
        /// </summary>
        /// <returns></returns>
        public IEnumerator<INode> GetEnumerator()
        {
            Queue<INode> queueList = new Queue<INode>(); //按序进出的队列
            perOrderTraverse(queueList, this); //本身不入队列
            while (queueList.Count > 0 && queueList.Peek() != null)
            {
                yield return queueList.Dequeue();
            }
        }        #endregion        #region IEnumerable 成员        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }        #endregion        /// <summary>
        /// 先序遍历,并按序入队列
        /// </summary>
        private void perOrderTraverse(Queue<INode> queueList, INode parentNode)
        {
            foreach (INode node in parentNode.Childs)
            {
                queueList.Enqueue(node);
                perOrderTraverse(queueList, node);
            }
        }        /// <summary>
        /// 取得深度
        /// </summary>
        private int getDepth(INode node)
        {
            if (node == null || node.Childs.Count == 0)
                return 0;            int[] childDepthArray = new int[node.Childs.Count];
            INodeList childList = node.Childs;
            for (int i = 0; i < childList.Count; i++)
            {
                childDepthArray[i] = getDepth(childList[i]);
            }            Array.Sort<int>(childDepthArray); //升序排序
            return childDepthArray[childDepthArray.Length - 1] + 1;  //取得最大层数子树的层数 + 1
        }        /// <summary>
        /// 取得层数
        /// </summary>
        private int getTier(INode node)
        {
            int tier = 0;
            INode n = node;
            while (n.Parent != null)
            {
                n = n.Parent;
                tier++;
            }
            return tier;
        }
    }

解决方案 »

  1.   


        /// <summary>
        /// Tree 的摘要说明
        /// </summary>
        public class BaseTree : BaseNode, ITree
        {
            protected IAttributeDictionary attributes = new BaseAttributeDictionary(); //属性集合        #region ITree 成员        ///// <summary>
            ///// 返回该树的"满数"
            ///// </summary>
            ///// <returns></returns>
            //public virtual ITree FullFill()
            //{
            //    int treeHeight = this.Depth; //该树的高度
            //    INodeList leafs = this.Leafs; //该树的叶子集合
            //    foreach (INode node in leafs)
            //    {
            //        int tier = node.Tier; //某叶子结点的所在层数
            //        if (tier < treeHeight)//填到树的高度
            //        {
            //            int length = treeHeight - tier; //需要填充的高度
            //            this.addFixLengthNode(length, node);
            //        }
            //    }
            //    return this;
            //}        /// <summary>
            /// 返回该树的"满数"(用T类型的结点填充)
            /// </summary>
            /// <returns></returns>
            public virtual ITree FullFill<T>() where T : INode, new()
            {
                int treeHeight = this.Depth; //该树的高度
                INodeList leafs = this.Leafs; //该树的叶子集合
                foreach (INode node in leafs)
                {
                    int tier = node.Tier; //某叶子结点的所在层数
                    if (tier < treeHeight)//填到树的高度
                    {
                        int length = treeHeight - tier; //需要填充的高度
                        this.addFixLengthNode<T>(length, node);
                    }
                }
                return this;
            }        #endregion        #region IEnumerable 成员        public new IEnumerator GetEnumerator()
            {
                return base.GetEnumerator();
            }        #endregion        public override IAttributeDictionary Attributes
            {
                get { return this.attributes; }
                set { this.attributes = value; }
            }        public override object Content
            {
                get { return this.ToString(); }
                set { new Exception("不可更改内容"); }
            }        /// <summary>
            /// 1循环子节点
            /// 2调用INode的[前序遍历] 
            /// 3调用INode.ToString(); 
            /// 4每行开始<tr>、结束(遍历到叶子节点)加上<![CDATA[</tr> ]]>
            /// 5加上<![CDATA[<table></table>]]>
            /// </summary>
            /// <returns></returns>
            public override string ToString()
            {
                System.Text.StringBuilder builder = new System.Text.StringBuilder();
                builder.Append("<table ").Append(this.attributes == null ? "" : this.attributes.ToString()).Append(">"); //加上table的属性
                //多根循环
                //foreach (INode rootNode in this.Childs)
                {
                    builder.Append(@"<tr>");
                    foreach (INode node in this) //前序遍历
                    {
                        //加上rowspan属性
                        countRowSpan(node);                    builder.Append(node.ToString());
                        if (node.IsLeaf)
                        {
                            builder.Append(@"</tr><tr>");
                        }
                    }
                    builder.Remove(builder.Length - 4, 4); //移除最后的<tr>
                }
                builder.Append(@"</table>");
                return builder.ToString();
            }        /// <summary>
            /// 计算td的rowspan,并加上rowspan属性
            /// </summary>
            /// <param name="node">原结点</param>
            protected virtual void countRowSpan(INode node)
            {
                int value = node.Leafs.Count; //计算叶子结点(rowspan的值)
                if (node.Attributes == null)
                {
                    node.Attributes = new BaseAttributeDictionary();
                }
                IAttri attri = new SingletonAttri("rowspan", value.ToString());
                node.Attributes.Add(attri); //加上rowspan属性
            }        ///// <summary>
            ///// 填充固定长度线性树
            ///// </summary>
            //private void addFixLengthNode(int length, INode parent)
            //{
            //    if (length < 1)
            //        throw new Exception("无效长度,必须大于1");        //    INode node = new SingletonNode(parent, "&nbsp;"); //空节点
            //    for (int i = 0; i < length - 1; i++)
            //    {
            //        INode tempNode = new SingletonNode("&nbsp;"); //空节点
            //        node.AddChild(tempNode);
            //        node = tempNode; //持有下一个节点
            //    }
            //}        /// <summary>
            /// 填充固定长度线性树(用类型T填充)
            /// </summary>
            private void addFixLengthNode<T>(int length, INode parent) where T : INode, new()
            {
                if (length < 1)
                    throw new Exception("无效长度,必须大于1");            INode node = new T(); //空节点
                node.Parent = parent;
                node.Content = "&nbsp;";            for (int i = 0; i < length - 1; i++)
                {
                    INode tempNode = new T(); //空节点
                    tempNode.Content = "&nbsp";                node.AddChild(tempNode);
                    node = tempNode; //持有下一个节点
                }
            }
        }
    INode具体实现 效果如图1    /// <summary>
        /// SingletonNode -- 单td树结点
        /// </summary>
        public class SingletonNode : BaseNode
        {
            private string content; //td中的内容
            private IAttributeDictionary attributes = new BaseAttributeDictionary(); //td中的属性集合        #region 构造器        public SingletonNode()
            { }        public SingletonNode(string content)
            {
                this.content = content;
            }        public SingletonNode(INode parent, string content)
                : base(parent)
            {
                this.content = content;
            }        public SingletonNode(INode parent, string content, IAttributeDictionary attributes)
                : this(parent, content)
            {
                this.attributes = attributes; //属性列表
            }        #endregion        public override IAttributeDictionary Attributes
            {
                get
                {
                    return this.attributes;
                }
                set
                {
                    this.attributes = value;
                }
            }        public override object Content
            {
                get
                {
                    return this.content;
                }
                set
                {
                    this.content = value.ToString();
                }
            }        public override string ToString()
            {
                System.Text.StringBuilder builder = new System.Text.StringBuilder();            string attriStr = attributes.ToString(); //属性            string showContent = this.content;
                if (string.IsNullOrEmpty(content))
                    showContent = "&nbsp";  //若该td中内容为空则需要显示一个空格,否则该td会显示不出来            builder.Append(@"<td ").Append(attriStr).Append(@">").Append(showContent).Append(@"</td>");
                return builder.ToString();
            }    }
      

  2.   

    INode的带索引实现 效果如图2    /// <summary>
        /// ContainIndexNode 包含index的结点
        /// </summary>
        public class ContainIndexNode : BaseNode
        {
            private string content; //td2中的内容
            private IAttributeDictionary attributes = new BaseAttributeDictionary(); //td中的属性集合        #region 构造器
            public ContainIndexNode()
            { }        public ContainIndexNode(string content)
            {
                this.content = content;
            }        public ContainIndexNode(INode parent, string content)
                : base(parent)
            {
                this.content = content;
            }        public ContainIndexNode(INode parent, string content, IAttributeDictionary attributes)
                : this(parent, content)
            {
                this.attributes = attributes; //属性列表
            }        #endregion        public override IAttributeDictionary Attributes
            {
                get
                {
                    return this.attributes;
                }
                set
                {
                    this.attributes = value;
                }
            }        public override object Content
            {
                get
                {
                    return this.content;
                }
                set
                {
                    this.content = value.ToString();
                }
            }        public override string ToString()
            {
                System.Text.StringBuilder builder = new System.Text.StringBuilder();            string attriStr = attributes.ToString(); //属性            string showContent = this.content;
                if (string.IsNullOrEmpty(content))
                    showContent = "&nbsp";  //若该td中内容为空则需要显示一个空格,否则该td会显示不出来            builder.Append(@"<td ").Append(attriStr).Append(@">").Append(this.getIndex()).Append(@"</td>");
                builder.Append(@"<td ").Append(attriStr).Append(@">").Append(showContent).Append(@"</td>");
                return builder.ToString();
            }        #region 取得索引        private string getIndex()
            {
                System.Text.StringBuilder builder = new System.Text.StringBuilder();
                System.Collections.Generic.Stack<int> stack = new System.Collections.Generic.Stack<int>();
                int parts = this.Tier;
                int index = 0;            INode node = this;
                while (index < parts)
                {
                    int num = this.getIndex(node);
                    stack.Push(num);
                    node = node.Parent;                index++;
                }            while (stack.Count > 0)
                {
                    builder.Append(stack.Pop()).Append(".");
                }            builder.Remove(builder.Length - 1, 1); //移除最后的点
                return builder.ToString();
            }        private int getIndex(INode node)
            {
                INodeList list = node.Parent.Childs;
                for (int i = 0; i < list.Count; i++)
                {
                    if (list[i] == node)
                        return i + 1;
                }
                return 0;
            }        #endregion
        }
    具体的内容说明以及源代码下载可以到这里查看http://www.cnblogs.com/jeremyyang824/archive/2008/05/24/1206232.html
      

  3.   

    主要是在于动态的画表,如果数据内容是不断变化的要合并纵向单元格就不能用dw了吧。
    一但一个td的rowspan有变,那么会影响到多个tr中的td的数量。
    如果我是一个制定计划大纲的客户,从1条主任务,到下面的分支任务,再到再下层的实施步骤,一层一层细分下来填写的话,就只能动态的画出表格来,如果是用代码画过html表格的人一定会有体会。
      

  4.   

    上面说的不错
    只要rowspan 或 colspan 不对
    整个表格就会显示错乱
    不过要是楼主能够用javascript写一个
    动态表格构建的实用类就更好了
      

  5.   

    @wfyfngu 
    最终生成的是html表格的字符串,可以用ajax传到前台显示。
    我js水平不太过关,纯js写比较累,呵呵 ...
      

  6.   

    呵呵
     用Repater 和DataList空间想怎么画就怎么画
     那用这么麻烦呢
      

  7.   

    但是要纵向合并单元格就不方便了吧
    重点在于对rowspan的控制