以横向树方式显示Html表格

    最近项目中常要画动态的Table,由于HTML表格中纵向合并单元格使用的是rowspan属性,一旦遇到纵向合并单元格的时候就会特别显得特别麻烦。其实我们项目中所画的Table大多都是些树,如果以类似TreeView添加节点的方式来构建Table对象,最后调用重写的ToString方法把整个表格呈现出来应该效果不错,避免了在代码中充斥着大量的td、tr等字符串,影响了代码的可读性及易于维护性。于是我简单得作了个类库分享给大家,希望能够对大家有。
    类的结构图如下:
  
    
INode接口代码如下,其中最重要就是ToString方法了,它决定了如何呈现树。
 1    /// <summary>
 2    /// INode [实现先序遍历]
 3    /// </summary>

 4    public interface INode : System.Collections.Generic.IEnumerable<INode>
 5    {
 6        INode Parent get;set;} //取得父结点
 7        INodeList Childs get; } //取得下级节点
 8        INodeList Leafs get;} //取得以该节点为根的子数的叶子节点
 9        bool IsLeaf get;} //是否为叶子结点
10
11        int Tier get;} //取得该节点在树中的所处层数(从0开始计数)
12        int Depth get;} //取得以该节点为根的子数的深度(本层为0)
13
14        IAttributeDictionary Attributes get;set;} //该节点的属性集合
15
16        object Content get;set;} //节点中的内容
17
18        children operation
25
26        string ToString(); //将节点和其属性以及内容表示为网页可显示的字符串
27
28
29    }

ITree接口如下,这个接口继承了INode,特别突出了下ToString方法,其作用是呈现整个树。为了将一个纵向的树横向的呈现,我们必须使用前根遍历该树的所有子节点,并依次调用子节点的ToString方法,并在遍历到叶子节点的时候加上“回车”(</tr>)以表示该html行结束。
注意到ITree在INode的基础上加上了一个FullFill的方法,该方法用于将一个非满树用空结点补满,否则表格就会出现缺格。其中T是指用于填充树的类型,也就是用何种INode来填满这颗树。当然这应该和你用来构建这颗树的节点类型一样。
 1    /// <summary>
 2    /// ITree 的摘要说明
 3    /// </summary>

 4    public interface ITree : INode
 5    {
 6        //ITree FullFill(); //返回该树的"满数"
 7        ITree FullFill<T>() where T : INode, new(); //泛型版本(用类型T来填充)
 8
 9        new string ToString(); //1循环子节点 2调用INode的[前序遍历] 3调用INode.ToString(); 4每行开始<tr>、结束(遍历到叶子节点)加上</tr> 5加上<table></table>
10    }

BaseNode是个模版类,其对添加和移除子节点时作了些额外控制,防止同一个INode对象添加到了2个或以上的父节点下:
  1    /// <summary>
  2    /// Node 的摘要说明
  3    /// </summary>

  4    public abstract class BaseNode : INode
  5    {
  6        protected INode parent; //父节点
  7        protected IList<INode> childs = new List<INode>(); //子节点的"内部表现"
  8
  9        public BaseNode()
 10        { }
 11
 12        public BaseNode(INode parent)
 13        {
 14            parent.AddChild(this);
 15            this.parent = parent;
 16        }

 17
 18        //private void initial()
 19        //{
 20
 21        //}
 22
 23        INode 成员
159
160        IEnumerable 成员
177
178        IEnumerable 成员
186
187        /// <summary>
188        /// 先序遍历,并按序入队列
189        /// </summary>

190        private void perOrderTraverse(Queue<INode> queueList, INode parentNode)
191        {
192            foreach (INode node in parentNode.Childs)
193            {
194                queueList.Enqueue(node);
195                perOrderTraverse(queueList, node);
196            }

197        }

198
199        /// <summary>
200        /// 取得深度
201        /// </summary>

202        private int getDepth(INode node)
203        {
204            if (node == null || node.Childs.Count == 0)
205                return 0;
206
207            int[] childDepthArray = new int[node.Childs.Count];
208            INodeList childList = node.Childs;
209            for (int i = 0; i < childList.Count; i++)
210            {
211                childDepthArray[i] = getDepth(childList[i]);
212            }

213
214            Array.Sort<int>(childDepthArray); //升序排序
215            return childDepthArray[childDepthArray.Length - 1+ 1;  //取得最大层数子树的层数 + 1
216        }

217
218        /// <summary>
219        /// 取得层数
220        /// </summary>

221        private int getTier(INode node)
222        {
223            int tier = 0;
224            INode n = node;
225            while (n.Parent != null)
226            {
227                n = n.Parent;
228                tier++;
229            }

230            return tier;
231        }

232    }

BaseTree是ITree的模版实现,其中的关键在于如何计算每个节点的rowspan(只需要计算以该节点为根的子树有几个叶子节点,即表示该节点需要多少rowspan),并且这里的IEnumerator必须以前序遍历来返回所有子节点,原因之前已经提到过。
  1  /// <summary>
  2    /// Tree 的摘要说明
  3    /// </summary>

  4    public class BaseTree : BaseNode, ITree
  5    {
  6        protected IAttributeDictionary attributes = new BaseAttributeDictionary(); //属性集合
  7
  8        ITree 成员
 51
 52        IEnumerable 成员
 60
 61        public override IAttributeDictionary Attributes
 62        {
 63            get return this.attributes; }
 64            set this.attributes = value; }
 65        }

 66
 67        public override object Content
 68        {
 69            get return this.ToString(); }
 70            set new Exception("不可更改内容"); }
 71        }

 72
 73        /// <summary>
 74        /// 1循环子节点
 75        /// 2调用INode的[前序遍历] 
 76        /// 3调用INode.ToString(); 
 77        /// 4每行开始<tr>、结束(遍历到叶子节点)加上<![CDATA[</tr> ]]>
 78        /// 5加上<![CDATA[<table></table>]]>
 79        /// </summary>
 80        /// <returns></returns>

 81        public override string ToString()
 82        {
 83            System.Text.StringBuilder builder = new System.Text.StringBuilder();
 84            builder.Append("<table ").Append(this.attributes == null ? "" : this.attributes.ToString()).Append(">"); //加上table的属性
 85            //多根循环
 86            //foreach (INode rootNode in this.Childs)
 87            {
 88                builder.Append(@"<tr>");
 89                foreach (INode node in this//前序遍历
 90                {
 91                    //加上rowspan属性
 92                    countRowSpan(node);
 93
 94                    builder.Append(node.ToString());
 95                    if (node.IsLeaf)
 96                    {
 97                        builder.Append(@"</tr><tr>");
 98                    }

 99                }

100                builder.Remove(builder.Length - 44); //移除最后的<tr>
101            }

102            builder.Append(@"</table>");
103            return builder.ToString();
104        }

105
106        /// <summary>
107        /// 计算td的rowspan,并加上rowspan属性
108        /// </summary>
109        /// <param name="node">原结点</param>

110        protected virtual void countRowSpan(INode node)
111        {
112            int value = node.Leafs.Count; //计算叶子结点(rowspan的值)
113            if (node.Attributes == null)
114            {
115                node.Attributes = new BaseAttributeDictionary();
116            }

117            IAttri attri = new SingletonAttri("rowspan", value.ToString());
118            node.Attributes.Add(attri); //加上rowspan属性
119        }

120
121        ///// <summary>
122        ///// 填充固定长度线性树
123        ///// </summary>

124        //private void addFixLengthNode(int length, INode parent)
125        //{
126        //    if (length < 1)
127        //        throw new Exception("无效长度,必须大于1");
128
129        //    INode node = new SingletonNode(parent, "&nbsp;"); //空节点
130        //    for (int i = 0; i < length - 1; i++)
131        //    {
132        //        INode tempNode = new SingletonNode("&nbsp;"); //空节点
133        //        node.AddChild(tempNode);
134        //        node = tempNode; //持有下一个节点
135        //    }
136        //}
137
138        /// <summary>
139        /// 填充固定长度线性树(用类型T填充)
140        /// </summary>

141        private void addFixLengthNode<T>(int length, INode parent) where T : INode, new()
142        {
143            if (length < 1)
144                throw new Exception("无效长度,必须大于1");
145
146            INode node = new T(); //空节点
147            node.Parent = parent;
148            node.Content = "&nbsp;";
149
150            for (int i = 0; i < length - 1; i++)
151            {
152                INode tempNode = new T(); //空节点
153                tempNode.Content = "&nbsp";
154
155                node.AddChild(tempNode);
156                node = tempNode; //持有下一个节点
157            }

158        }

159    }

最后就是节点的具体实现了,SingletonNode是单td树节点:
 1/// <summary>
 2    /// SingletonNode -- 单td树结点
 3    /// </summary>

 4    public class SingletonNode : BaseNode
 5    {
 6        private string content; //td中的内容
 7        private IAttributeDictionary attributes = new BaseAttributeDictionary(); //td中的属性集合
 8
 9        构造器
32
33        public override IAttributeDictionary Attributes
34        {
35            get
36            {
37                return this.attributes;
38            }

39            set
40            {
41                this.attributes = value;
42            }

43        }

44
45        public override object Content
46        {
47            get
48            {
49                return this.content;
50            }

51            set
52            {
53                this.content = value.ToString();
54            }

55        }

56
57        public override string ToString()
58        {
59            System.Text.StringBuilder builder = new System.Text.StringBuilder();
60
61            string attriStr = attributes.ToString(); //属性
62
63            string showContent = this.content;
64            if (string.IsNullOrEmpty(content))
65                showContent = "&nbsp";  //若该td中内容为空则需要显示一个空格,否则该td会显示不出来
66
67            builder.Append(@"<td ").Append(attriStr).Append(@">").Append(showContent).Append(@"</td>");
68            return builder.ToString();
69        }

70
71    }

ContainIndexNode是带index的双td树节点:
  1    /// <summary>
  2    /// ContainIndexNode 包含index的结点
  3    /// </summary>

  4    public class ContainIndexNode : BaseNode
  5    {
  6        private string content; //td2中的内容
  7        private IAttributeDictionary attributes = new BaseAttributeDictionary(); //td中的属性集合
  8
  9        构造器
 31
 32        public override IAttributeDictionary Attributes
 33        {
 34            get
 35            {
 36                return this.attributes;
 37            }

 38            set
 39            {
 40                this.attributes = value;
 41            }

 42        }

 43
 44        public override object Content
 45        {
 46            get
 47            {
 48                return this.content;
 49            }

 50            set
 51            {
 52                this.content = value.ToString();
 53            }

 54        }

 55
 56        public override string ToString()
 57        {
 58            System.Text.StringBuilder builder = new System.Text.StringBuilder();
 59
 60            string attriStr = attributes.ToString(); //属性
 61
 62            string showContent = this.content;
 63            if (string.IsNullOrEmpty(content))
 64                showContent = "&nbsp";  //若该td中内容为空则需要显示一个空格,否则该td会显示不出来
 65
 66            builder.Append(@"<td ").Append(attriStr).Append(@">").Append(this.getIndex()).Append(@"</td>");
 67            builder.Append(@"<td ").Append(attriStr).Append(@">").Append(showContent).Append(@"</td>");
 68            return builder.ToString();
 69        }

 70
 71        取得索引
111    }
只需要将注意力集中在单个内容对象Content的呈现方式上就可以了。

其他有关INode集合以及html标签属性的接口和类的实现这里就不贴出来了,大家如果有兴趣可以下载源代码看下。
好了,赶快建个页面看下效果吧 ^_^
 1public partial class _Default : System.Web.UI.Page 
 2{
 3    protected void Page_Load(object sender, EventArgs e)
 4    {
 5        BaseTree tree = new BaseTree();
 6
 7        build node
51        
52        //填充树
53        tree.FullFill<SingletonNode>();  //单td的
54        //tree.FullFill<ContainIndexNode>(); //带index的双td节点表
55
56        string html = tree.ToString();
57        this.div1.InnerHtml = html;
58    }

59
60    private INode nodeFactory(string content)
61    {
62        return new SingletonNode(content); //单td的
63        //return new ContainIndexNode(content); //带index的双td节点表
64    }

65}


效果分别如下图:
     

第一次写blog,如果写的不好请大家见谅
TreeTable源代码
posted @ 2008-05-24 02:30  jeremyyang824  阅读(7351)  评论(22编辑  收藏  举报