最近项目中常要画动态的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;
}
}
重写树节点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;
}
}
/// <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, " "); //空节点
// for (int i = 0; i < length - 1; i++)
// {
// INode tempNode = new SingletonNode(" "); //空节点
// 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 = " "; for (int i = 0; i < length - 1; i++)
{
INode tempNode = new T(); //空节点
tempNode.Content = " "; 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 = " "; //若该td中内容为空则需要显示一个空格,否则该td会显示不出来 builder.Append(@"<td ").Append(attriStr).Append(@">").Append(showContent).Append(@"</td>");
return builder.ToString();
} }
/// 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 = " "; //若该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
一但一个td的rowspan有变,那么会影响到多个tr中的td的数量。
如果我是一个制定计划大纲的客户,从1条主任务,到下面的分支任务,再到再下层的实施步骤,一层一层细分下来填写的话,就只能动态的画出表格来,如果是用代码画过html表格的人一定会有体会。
只要rowspan 或 colspan 不对
整个表格就会显示错乱
不过要是楼主能够用javascript写一个
动态表格构建的实用类就更好了
最终生成的是html表格的字符串,可以用ajax传到前台显示。
我js水平不太过关,纯js写比较累,呵呵 ...
用Repater 和DataList空间想怎么画就怎么画
那用这么麻烦呢
重点在于对rowspan的控制