Silverlight多重表头实现

效果:

  

实现主要逻辑:通过动态拼接XML生成表头样式,绑定到列上。

主要是动态拼接XML时要仔细核对对应的占位行,具体可以看代码,注释很详细

两个类一个接口

NTree<T>:定义表头树形结构

  1 using System;
  2 using System.Collections.Generic;
  3 using System.Linq;
  4 using System.Collections.ObjectModel;
  5 
  6 namespace SLDGHeader
  7 {
  8     /// <summary>
  9     /// 树结构
 10     /// </summary>
 11     /// <typeparam name="T">节点中的数据</typeparam>
 12     public class NTree<T>
 13     {
 14         /// <summary>
 15         /// 节点数据
 16         /// </summary>
 17         private readonly T data;
 18         /// <summary>
 19         /// 节点数据
 20         /// </summary>
 21         public T Data
 22         {
 23             get { return data; }
 24         }
 25         /// <summary>
 26         /// 是否根节点
 27         /// </summary>
 28         public bool IsRoot { get { return Parent == null; } }
 29         /// <summary>
 30         /// 当前节点深度
 31         /// 根节点为1
 32         /// </summary>
 33         public int Depth { get; private set; }
 34         /// <summary>
 35         /// 父节点
 36         /// </summary>
 37         public NTree<T> Parent
 38         {
 39             get;
 40             private set;
 41         }
 42         /// <summary>
 43         /// 子节点
 44         /// </summary>
 45         public ReadOnlyCollection<NTree<T>> Children
 46         {
 47             get { return children.AsReadOnly(); }
 48         }
 49         private List<NTree<T>> children;
 50         /// <summary>
 51         /// 实例化一个节点
 52         /// </summary>
 53         /// <param name="data">节点数据</param>
 54         public NTree(T data)
 55         {
 56             this.Depth = 1;
 57             this.data = data;
 58             children = new List<NTree<T>>();
 59         }
 60         /// <summary>
 61         /// 在当前节点添加子节点
 62         /// </summary>
 63         /// <param name="data">节点数据</param>
 64         /// <returns>当前节点</returns>
 65         public NTree<T> AddChild(T data)
 66         {
 67             var node = new NTree<T>(data) { Parent = this, Depth = this.Depth + 1 };
 68             children.Add(node);
 69             return this;
 70         }
 71         /// <summary>
 72         /// 在当前节点子节点中插入子节点
 73         /// </summary>
 74         /// <param name="index">插入位置</param>
 75         /// <param name="data">节点数据</param>
 76         /// <returns>当前节点</returns>
 77         public NTree<T> InsertChild(int index, T data)
 78         {
 79             var node = new NTree<T>(data) { Parent = this, Depth = this.Depth + 1 };
 80             children.Insert(index, node);
 81             return this;
 82         }
 83         /// <summary>
 84         /// 在当前节点添加子节点
 85         /// </summary>
 86         /// <param name="data">节点数据</param>
 87         /// <returns>当前节点</returns>
 88         public NTree<T> AddChilren(params T[] datas)
 89         {
 90             foreach (var data in datas)
 91             {
 92                 AddChild(data);
 93             }
 94             return this;
 95         }
 96         /// <summary>
 97         /// 移除当前节点下指定的子节点
 98         /// </summary>
 99         /// <param name="node">要移除的子节点</param>
100         /// <returns>当前节点</returns>
101         public NTree<T> RemoveChild(NTree<T> node)
102         {
103             children.Remove(node);
104             return this;
105         }
106         /// <summary>
107         /// 在当前节点添加兄弟节点
108         /// </summary>
109         /// <param name="data">节点数据</param>
110         /// <returns>当前节点</returns>
111         /// <exception cref="NullParentNodeException:当前节点没有父节点">当前节点没有父节点</exception>
112         public NTree<T> AddBrother(T data)
113         {
114             if (this.Parent == null)
115             {
116                 throw new NullParentNodeException("有父节点的节点才能添加兄弟节点。");
117             }
118 
119             this.Parent.AddChild(data);
120             return this;
121         }
122         /// <summary>
123         /// 获取指定索引处的子节点
124         /// </summary>
125         /// <param name="i">子节点索引</param>
126         /// <returns>子节点</returns>
127         /// <exception cref="ArgumentOutOfRangeException:子节点索引超出范围">子节点索引超出范围</exception>
128         public NTree<T> GetChild(int i)
129         {
130             if (i >= children.Count || i < 0)
131             {
132                 throw new ArgumentOutOfRangeException("子节点索引超出范围");
133             }
134             return children[i];
135         }
136         /// <summary>
137         /// 获取指定的子节点
138         /// </summary>
139         /// <param name="data">节点数据</param>
140         /// <returns>查找到的第一个子节点</returns>
141         public NTree<T> GetChild(T data)
142         {
143             return children.Where(i => i.data.Equals(data)).FirstOrDefault();
144         }
145         /// <summary>
146         /// 获取指定子节点的索引
147         /// </summary>
148         /// <param name="data">节点数据</param>
149         /// <returns>查找到的第一个子节点的索引,没有找到返回-1</returns>
150         public int GetChildIndex(NTree<T> data)
151         {
152             var index = -1;
153             for (int i = 0; i < children.Count; i++)
154             {
155                 if (children[i].Equals(data))
156                 {
157                     index = i;
158                     break;
159                 }
160             }
161             return index;
162         }
163         /// <summary>
164         /// 遍历树节点
165         /// </summary>
166         /// <param name="node">起始节点</param>
167         /// <param name="action">遍历到每个节点的操作</param>
168         /// <exception cref="ArgumentNullException:参数action不可为空">参数action不可为空</exception>
169         public static void Traverse(NTree<T> node, Action<T> action)
170         {
171             if (action == null)
172             {
173                 throw new ArgumentNullException("参数action不可为空");
174             }
175             action(node.data);
176             foreach (var kid in node.children)
177             {
178                 Traverse(kid, action);
179             }
180         }
181         /// <summary>
182         /// 从当前节点开始遍历树节点
183         /// </summary>
184         /// <param name="action">遍历到每个节点的操作</param>
185         /// <exception cref="ArgumentNullException:参数action不可为空">参数action不可为空</exception>
186         public void Traverse(Action<T> action)
187         {
188             Traverse(this, action);
189         }
190         /// <summary>
191         /// 遍历树节点
192         /// </summary>
193         /// <param name="node">起始节点</param>
194         /// <param name="action">遍历到每个节点的操作</param>
195         /// <exception cref="ArgumentNullException:参数action不可为空">参数action不可为空</exception>
196         public static void Traverse(NTree<T> node, Action<NTree<T>> action)
197         {
198             if (action == null)
199             {
200                 throw new ArgumentNullException("参数action不可为空");
201             }
202             action(node);
203             foreach (var kid in node.children)
204             {
205                 Traverse(kid, action);
206             }
207         }
208         /// <summary>
209         /// 从当前节点开始遍历树节点
210         /// </summary>
211         /// <param name="action">遍历到每个节点的操作</param>
212         /// <exception cref="ArgumentNullException:参数action不可为空">参数action不可为空</exception>
213         public void Traverse(Action<NTree<T>> action)
214         {
215             if (action == null)
216             {
217                 throw new ArgumentNullException("参数action不可为空");
218             }
219             action(this);
220             foreach (var kid in this.children)
221             {
222                 Traverse(kid, action);
223             }
224         }
225         /// <summary>
226         /// 获取当前节点开始的所有节点中数据
227         /// </summary>
228         /// <returns>节点数据列表</returns>
229         public IEnumerable<T> GetDatas()
230         {
231             return new[] { data }.Union(children.SelectMany(x => x.GetDatas()));
232         }
233         /// <summary>
234         /// 当前节点下叶节点的数量
235         /// </summary>
236         /// <returns></returns>
237         public int GetCount()
238         {
239             var count = 0;
240             Traverse((NTree<T> n) =>
241             {
242                 if (n.Children.Count == 0)
243                 {
244                     count++;
245                 }
246             });
247             return count;
248         }
249         /// <summary>
250         /// 获取当前节点所在树的深度
251         /// </summary>
252         /// <returns>当前节点所在树的深度</returns>
253         public int GetTreeDepth()
254         {
255             int Depth = 1;
256             var parent = this;
257             while (parent.Parent != null)
258             {
259                 parent = parent.Parent;
260             }
261             Traverse((NTree<T> n) =>
262             {
263                 if (Depth < n.Depth)
264                 {
265                     Depth = n.Depth;
266                 }
267             });
268 
269             return Depth;
270         }
271     }
272     /// <summary>
273     /// 父节点为空引用异常
274     /// </summary>
275     public class NullParentNodeException : Exception
276     {
277         public NullParentNodeException()
278             : base("父节点为空引用")
279         {
280 
281         }
282         public NullParentNodeException(string message)
283             : base(message)
284         {
285 
286         }
287         public NullParentNodeException(string message, Exception inner)
288             : base(message)
289         {
290 
291         }
292         //public NullParentNodeException(SerializationInfo info, StreamingContext context)
293         //{
294 
295         //}
296     }
297 }
View Code

MultiHeadersColumn:多重表头和绑定的列

IDataGridHeader:定义生成表头和绑定列的数据接口

  1 using System;
  2 using System.Windows;
  3 using System.Windows.Controls;
  4 using System.Collections.Generic;
  5 using System.Windows.Markup;
  6 using System.Text;
  7 
  8 namespace SLDGHeader
  9 {
 10     public class MultiHeadersColumn
 11     {
 12         #region 字段
 13         /// <summary>
 14         /// 单元格边框颜色
 15         /// </summary>
 16         string splitLineColor = "#ccc";
 17         /// <summary>
 18         /// 数据行宽度
 19         /// </summary>
 20         string dataWidth = "30";
 21         /// <summary>
 22         /// 表头行高度
 23         /// </summary>
 24         string dataHeight = "auto";
 25         /// <summary>
 26         /// 分隔线线宽度
 27         /// </summary>
 28         string lineWidth = "1";
 29         /// <summary>
 30         /// 分隔符线高度
 31         /// </summary>
 32         string lineHeight = "1";
 33         #endregion
 34         #region 属性
 35         /// <summary>
 36         /// 单元格边框颜色
 37         /// </summary>
 38         public string SplitLineColor
 39         {
 40             get { return splitLineColor; }
 41             set { splitLineColor = value; }
 42         }
 43         /// <summary>
 44         /// 数据行宽度
 45         /// </summary>
 46         public string DataWidth
 47         {
 48             get { return dataWidth; }
 49             set { dataWidth = value; }
 50         }
 51         /// <summary>
 52         /// 表头行高度
 53         /// </summary>
 54         public string DataHeight
 55         {
 56             get { return dataHeight; }
 57             set { dataHeight = value; }
 58         }
 59         /// <summary>
 60         /// 分隔线线宽度
 61         /// </summary>
 62         public string LineWidth
 63         {
 64             get { return lineWidth; }
 65             set { lineWidth = value; }
 66         }
 67         /// <summary>
 68         /// 分隔符线高度
 69         /// </summary>
 70         public string LineHeight
 71         {
 72             get { return lineHeight; }
 73             set { lineHeight = value; }
 74         }
 75         #endregion
 76         public DataGridTemplateColumn GetMultiHeadersColumn(NTree<IDataGridHeader> node)
 77         {
 78             DataGridTemplateColumn col = GetTemplateColumn(node);
 79             col.HeaderStyle = GetStyle("NameHeaderStyle", node);
 80             return col;
 81         }
 82         DataGridTemplateColumn GetTemplateColumn(NTree<IDataGridHeader> node)
 83         {
 84             List<NTree<IDataGridHeader>> nodes = new List<NTree<IDataGridHeader>>();
 85             node.Traverse((NTree<IDataGridHeader> n) =>
 86             {
 87                 if (n.Children.Count == 0)
 88                 {
 89                     nodes.Add(n);
 90                 }
 91             });
 92             string strtemplate = GetDataTemplate(nodes);
 93             DataTemplate template = (DataTemplate)XamlReader.Load(strtemplate);
 94 
 95             DataGridTemplateColumn colunm = new DataGridTemplateColumn();
 96             colunm.CellTemplate = template;
 97 
 98             return colunm;
 99         }
100         /// <summary>
101         /// 获取绑定列模板XML字符串
102         /// </summary>
103         /// <param name="nodes">列对应节点</param>
104         /// <returns>返回列模板XML字符串</returns>
105         string GetDataTemplate(List<NTree<IDataGridHeader>> nodes)
106         {
107             if (nodes == null)
108             {
109                 throw new ArgumentNullException("参数nodes不能为空");
110             }
111             StringBuilder sb = new StringBuilder(200);
112 
113             sb.Append(@"<DataTemplate ");
114             sb.AppendLine("xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' ");
115             sb.AppendLine("xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' ");
116             sb.AppendLine("xmlns:sdk='http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk'");
117             sb.AppendLine(" >");
118 
119             sb.AppendLine("<StackPanel Orientation='Horizontal'> ");
120             for (int i = 0; i < nodes.Count * 2 - 1; i++)
121             {
122                 if (i % 2 == 0)
123                 {
124                     sb.Append("<TextBlock VerticalAlignment='Center' ");
125                     sb.Append(" Width='").Append(dataWidth).Append("' ");
126                     sb.Append(" HorizontalAlignment='Center' TextWrapping='Wrap' Text='{Binding ").Append(nodes[(i + 1) / 2].Data.ColName).AppendLine("}' /> ");
127                 }
128                 else
129                 {
130                     sb.Append(" <Rectangle Fill='").Append(splitLineColor).Append("' VerticalAlignment='Stretch' ");
131                     sb.Append(" Width='").Append(lineWidth).Append("' ");
132                     sb.AppendLine(" /> ");
133                 }
134             }
135 
136             sb.AppendLine("</StackPanel> ");
137             sb.AppendLine("</DataTemplate> ");
138             return sb.ToString();
139         }
140 
141         Style GetStyle(string headerstyle, NTree<IDataGridHeader> node)
142         {
143             var depth = node.GetTreeDepth();
144             string stylestr = GetStyleStr("NameHeaderStyle", depth, node);
145             Style ste = (Style)XamlReader.Load(stylestr);
146 
147             return ste;
148         }
149         /// <summary>
150         /// 获取表头样式XML字符串
151         /// </summary>
152         /// <param name="headerstyle">样式名称</param>
153         /// <param name="depth">树的深度</param>
154         /// <param name="node">树的根节点</param>
155         /// <returns>返回表头样式XML字符串</returns>
156         string GetStyleStr(string headerstyle, int depth, NTree<IDataGridHeader> node)
157         {
158 
159             StringBuilder sb = new StringBuilder(2000);
160             //计算数据列数量
161             int colCount = node.GetCount();
162 
163             AppendHeader(headerstyle, sb);
164             //构建表头行列
165             sb.AppendLine("<Grid HorizontalAlignment='{TemplateBinding HorizontalContentAlignment}' VerticalAlignment='{TemplateBinding VerticalContentAlignment}'>");
166             //多少行
167             sb.AppendLine("<Grid.RowDefinitions>");
168             //int rowCount = 3;
169             for (int i = 0; i < depth * 2; i++)
170             {
171                 if (i % 2 == 0)
172                 {
173                     sb.AppendLine("<RowDefinition ");
174                     sb.Append(" Height='").Append(dataHeight).Append("' ");
175                     sb.AppendLine(" /> ");
176                 }
177                 else
178                 {
179                     sb.AppendLine("<RowDefinition ");
180                     sb.Append(" Height='").Append(lineHeight).Append("' ");
181                     sb.AppendLine("/>");
182                 }
183             }
184             sb.AppendLine("</Grid.RowDefinitions>");
185 
186             //多少列
187             sb.AppendLine("<Grid.ColumnDefinitions>");
188 
189             for (int i = 0; i < colCount * 2; i++)
190             {
191                 if (i % 2 == 0)
192                 {
193                     sb.AppendLine("<ColumnDefinition ");
194                     sb.Append("Width='").Append(dataWidth).Append("' ");
195                     sb.AppendLine(" />");
196                 }
197                 else
198                 {
199                     sb.AppendLine("<ColumnDefinition ");
200                     sb.Append("Width='").Append(lineWidth).Append("' ");
201                     sb.AppendLine(" />");
202                 }
203 
204             }
205             sb.AppendLine("</Grid.ColumnDefinitions>");
206 
207             //开始构单元格
208             AppendCell(node, sb, depth, colCount);
209 
210 
211             AppendEnd(sb);
212 
213             return sb.ToString();
214         }
215         /// <summary>
216         /// 绘制头部和表头背景
217         /// </summary>
218         /// <param name="headerstyle">样式名称</param>
219         /// <param name="sb"></param>
220         private void AppendHeader(string headerstyle, StringBuilder sb)
221         {
222             sb.Append(@"<Style ");
223             sb.AppendLine("xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' ");
224             sb.AppendLine("xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' ");
225             sb.AppendLine("xmlns:dataprimitives='clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls.Data' ");
226 
227             //列样式名称
228             sb.Append(@" x:Key='").Append(headerstyle).Append("' ");
229 
230             sb.AppendLine(@"TargetType='dataprimitives:DataGridColumnHeader' >");
231             sb.AppendLine("<Setter Property='Template'>");
232             sb.AppendLine("<Setter.Value>");
233             sb.AppendLine("<ControlTemplate>");
234             sb.AppendLine("<Grid x:Name='Root'>");
235             sb.AppendLine("<Grid.ColumnDefinitions>");
236             sb.AppendLine("<ColumnDefinition/>");
237             sb.AppendLine("<ColumnDefinition Width='Auto'/>");
238             sb.AppendLine("</Grid.ColumnDefinitions>");
239             sb.AppendLine("<Rectangle x:Name='BackgroundRectangle' Fill='#FF1F3B53' Stretch='Fill' Grid.ColumnSpan='2'/>");
240             sb.AppendLine("<Rectangle x:Name='BackgroundGradient' Stretch='Fill' Grid.ColumnSpan='2'>");
241             sb.AppendLine("<Rectangle.Fill>");
242             //表头背景色
243             sb.AppendLine("<LinearGradientBrush EndPoint='.7,1' StartPoint='.7,0'>");
244             sb.AppendLine(@"<GradientStop Color='#FCFFFFFF' Offset='0.015'/>
245                         <GradientStop Color='#F7FFFFFF' Offset='0.375'/>
246                         <GradientStop Color='#E5FFFFFF' Offset='0.6'/>
247                         <GradientStop Color='#D1FFFFFF' Offset='1'/>");
248             sb.AppendLine("</LinearGradientBrush>");
249             sb.AppendLine("</Rectangle.Fill>");
250             sb.AppendLine("</Rectangle>");
251         }
252         /// <summary>
253         /// 添加结束XML部分
254         /// </summary>
255         /// <param name="sb"></param>
256         private void AppendEnd(StringBuilder sb)
257         {
258             sb.AppendLine("</Grid>");
259             //绘制最后一列分割线
260             sb.AppendLine(@"<Rectangle x:Name='VerticalSeparator' Fill='")
261                 .Append(splitLineColor)
262                 .Append(@"' VerticalAlignment='Stretch'")
263                 .Append(" Width='").Append(lineWidth).Append("' ")
264                 .AppendLine(" Visibility='Visible' Grid.Row='1' Grid.Column='1' />");
265             sb.AppendLine("</Grid>");
266             sb.AppendLine("</ControlTemplate>");
267             sb.AppendLine("</Setter.Value>");
268             sb.AppendLine("</Setter>");
269             sb.AppendLine("</Style>");
270         }
271         /// <summary>
272         /// 构建行
273         /// 偶数行、偶数列为数据,奇数行、奇数列为分割线,以此计算单元格占位、合并行列
274         /// </summary>
275         /// <param name="node">构建行的树结构</param>
276         /// <param name="sb">字符串</param>
277         /// <param name="depth">树的深度</param>
278         private StringBuilder AppendCell(NTree<IDataGridHeader> node, StringBuilder sb, int depth, int totalCol)
279         {
280             //当前节点左兄弟节点的页节点数量,用于计算当前单元格列位置
281             var precolCount = 0;
282             if (!node.IsRoot)
283             {
284                 precolCount = GetLeftCount(node);
285             }
286 
287             //当前节点的页节点数量
288             var colCount = node.GetCount();
289 
290             //1、数据单元格
291             sb.Append(@"<ContentPresenter  ");
292             sb.Append(@"Content='").Append(node.Data.DisplayColName).Append("' ");
293             sb.Append(@"VerticalAlignment='Center' HorizontalAlignment='Center' ");
294             sb.Append(" Grid.Row='").Append((node.Depth - 1) * 2).Append("' ");
295             //考虑表头行可能不一致(层级有深有浅),层级较少的需要合并列显示,所有合并在最后一行表头
296             var rowSpan = 1;
297             if (node.Children.Count == 0 && node.Depth != depth)
298             {
299                 rowSpan = depth * 2 - 1 - (node.Depth - 1) * 2;
300                 sb.Append(" Grid.RowSpan='").Append(rowSpan).Append("' ");
301             }
302 
303             sb.Append(" Grid.Column='").Append(precolCount * 2).Append("' ");
304             sb.Append("Grid.ColumnSpan='").Append(colCount * 2 - 1).AppendLine("' />");
305 
306             //2、行分隔线单元格
307             sb.Append(@"<Rectangle Fill='").Append(splitLineColor).Append("' VerticalAlignment='Stretch' ")
308                 .Append("Height='").Append(lineHeight).Append("' ");
309 
310             //表头行合并后要调整分割线,否则会在合并单元格中间显示分割线
311             if (node.Children.Count == 0 && node.Depth != depth)
312             {
313                 //如果有合并单元,则分割线要跳过合并单元格,合并数量为(rowSpan - 1)
314                 sb.Append("Grid.Row='").Append(node.Depth * 2 - 1 + rowSpan - 1).Append("' ");
315             }
316             else
317             {
318                 sb.Append("Grid.Row='").Append(node.Depth * 2 - 1).Append("' ");
319             }
320 
321             sb.Append(" Grid.Column='").Append(precolCount * 2).Append("' ");
322             sb.Append("Grid.ColumnSpan='").Append(colCount * 2).AppendLine("' />");
323 
324             //4、列分隔线单元格
325             //最后一列分割线不绘制,否则在调整了后面列的列宽会出现类似空列的单元格
326             if ((precolCount + colCount) != totalCol)
327             {
328                 sb.Append(@"<Rectangle Fill='").Append(splitLineColor).Append("' VerticalAlignment='Stretch' ")
329                     .Append(" Width='").Append(lineWidth).Append("' ");
330 
331                 sb.Append("Grid.Row='").Append((node.Depth - 1) * 2).Append("' ");
332                 sb.Append("Grid.RowSpan='").Append(rowSpan).Append("' ");
333                 sb.Append("Grid.Column='").Append((precolCount + colCount) * 2 - 1).AppendLine("' />");
334             }
335 
336 
337             //5、递归生成子节点单元格
338             if (node.Children.Count != 0)
339             {
340                 foreach (var item1 in node.Children)
341                 {
342                     AppendCell(item1, sb, depth, totalCol);
343                 }
344             }
345 
346 
347             return sb;
348         }
349         /// <summary>
350         /// 获取当前节点左边的叶节点数量
351         /// </summary>
352         /// <param name="node"></param>
353         /// <returns></returns>
354         private int GetLeftCount(NTree<IDataGridHeader> node)
355         {
356             var precolCount = 0;
357             var index = node.Parent.GetChildIndex(node);
358             for (int i = 0; i < index; i++)
359             {
360                 precolCount += node.Parent.GetChild(i).GetCount();
361             }
362             if (!node.Parent.IsRoot)
363             {
364                 precolCount += GetLeftCount(node.Parent);
365             }
366             return precolCount;
367         }
368     }
369     public interface IDataGridHeader
370     {
371         /// <summary>
372         /// 绑定列名
373         /// </summary>
374         string ColName { get; set; }
375         /// <summary>
376         /// 显示名称
377         /// </summary>
378         string DisplayColName { get; set; }
379     }
380 }
View Code
原文地址:https://www.cnblogs.com/missile/p/7133503.html