效果:

  

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

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

两个类一个接口

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

 using System;
using System.Collections.Generic;
using System.Linq;
using System.Collections.ObjectModel; namespace SLDGHeader
{
/// <summary>
/// 树结构
/// </summary>
/// <typeparam name="T">节点中的数据</typeparam>
public class NTree<T>
{
/// <summary>
/// 节点数据
/// </summary>
private readonly T data;
/// <summary>
/// 节点数据
/// </summary>
public T Data
{
get { return data; }
}
/// <summary>
/// 是否根节点
/// </summary>
public bool IsRoot { get { return Parent == null; } }
/// <summary>
/// 当前节点深度
/// 根节点为1
/// </summary>
public int Depth { get; private set; }
/// <summary>
/// 父节点
/// </summary>
public NTree<T> Parent
{
get;
private set;
}
/// <summary>
/// 子节点
/// </summary>
public ReadOnlyCollection<NTree<T>> Children
{
get { return children.AsReadOnly(); }
}
private List<NTree<T>> children;
/// <summary>
/// 实例化一个节点
/// </summary>
/// <param name="data">节点数据</param>
public NTree(T data)
{
this.Depth = ;
this.data = data;
children = new List<NTree<T>>();
}
/// <summary>
/// 在当前节点添加子节点
/// </summary>
/// <param name="data">节点数据</param>
/// <returns>当前节点</returns>
public NTree<T> AddChild(T data)
{
var node = new NTree<T>(data) { Parent = this, Depth = this.Depth + };
children.Add(node);
return this;
}
/// <summary>
/// 在当前节点子节点中插入子节点
/// </summary>
/// <param name="index">插入位置</param>
/// <param name="data">节点数据</param>
/// <returns>当前节点</returns>
public NTree<T> InsertChild(int index, T data)
{
var node = new NTree<T>(data) { Parent = this, Depth = this.Depth + };
children.Insert(index, node);
return this;
}
/// <summary>
/// 在当前节点添加子节点
/// </summary>
/// <param name="data">节点数据</param>
/// <returns>当前节点</returns>
public NTree<T> AddChilren(params T[] datas)
{
foreach (var data in datas)
{
AddChild(data);
}
return this;
}
/// <summary>
/// 移除当前节点下指定的子节点
/// </summary>
/// <param name="node">要移除的子节点</param>
/// <returns>当前节点</returns>
public NTree<T> RemoveChild(NTree<T> node)
{
children.Remove(node);
return this;
}
/// <summary>
/// 在当前节点添加兄弟节点
/// </summary>
/// <param name="data">节点数据</param>
/// <returns>当前节点</returns>
/// <exception cref="NullParentNodeException:当前节点没有父节点">当前节点没有父节点</exception>
public NTree<T> AddBrother(T data)
{
if (this.Parent == null)
{
throw new NullParentNodeException("有父节点的节点才能添加兄弟节点。");
} this.Parent.AddChild(data);
return this;
}
/// <summary>
/// 获取指定索引处的子节点
/// </summary>
/// <param name="i">子节点索引</param>
/// <returns>子节点</returns>
/// <exception cref="ArgumentOutOfRangeException:子节点索引超出范围">子节点索引超出范围</exception>
public NTree<T> GetChild(int i)
{
if (i >= children.Count || i < )
{
throw new ArgumentOutOfRangeException("子节点索引超出范围");
}
return children[i];
}
/// <summary>
/// 获取指定的子节点
/// </summary>
/// <param name="data">节点数据</param>
/// <returns>查找到的第一个子节点</returns>
public NTree<T> GetChild(T data)
{
return children.Where(i => i.data.Equals(data)).FirstOrDefault();
}
/// <summary>
/// 获取指定子节点的索引
/// </summary>
/// <param name="data">节点数据</param>
/// <returns>查找到的第一个子节点的索引,没有找到返回-1</returns>
public int GetChildIndex(NTree<T> data)
{
var index = -;
for (int i = ; i < children.Count; i++)
{
if (children[i].Equals(data))
{
index = i;
break;
}
}
return index;
}
/// <summary>
/// 遍历树节点
/// </summary>
/// <param name="node">起始节点</param>
/// <param name="action">遍历到每个节点的操作</param>
/// <exception cref="ArgumentNullException:参数action不可为空">参数action不可为空</exception>
public static void Traverse(NTree<T> node, Action<T> action)
{
if (action == null)
{
throw new ArgumentNullException("参数action不可为空");
}
action(node.data);
foreach (var kid in node.children)
{
Traverse(kid, action);
}
}
/// <summary>
/// 从当前节点开始遍历树节点
/// </summary>
/// <param name="action">遍历到每个节点的操作</param>
/// <exception cref="ArgumentNullException:参数action不可为空">参数action不可为空</exception>
public void Traverse(Action<T> action)
{
Traverse(this, action);
}
/// <summary>
/// 遍历树节点
/// </summary>
/// <param name="node">起始节点</param>
/// <param name="action">遍历到每个节点的操作</param>
/// <exception cref="ArgumentNullException:参数action不可为空">参数action不可为空</exception>
public static void Traverse(NTree<T> node, Action<NTree<T>> action)
{
if (action == null)
{
throw new ArgumentNullException("参数action不可为空");
}
action(node);
foreach (var kid in node.children)
{
Traverse(kid, action);
}
}
/// <summary>
/// 从当前节点开始遍历树节点
/// </summary>
/// <param name="action">遍历到每个节点的操作</param>
/// <exception cref="ArgumentNullException:参数action不可为空">参数action不可为空</exception>
public void Traverse(Action<NTree<T>> action)
{
if (action == null)
{
throw new ArgumentNullException("参数action不可为空");
}
action(this);
foreach (var kid in this.children)
{
Traverse(kid, action);
}
}
/// <summary>
/// 获取当前节点开始的所有节点中数据
/// </summary>
/// <returns>节点数据列表</returns>
public IEnumerable<T> GetDatas()
{
return new[] { data }.Union(children.SelectMany(x => x.GetDatas()));
}
/// <summary>
/// 当前节点下叶节点的数量
/// </summary>
/// <returns></returns>
public int GetCount()
{
var count = ;
Traverse((NTree<T> n) =>
{
if (n.Children.Count == )
{
count++;
}
});
return count;
}
/// <summary>
/// 获取当前节点所在树的深度
/// </summary>
/// <returns>当前节点所在树的深度</returns>
public int GetTreeDepth()
{
int Depth = ;
var parent = this;
while (parent.Parent != null)
{
parent = parent.Parent;
}
Traverse((NTree<T> n) =>
{
if (Depth < n.Depth)
{
Depth = n.Depth;
}
}); return Depth;
}
}
/// <summary>
/// 父节点为空引用异常
/// </summary>
public class NullParentNodeException : Exception
{
public NullParentNodeException()
: base("父节点为空引用")
{ }
public NullParentNodeException(string message)
: base(message)
{ }
public NullParentNodeException(string message, Exception inner)
: base(message)
{ }
//public NullParentNodeException(SerializationInfo info, StreamingContext context)
//{ //}
}
}

MultiHeadersColumn:多重表头和绑定的列

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

 using System;
using System.Windows;
using System.Windows.Controls;
using System.Collections.Generic;
using System.Windows.Markup;
using System.Text; namespace SLDGHeader
{
public class MultiHeadersColumn
{
#region 字段
/// <summary>
/// 单元格边框颜色
/// </summary>
string splitLineColor = "#ccc";
/// <summary>
/// 数据行宽度
/// </summary>
string dataWidth = "";
/// <summary>
/// 表头行高度
/// </summary>
string dataHeight = "auto";
/// <summary>
/// 分隔线线宽度
/// </summary>
string lineWidth = "";
/// <summary>
/// 分隔符线高度
/// </summary>
string lineHeight = "";
#endregion
#region 属性
/// <summary>
/// 单元格边框颜色
/// </summary>
public string SplitLineColor
{
get { return splitLineColor; }
set { splitLineColor = value; }
}
/// <summary>
/// 数据行宽度
/// </summary>
public string DataWidth
{
get { return dataWidth; }
set { dataWidth = value; }
}
/// <summary>
/// 表头行高度
/// </summary>
public string DataHeight
{
get { return dataHeight; }
set { dataHeight = value; }
}
/// <summary>
/// 分隔线线宽度
/// </summary>
public string LineWidth
{
get { return lineWidth; }
set { lineWidth = value; }
}
/// <summary>
/// 分隔符线高度
/// </summary>
public string LineHeight
{
get { return lineHeight; }
set { lineHeight = value; }
}
#endregion
public DataGridTemplateColumn GetMultiHeadersColumn(NTree<IDataGridHeader> node)
{
DataGridTemplateColumn col = GetTemplateColumn(node);
col.HeaderStyle = GetStyle("NameHeaderStyle", node);
return col;
}
DataGridTemplateColumn GetTemplateColumn(NTree<IDataGridHeader> node)
{
List<NTree<IDataGridHeader>> nodes = new List<NTree<IDataGridHeader>>();
node.Traverse((NTree<IDataGridHeader> n) =>
{
if (n.Children.Count == )
{
nodes.Add(n);
}
});
string strtemplate = GetDataTemplate(nodes);
DataTemplate template = (DataTemplate)XamlReader.Load(strtemplate); DataGridTemplateColumn colunm = new DataGridTemplateColumn();
colunm.CellTemplate = template; return colunm;
}
/// <summary>
/// 获取绑定列模板XML字符串
/// </summary>
/// <param name="nodes">列对应节点</param>
/// <returns>返回列模板XML字符串</returns>
string GetDataTemplate(List<NTree<IDataGridHeader>> nodes)
{
if (nodes == null)
{
throw new ArgumentNullException("参数nodes不能为空");
}
StringBuilder sb = new StringBuilder(); sb.Append(@"<DataTemplate ");
sb.AppendLine("xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' ");
sb.AppendLine("xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' ");
sb.AppendLine("xmlns:sdk='http://schemas.microsoft.com/winfx/2006/xaml/presentation/sdk'");
sb.AppendLine(" >"); sb.AppendLine("<StackPanel Orientation='Horizontal'> ");
for (int i = ; i < nodes.Count * - ; i++)
{
if (i % == )
{
sb.Append("<TextBlock VerticalAlignment='Center' ");
sb.Append(" Width='").Append(dataWidth).Append("' ");
sb.Append(" HorizontalAlignment='Center' TextWrapping='Wrap' Text='{Binding ").Append(nodes[(i + ) / ].Data.ColName).AppendLine("}' /> ");
}
else
{
sb.Append(" <Rectangle Fill='").Append(splitLineColor).Append("' VerticalAlignment='Stretch' ");
sb.Append(" Width='").Append(lineWidth).Append("' ");
sb.AppendLine(" /> ");
}
} sb.AppendLine("</StackPanel> ");
sb.AppendLine("</DataTemplate> ");
return sb.ToString();
} Style GetStyle(string headerstyle, NTree<IDataGridHeader> node)
{
var depth = node.GetTreeDepth();
string stylestr = GetStyleStr("NameHeaderStyle", depth, node);
Style ste = (Style)XamlReader.Load(stylestr); return ste;
}
/// <summary>
/// 获取表头样式XML字符串
/// </summary>
/// <param name="headerstyle">样式名称</param>
/// <param name="depth">树的深度</param>
/// <param name="node">树的根节点</param>
/// <returns>返回表头样式XML字符串</returns>
string GetStyleStr(string headerstyle, int depth, NTree<IDataGridHeader> node)
{ StringBuilder sb = new StringBuilder();
//计算数据列数量
int colCount = node.GetCount(); AppendHeader(headerstyle, sb);
//构建表头行列
sb.AppendLine("<Grid HorizontalAlignment='{TemplateBinding HorizontalContentAlignment}' VerticalAlignment='{TemplateBinding VerticalContentAlignment}'>");
//多少行
sb.AppendLine("<Grid.RowDefinitions>");
//int rowCount = 3;
for (int i = ; i < depth * ; i++)
{
if (i % == )
{
sb.AppendLine("<RowDefinition ");
sb.Append(" Height='").Append(dataHeight).Append("' ");
sb.AppendLine(" /> ");
}
else
{
sb.AppendLine("<RowDefinition ");
sb.Append(" Height='").Append(lineHeight).Append("' ");
sb.AppendLine("/>");
}
}
sb.AppendLine("</Grid.RowDefinitions>"); //多少列
sb.AppendLine("<Grid.ColumnDefinitions>"); for (int i = ; i < colCount * ; i++)
{
if (i % == )
{
sb.AppendLine("<ColumnDefinition ");
sb.Append("Width='").Append(dataWidth).Append("' ");
sb.AppendLine(" />");
}
else
{
sb.AppendLine("<ColumnDefinition ");
sb.Append("Width='").Append(lineWidth).Append("' ");
sb.AppendLine(" />");
} }
sb.AppendLine("</Grid.ColumnDefinitions>"); //开始构单元格
AppendCell(node, sb, depth, colCount); AppendEnd(sb); return sb.ToString();
}
/// <summary>
/// 绘制头部和表头背景
/// </summary>
/// <param name="headerstyle">样式名称</param>
/// <param name="sb"></param>
private void AppendHeader(string headerstyle, StringBuilder sb)
{
sb.Append(@"<Style ");
sb.AppendLine("xmlns='http://schemas.microsoft.com/winfx/2006/xaml/presentation' ");
sb.AppendLine("xmlns:x='http://schemas.microsoft.com/winfx/2006/xaml' ");
sb.AppendLine("xmlns:dataprimitives='clr-namespace:System.Windows.Controls.Primitives;assembly=System.Windows.Controls.Data' "); //列样式名称
sb.Append(@" x:Key='").Append(headerstyle).Append("' "); sb.AppendLine(@"TargetType='dataprimitives:DataGridColumnHeader' >");
sb.AppendLine("<Setter Property='Template'>");
sb.AppendLine("<Setter.Value>");
sb.AppendLine("<ControlTemplate>");
sb.AppendLine("<Grid x:Name='Root'>");
sb.AppendLine("<Grid.ColumnDefinitions>");
sb.AppendLine("<ColumnDefinition/>");
sb.AppendLine("<ColumnDefinition Width='Auto'/>");
sb.AppendLine("</Grid.ColumnDefinitions>");
sb.AppendLine("<Rectangle x:Name='BackgroundRectangle' Fill='#FF1F3B53' Stretch='Fill' Grid.ColumnSpan='2'/>");
sb.AppendLine("<Rectangle x:Name='BackgroundGradient' Stretch='Fill' Grid.ColumnSpan='2'>");
sb.AppendLine("<Rectangle.Fill>");
//表头背景色
sb.AppendLine("<LinearGradientBrush EndPoint='.7,1' StartPoint='.7,0'>");
sb.AppendLine(@"<GradientStop Color='#FCFFFFFF' Offset='0.015'/>
<GradientStop Color='#F7FFFFFF' Offset='0.375'/>
<GradientStop Color='#E5FFFFFF' Offset='0.6'/>
<GradientStop Color='#D1FFFFFF' Offset='1'/>");
sb.AppendLine("</LinearGradientBrush>");
sb.AppendLine("</Rectangle.Fill>");
sb.AppendLine("</Rectangle>");
}
/// <summary>
/// 添加结束XML部分
/// </summary>
/// <param name="sb"></param>
private void AppendEnd(StringBuilder sb)
{
sb.AppendLine("</Grid>");
//绘制最后一列分割线
sb.AppendLine(@"<Rectangle x:Name='VerticalSeparator' Fill='")
.Append(splitLineColor)
.Append(@"' VerticalAlignment='Stretch'")
.Append(" Width='").Append(lineWidth).Append("' ")
.AppendLine(" Visibility='Visible' Grid.Row='1' Grid.Column='1' />");
sb.AppendLine("</Grid>");
sb.AppendLine("</ControlTemplate>");
sb.AppendLine("</Setter.Value>");
sb.AppendLine("</Setter>");
sb.AppendLine("</Style>");
}
/// <summary>
/// 构建行
/// 偶数行、偶数列为数据,奇数行、奇数列为分割线,以此计算单元格占位、合并行列
/// </summary>
/// <param name="node">构建行的树结构</param>
/// <param name="sb">字符串</param>
/// <param name="depth">树的深度</param>
private StringBuilder AppendCell(NTree<IDataGridHeader> node, StringBuilder sb, int depth, int totalCol)
{
//当前节点左兄弟节点的页节点数量,用于计算当前单元格列位置
var precolCount = ;
if (!node.IsRoot)
{
precolCount = GetLeftCount(node);
} //当前节点的页节点数量
var colCount = node.GetCount(); //1、数据单元格
sb.Append(@"<ContentPresenter ");
sb.Append(@"Content='").Append(node.Data.DisplayColName).Append("' ");
sb.Append(@"VerticalAlignment='Center' HorizontalAlignment='Center' ");
sb.Append(" Grid.Row='").Append((node.Depth - ) * ).Append("' ");
//考虑表头行可能不一致(层级有深有浅),层级较少的需要合并列显示,所有合并在最后一行表头
var rowSpan = ;
if (node.Children.Count == && node.Depth != depth)
{
rowSpan = depth * - - (node.Depth - ) * ;
sb.Append(" Grid.RowSpan='").Append(rowSpan).Append("' ");
} sb.Append(" Grid.Column='").Append(precolCount * ).Append("' ");
sb.Append("Grid.ColumnSpan='").Append(colCount * - ).AppendLine("' />"); //2、行分隔线单元格
sb.Append(@"<Rectangle Fill='").Append(splitLineColor).Append("' VerticalAlignment='Stretch' ")
.Append("Height='").Append(lineHeight).Append("' "); //表头行合并后要调整分割线,否则会在合并单元格中间显示分割线
if (node.Children.Count == && node.Depth != depth)
{
//如果有合并单元,则分割线要跳过合并单元格,合并数量为(rowSpan - 1)
sb.Append("Grid.Row='").Append(node.Depth * - + rowSpan - ).Append("' ");
}
else
{
sb.Append("Grid.Row='").Append(node.Depth * - ).Append("' ");
} sb.Append(" Grid.Column='").Append(precolCount * ).Append("' ");
sb.Append("Grid.ColumnSpan='").Append(colCount * ).AppendLine("' />"); //4、列分隔线单元格
//最后一列分割线不绘制,否则在调整了后面列的列宽会出现类似空列的单元格
if ((precolCount + colCount) != totalCol)
{
sb.Append(@"<Rectangle Fill='").Append(splitLineColor).Append("' VerticalAlignment='Stretch' ")
.Append(" Width='").Append(lineWidth).Append("' "); sb.Append("Grid.Row='").Append((node.Depth - ) * ).Append("' ");
sb.Append("Grid.RowSpan='").Append(rowSpan).Append("' ");
sb.Append("Grid.Column='").Append((precolCount + colCount) * - ).AppendLine("' />");
} //5、递归生成子节点单元格
if (node.Children.Count != )
{
foreach (var item1 in node.Children)
{
AppendCell(item1, sb, depth, totalCol);
}
} return sb;
}
/// <summary>
/// 获取当前节点左边的叶节点数量
/// </summary>
/// <param name="node"></param>
/// <returns></returns>
private int GetLeftCount(NTree<IDataGridHeader> node)
{
var precolCount = ;
var index = node.Parent.GetChildIndex(node);
for (int i = ; i < index; i++)
{
precolCount += node.Parent.GetChild(i).GetCount();
}
if (!node.Parent.IsRoot)
{
precolCount += GetLeftCount(node.Parent);
}
return precolCount;
}
}
public interface IDataGridHeader
{
/// <summary>
/// 绑定列名
/// </summary>
string ColName { get; set; }
/// <summary>
/// 显示名称
/// </summary>
string DisplayColName { get; set; }
}
}

Silverlight多重表头实现的更多相关文章

  1. Gridview 多重表头 (二)

    多重表头之排序 这是个有点忧桑的故事...Cynthia告诉我,研究一个问题,我们不可能有超过一天的时间... 结果好好几天过去鸟~~还没有完成... 由于不再使用Gridview自带的表头行,于是无 ...

  2. Gridview 多重表头 (一)

    今天看到一个人每个月更新博客,结果七年后改行去卖土特产...感慨良多... 虽然我也想去开餐厅~~ 今天需求里有一个多重表头,感觉比较奇特,特意留下记录,以防我的大脑被艾滋海默攻占~~没有女主的命,不 ...

  3. GridView合并表头多重表头

    后台代码: using System; using System.Data; using System.Configuration; using System.Web; using System.We ...

  4. GridView合并表头、多重表头(转)

    protected void GridView1_RowCreated(object sender, GridViewRowEventArgs e) { switch (e.Row.RowType) ...

  5. devexpress实现多行表头(复合表头),附源代码

    在许多项目中都会涉及到复合表头(多行表头),这里给大家分享一个devexpress实现多重表头的案例. 1.第一步将表格类型由默认的gridview变为bandedgridview,如图所示: 2.第 ...

  6. Gridview转发

    首页 开源项目 问答 动弹 博客 翻译 资讯 专题 城市圈 [ 登录 | 注册 ] 博客专区 > Reya滴水心的博客详情 Asp.net中GridView使用详解(很全,很经典) Reya滴水 ...

  7. Asp.net中GridView使用详解(引)

    GridView无代码分页排序GridView选中,编辑,取消,删除GridView正反双向排序GridView和下拉菜单DropDownList结合GridView和CheckBox结合鼠标移到Gr ...

  8. 【转】 GridView 72般绝技

    说明:准备出一个系列,所谓精髓讲C#语言要点.这个系列没有先后顺序,不过尽量做到精.可能会不断增删整理,本系列最原始出处是csdn博客,谢谢关注. C#精髓 第四讲 GridView 72般绝技 作者 ...

  9. GridView的详细用法

    l GridView无代码分页排序 l GridView选中,编辑,取消,删除 l GridView正反双向排序 l GridView和下拉菜单DropDownList结合 l GridView和Ch ...

随机推荐

  1. struts 1.x 方法探析

    public ActionForward index(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpS ...

  2. double team

    队长博客链接 https://www.cnblogs.com/98-10-22-25/p/9806296.html 团队队名 泡面 团队成员 211606361 何承华(队长) 211606356 陈 ...

  3. LR 11录制IE起不来

    注:LR 11一般使用的是IE8或IE9 1.在录制脚本时Start Recoding中,默认如下,这样有可能IE打不开,需要更改路径,到对应的IE路径再尝试. 2.降低IE版本到IE8或者9 3.I ...

  4. [精华][推荐]CAS SSO实现单点登录框架学习源码

    1.通过下载稳定版本的方式下载cas的相关源码包,如下: 直接选择4.2.1的稳定代码即可 2.我们项目中的版本版本使用maven apereo远程库去下载 通过远程maven库下载cas-serve ...

  5. python3 安装 google-visualization-python(windows10)

    google-visualization-python 的 github 官网:https://github.com/google/google-visualization-python 安装: 打开 ...

  6. Lucene学习笔记:基础

    Lucence是Apache的一个全文检索引擎工具包.可以将采集的数据存储到索引库中,然后在根据查询条件从索引库中取出结果.索引库可以存在内存中或者存在硬盘上. 本文主要是参考了这篇博客进行学习的,原 ...

  7. ----改写superheros的json以及上传到github----

    以下为js代码: var header = document.querySelector('header'); var section = document.querySelector('sectio ...

  8. ----constructor 与 object----

    CONSTRUCTOR constructor是一种特殊的object,同样是用来创建和声明一个类 语法规则: constructor([arguments]) { ... } 注意: 1.在类中,只 ...

  9. ASCII, Unicode, UTF-8, 8进制, 16进制等各种编码学习理解笔记

    字符编码的发展历史 Unicode和UTF-8有何区别? 在这个问题下的于洋的最高票回答中,比较完整地介绍了字符编码的发展历史,为了便于记忆,再次简要概括一番. 一个字节:最初一个字节的标准是混乱的, ...

  10. Tomcat 500error: Could not initialize class

    Web 项目报错Could not initialize class ,出现500,说明服务器是起来了,可能是这个类的驱动有问题,缺少初始化需要的文件 查到有相关情况: 考虑是jar 包的问题,检查了 ...