以横向树方式显示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
}
/// <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 operation25

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
}
/// <summary>2
/// ITree 的摘要说明3
/// </summary>4
public interface ITree : INode5
{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
}
/// <summary>2
/// Node 的摘要说明3
/// </summary>4
public abstract class BaseNode : INode5
{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
IEnumerable177

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; //取得最大层数子树的层数 + 1216
}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
}
/// <summary>2
/// Tree 的摘要说明3
/// </summary>4
public class BaseTree : BaseNode, ITree5
{6
protected IAttributeDictionary attributes = new BaseAttributeDictionary(); //属性集合7

8
ITree 成员51

52
IEnumerable 成员60

61
public override IAttributeDictionary Attributes62
{63
get { return this.attributes; }64
set { this.attributes = value; }65
}66

67
public override object Content68
{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
}
/// <summary>2
/// SingletonNode -- 单td树结点3
/// </summary>4
public class SingletonNode : BaseNode5
{6
private string content; //td中的内容7
private IAttributeDictionary attributes = new BaseAttributeDictionary(); //td中的属性集合8

9
构造器32

33
public override IAttributeDictionary Attributes34
{35
get36
{37
return this.attributes;38
}39
set40
{41
this.attributes = value;42
}43
}44

45
public override object Content46
{47
get48
{49
return this.content;50
}51
set52
{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的呈现方式上就可以了。
/// <summary>2
/// ContainIndexNode 包含index的结点3
/// </summary>4
public class ContainIndexNode : BaseNode5
{6
private string content; //td2中的内容7
private IAttributeDictionary attributes = new BaseAttributeDictionary(); //td中的属性集合8

9
构造器31

32
public override IAttributeDictionary Attributes33
{34
get35
{36
return this.attributes;37
}38
set39
{40
this.attributes = value;41
}42
}43

44
public override object Content45
{46
get47
{48
return this.content;49
}50
set51
{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标签属性的接口和类的实现这里就不贴出来了,大家如果有兴趣可以下载源代码看下。
好了,赶快建个页面看下效果吧 ^_^
1
public 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
}
public 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 node51
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源代码

以组合模式建立横向树形Html表格


浙公网安备 33010602011771号