以横向树方式显示Html表格
最近项目中常要画动态的Table,由于HTML表格中纵向合并单元格使用的是rowspan属性,一旦遇到纵向合并单元格的时候就会特别显得特别麻烦。其实我们项目中所画的Table大多都是些树,如果以类似TreeView添加节点的方式来构建Table对象,最后调用重写的ToString方法把整个表格呈现出来应该效果不错,避免了在代码中充斥着大量的td、tr等字符串,影响了代码的可读性及易于维护性。于是我简单得作了个类库分享给大家,希望能够对大家有。
类的结构图如下:
INode接口代码如下,其中最重要就是ToString方法了,它决定了如何呈现树。
ITree接口如下,这个接口继承了INode,特别突出了下ToString方法,其作用是呈现整个树。为了将一个纵向的树横向的呈现,我们必须使用前根遍历该树的所有子节点,并依次调用子节点的ToString方法,并在遍历到叶子节点的时候加上“回车”(</tr>)以表示该html行结束。
注意到ITree在INode的基础上加上了一个FullFill的方法,该方法用于将一个非满树用空结点补满,否则表格就会出现缺格。其中T是指用于填充树的类型,也就是用何种INode来填满这颗树。当然这应该和你用来构建这颗树的节点类型一样。
BaseNode是个模版类,其对添加和移除子节点时作了些额外控制,防止同一个INode对象添加到了2个或以上的父节点下:
BaseTree是ITree的模版实现,其中的关键在于如何计算每个节点的rowspan(只需要计算以该节点为根的子树有几个叶子节点,即表示该节点需要多少rowspan),并且这里的IEnumerator必须以前序遍历来返回所有子节点,原因之前已经提到过。
最后就是节点的具体实现了,SingletonNode是单td树节点:
而ContainIndexNode是带index的双td树节点:
其他有关INode集合以及html标签属性的接口和类的实现这里就不贴出来了,大家如果有兴趣可以下载源代码看下。
好了,赶快建个页面看下效果吧 ^_^
效果分别如下图:
第一次写blog,如果写的不好请大家见谅
TreeTable源代码
类的结构图如下:
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 }
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 }
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 }
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 - 4, 4); //移除最后的<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, " "); //空节点
130 // for (int i = 0; i < length - 1; i++)
131 // {
132 // INode tempNode = new SingletonNode(" "); //空节点
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 = " ";
149
150 for (int i = 0; i < length - 1; i++)
151 {
152 INode tempNode = new T(); //空节点
153 tempNode.Content = " ";
154
155 node.AddChild(tempNode);
156 node = tempNode; //持有下一个节点
157 }
158 }
159 }
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 - 4, 4); //移除最后的<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, " "); //空节点
130 // for (int i = 0; i < length - 1; i++)
131 // {
132 // INode tempNode = new SingletonNode(" "); //空节点
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 = " ";
149
150 for (int i = 0; i < length - 1; i++)
151 {
152 INode tempNode = new T(); //空节点
153 tempNode.Content = " ";
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 = " "; //若该td中内容为空则需要显示一个空格,否则该td会显示不出来
66
67 builder.Append(@"<td ").Append(attriStr).Append(@">").Append(showContent).Append(@"</td>");
68 return builder.ToString();
69 }
70
71 }
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 = " "; //若该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 = " "; //若该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的呈现方式上就可以了。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 = " "; //若该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 }
其他有关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}
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源代码