以横向树方式显示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#region children operation
19
20        void AddChild(INode child);
21        bool RemoveChild(INode child);
22        void ClearChildren();
23
24        #endregion

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 成员#region INode 成员
 24
 25        /**//// <summary>
 26        /// 取得父结点
 27        /// </summary>

 28        public INode Parent
 29        {
 30            get return this.parent; }
 31            set
 32            {
 33                value.AddChild(this); //设定父节点的同时,在父节点的子列表中加入该结点
 34                this.parent = value;
 35            }

 36        }

 37
 38        /**//// <summary>
 39        /// 取得下级节点--子结点的外部表现(只读)
 40        /// </summary>

 41        public INodeList Childs
 42        {
 43            get return new BaseNodeList(childs); }
 44        }

 45
 46        /**//// <summary>
 47        /// 取得以该节点为根的子数的叶子节点
 48        /// </summary>

 49        public INodeList Leafs
 50        {
 51            get
 52            {
 53                IList<INode> leafs = new List<INode>();
 54                foreach (INode node in this)
 55                {
 56                    //判断是否为叶子结点
 57                    if (node.Childs.Count == 0)
 58                        leafs.Add(node);
 59                }

 60                return new BaseNodeList(leafs);
 61            }

 62        }

 63
 64        /**//// <summary>
 65        /// 是否是叶子结点
 66        /// </summary>

 67        public bool IsLeaf
 68        {
 69            get
 70            {
 71                if (this.childs.Count == 0)
 72                    return true;
 73                return false;
 74            }

 75        }

 76
 77        /**//// <summary>
 78        /// 取得该节点在树中的所处层数(从0开始计数)
 79        /// </summary>

 80        public int Tier
 81        {
 82            get return this.getTier(this); }
 83        }

 84
 85        /**//// <summary>
 86        /// 取得以该节点为根的子数的深度(本层为0)
 87        /// </summary>

 88        public int Depth
 89        {
 90            get return this.getDepth(this); }
 91        }

 92
 93        抽象方法#region 抽象方法
 94
 95        /**//// <summary>
 96        /// 属性列表(根据其ToString方法来显示)
 97        /// </summary>

 98        public abstract IAttributeDictionary Attributes get;set;}
 99
100        /**//// <summary>
101        /// 结点内容(可以是任何对象,最终根据INode.ToString方法来显示)
102        /// </summary>

103        public abstract object Content get;set;}
104
105        /**//// <summary>
106        /// 必须重写ToString方法,用于呈现该INode
107        /// </summary>
108        /// <returns></returns>

109        public abstract override string ToString();
110
111        #endregion

112
113        操作子结点#region 操作子结点
114
115        /**//// <summary>
116        /// 添加一个子结点
117        /// </summary>
118        /// <param name="item"></param>

119        public void AddChild(INode item)
120        {
121            INode oldParent = item.Parent; //原父结点
122            if (oldParent == null || oldParent.RemoveChild(item)) //从原父节点的子结点中移除该结点
123            {
124                childs.Add(item);
125                //item.Parent = this;  //该句会导致无限递归错误!
126                ((BaseNode)item).parent = this;
127            }

128        }

129
130        /**//// <summary>
131        /// 移除一个子结点
132        /// </summary>
133        /// <param name="item"></param>
134        /// <returns></returns>

135        public bool RemoveChild(INode item)
136        {
137            if (this.childs.Remove(item))
138            {
139                item.Parent = null;
140                return true;
141            }

142            return false;
143        }

144
145        /**//// <summary>
146        /// 清空子结点
147        /// </summary>

148        public void ClearChildren()
149        {
150            foreach (INode node in childs)
151            {
152                childs.Remove(node);
153            }

154        }

155
156        #endregion

157
158        #endregion

159
160        IEnumerable 成员#region IEnumerable<INode> 成员
161
162        /**//// <summary>
163        /// 先序遍历
164        /// </summary>
165        /// <returns></returns>

166        public IEnumerator<INode> GetEnumerator()
167        {
168            Queue<INode> queueList = new Queue<INode>(); //按序进出的队列
169            perOrderTraverse(queueList, this); //本身不入队列
170            while (queueList.Count > 0 && queueList.Peek() != null)
171            {
172                yield return queueList.Dequeue();
173            }

174        }

175
176        #endregion

177
178        IEnumerable 成员#region IEnumerable 成员
179
180        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
181        {
182            return GetEnumerator();
183        }

184
185        #endregion

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 成员#region ITree 成员
  9
 10        /**////// <summary>
 11        ///// 返回该树的"满数"
 12        ///// </summary>
 13        ///// <returns></returns>

 14        //public virtual ITree FullFill()
 15        //{
 16        //    int treeHeight = this.Depth; //该树的高度
 17        //    INodeList leafs = this.Leafs; //该树的叶子集合
 18        //    foreach (INode node in leafs)
 19        //    {
 20        //        int tier = node.Tier; //某叶子结点的所在层数
 21        //        if (tier < treeHeight)//填到树的高度
 22        //        {
 23        //            int length = treeHeight - tier; //需要填充的高度
 24        //            this.addFixLengthNode(length, node);
 25        //        }
 26        //    }
 27        //    return this;
 28        //}
 29
 30        /**//// <summary>
 31        /// 返回该树的"满数"(用T类型的结点填充)
 32        /// </summary>
 33        /// <returns></returns>

 34        public virtual ITree FullFill<T>() where T : INode, new()
 35        {
 36            int treeHeight = this.Depth; //该树的高度
 37            INodeList leafs = this.Leafs; //该树的叶子集合
 38            foreach (INode node in leafs)
 39            {
 40                int tier = node.Tier; //某叶子结点的所在层数
 41                if (tier < treeHeight)//填到树的高度
 42                {
 43                    int length = treeHeight - tier; //需要填充的高度
 44                    this.addFixLengthNode<T>(length, node);
 45                }

 46            }

 47            return this;
 48        }

 49
 50        #endregion

 51
 52        IEnumerable 成员#region IEnumerable 成员
 53
 54        public new IEnumerator GetEnumerator()
 55        {
 56            return base.GetEnumerator();
 57        }

 58
 59        #endregion

 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        构造器#region 构造器
10
11        public SingletonNode()
12        { }
13
14        public SingletonNode(string content)
15        {
16            this.content = content;
17        }

18
19        public SingletonNode(INode parent, string content)
20            : base(parent)
21        {
22            this.content = content;
23        }

24
25        public SingletonNode(INode parent, string content, IAttributeDictionary attributes)
26            : this(parent, content)
27        {
28            this.attributes = attributes; //属性列表
29        }

30
31        #endregion

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        构造器#region 构造器
 10        public ContainIndexNode()
 11        { }
 12
 13        public ContainIndexNode(string content)
 14        {
 15            this.content = content;
 16        }

 17
 18        public ContainIndexNode(INode parent, string content)
 19            : base(parent)
 20        {
 21            this.content = content;
 22        }

 23
 24        public ContainIndexNode(INode parent, string content, IAttributeDictionary attributes)
 25            : this(parent, content)
 26        {
 27            this.attributes = attributes; //属性列表
 28        }

 29
 30        #endregion

 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        取得索引#region 取得索引
 72
 73        private string getIndex()
 74        {
 75            System.Text.StringBuilder builder = new System.Text.StringBuilder();
 76            System.Collections.Generic.Stack<int> stack = new System.Collections.Generic.Stack<int>();
 77            int parts = this.Tier;
 78            int index = 0;
 79
 80            INode node = this;
 81            while (index < parts)
 82            {
 83                int num = this.getIndex(node);
 84                stack.Push(num);
 85                node = node.Parent;
 86
 87                index++;
 88            }

 89
 90            while (stack.Count > 0)
 91            {
 92                builder.Append(stack.Pop()).Append(".");
 93            }

 94
 95            builder.Remove(builder.Length - 11); //移除最后的点
 96            return builder.ToString();
 97        }

 98
 99        private int getIndex(INode node)
100        {
101            INodeList list = node.Parent.Childs;
102            for (int i = 0; i < list.Count; i++)
103            {
104                if (list[i] == node)
105                    return i + 1;
106            }

107            return 0;
108        }

109
110        #endregion

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

其他有关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#region build node
 8        INode node0 = nodeFactory("node0");
 9        INode node1 = nodeFactory("node1");
10        INode node2 = nodeFactory("node2");
11        INode node3 = nodeFactory("node3");
12        INode node4 = nodeFactory("node4");
13        INode node5 = nodeFactory("node5");
14        INode node6 = nodeFactory("node6");
15        INode node7 = nodeFactory("node7");
16        INode node8 = nodeFactory("node8");
17        INode node9 = nodeFactory(null);
18
19        tree.AddChild(node0);
20        tree.Attributes.Add(new SingletonAttri("border""1px"));
21
22        node0.AddChild(node1);
23        node0.AddChild(node2);
24        node0.AddChild(node3);
25
26        node1.AddChild(node4);
27        node2.AddChild(node5);
28        node2.AddChild(node6);
29
30        node3.AddChild(node9);
31        node6.AddChild(node7);
32        node7.AddChild(node8);
33
34        node7.Attributes.Add(new SingletonAttri("bgcolor""#FFE0D1"));
35
36        node8.AddChild(nodeFactory("node10"));
37        node2.AddChild(nodeFactory("node11"));
38        node7.AddChild(nodeFactory("node12"));
39        INode node13 = nodeFactory("node13");
40        node6.AddChild(node13);
41
42        INode node15 = nodeFactory("node15");
43        INode node14 = nodeFactory("node14");
44        tree.AddChild(node15);
45        node15.AddChild(node14);
46        node15.AddChild(nodeFactory("node17"));
47        node14.AddChild(nodeFactory("node16"));
48
49        node13.Attributes.Add(new SingletonAttri("onclick""window.alert('ok');"));
50        #endregion

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源代码

转载于:https://www.cnblogs.com/jeremyyang824/archive/2008/05/24/1206232.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值