Silverlight多重表头实现

  • 2019 年 10 月 10 日
  • 筆記

效果:

实现主要逻辑:通过动态拼接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 }

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 }