功能说明: 将 DataGrid嵌套在本控件内,使用Label自定义表头,避免写HeaderTemplete模板,如果需要上下左右滚动 需要在控件外围添加 ScrollViewer 并且设置 ScrollVisibility 为Auto
实现方式:控件继承了Grid,所以datagrid上面的子节点Label可以设置行列合并 以实现我们需要的 复合表头需求, 复合表头会读取 datagrid中的列,Label的宽度会跟随 datagrid表头宽度的变化,我们可以自己设置显示或者隐藏datagrid的表头,自己设置label的样式
截图:
使用Demo:
<UserControl x:Class="SelfControlWPF.UserControl2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:SelfControlWPF.Controls"
mc:Ignorable="d"
d:DesignHeight="450" d:DesignWidth="800">
<UserControl.Resources>
<Style x:Key="h1" TargetType="DataGridColumnHeader">
<Setter Property="Foreground" Value="Blue"></Setter>
</Style>
<Style x:Key="comlab" TargetType="Label">
<Setter Property="BorderBrush" Value="Black"></Setter>
<Setter Property="HorizontalContentAlignment" Value="Center"></Setter>
<Setter Property="VerticalContentAlignment" Value="Center"></Setter>
</Style>
</UserControl.Resources>
<Grid>
<StackPanel Orientation="Vertical">
<TextBlock Text="复合表头:" FontSize="20" Margin="5"></TextBlock>
<ScrollViewer HorizontalScrollBarVisibility="Auto">
<local:ComplexDataGridHeader x:Name="p" AddRow="2" >
<Label Grid.Row="0" Grid.Column="0" Grid.RowSpan="2" Grid.ColumnSpan="2" Style="{StaticResource comlab}" BorderThickness="1" Content="一"></Label>
<Label Grid.Row="0" Grid.Column="2" Grid.ColumnSpan="3" Style="{StaticResource comlab}" BorderThickness="1" Content="二"></Label>
<Label Grid.Row="0" Grid.Column="5" Grid.ColumnSpan="4" Style="{StaticResource comlab}" BorderThickness="1" Content="三"></Label>
<Label Grid.Row="1" Grid.Column="2" Grid.ColumnSpan="7" Style="{StaticResource comlab}" BorderThickness="1" Content="四"></Label>
<DataGrid HeadersVisibility="All" x:Name="a" >
<DataGrid.Columns>
<DataGridTextColumn HeaderStyle="{StaticResource h1}" Header="A" Width="200" Binding="{Binding A}"></DataGridTextColumn>
<DataGridTextColumn Header="B" Binding="{Binding B}" ></DataGridTextColumn>
<DataGridTextColumn Header="C" Binding="{Binding B}" ></DataGridTextColumn>
<DataGridTextColumn Header="D" Binding="{Binding B}" ></DataGridTextColumn>
<DataGridTextColumn Header="E" Binding="{Binding B}" ></DataGridTextColumn>
<DataGridTextColumn Width="1000" Header="F" Binding="{Binding B}" ></DataGridTextColumn>
<DataGridTextColumn Header="G" Binding="{Binding B}" ></DataGridTextColumn>
<DataGridTextColumn Header="G" Binding="{Binding B}" ></DataGridTextColumn>
<DataGridTextColumn Header="G" Binding="{Binding B}" ></DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
</local:ComplexDataGridHeader>
</ScrollViewer>
</StackPanel>
</Grid>
</UserControl>
控件代码:
using System; using System.Collections.Generic; using System.ComponentModel; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Media; namespace SelfControlWPF.Controls { /// <summary> /// 将DataGrid放入控件内,用Label基于Grid的方式画复杂表头,表头合并 /// 需要滚动的的情况下可在控件外包一层 ScrollView 设置Scrollvibility为Auto 固定高度宽度 /// </summary> public class ComplexDataGridHeader : Grid { #region 依赖属性 /// <summary> /// 表头需要添加的行数 /// </summary> public static readonly DependencyProperty AddRowProperty = DependencyProperty.Register("AddRow", typeof(int), typeof(ComplexDataGridHeader)); public int AddRow { get { return (int)GetValue(AddRowProperty); } set { SetValue(AddRowProperty, value); } } private double RowHeaderWidth { get; set; } #endregion #region 所有字段 /// <summary> /// 需要显示的DataGrid /// </summary> private DataGrid dataGrid; /// <summary> /// 所有列宽度字典 /// </summary> private readonly Dictionary<int, double> colArr = new Dictionary<int, double>(); /// <summary> /// 最右边的一个Label /// </summary> private Label RightLabel; /// <summary> /// 最右边Label右边的BorderThinkness /// </summary> private double RightTh; /// <summary> /// 监测datagrid Columun的宽度变化 /// </summary> readonly DependencyPropertyDescriptor descriptor = DependencyPropertyDescriptor.FromProperty(DataGridColumn.WidthProperty, typeof(DataGridColumn)); /// <summary> /// DataGrid所有Column字典 /// </summary> readonly Dictionary<int, ColumnDefinition> columnDefinitions = new Dictionary<int, ColumnDefinition>(); //double SumWidth = 0; /// <summary> /// 记录DataGrid的RowHeaderActualWidth /// </summary> #endregion #region 主要方法 public ComplexDataGridHeader() { RowDefinition rowDefinitionh = new RowDefinition { Height = GridLength.Auto }; RowDefinitions.Add(rowDefinitionh); } /// <summary> /// 设置DataGrid初始样式 /// </summary> /// <param name="obj"></param> private void SetHeaderGrid(object obj) { if (obj is DataGrid) { dataGrid = obj as DataGrid; dataGrid.SetValue(RowProperty, 1); dataGrid.Loaded += DataGridLoaded; dataGrid.HorizontalScrollBarVisibility = ScrollBarVisibility.Hidden; dataGrid.CanUserReorderColumns = false; } } /// <summary> /// 开始渲染表头 /// </summary> public void Render() { if (Utils.ListIsNullOrEmpty(dataGrid.Columns)|| Utils.ListIsNullOrEmpty(Children)) return; columnDefinitions.Clear(); colArr.Clear(); //SumWidth = 0; ColumnDefinitions.Clear(); RowDefinitions.Clear(); RowDefinition rowDefinitionh = new RowDefinition { Height = GridLength.Auto }; RowDefinitions.Add(rowDefinitionh); foreach (DataGridColumn col in dataGrid.Columns) { descriptor.RemoveValueChanged(col, ColumnWidth_Changed); descriptor.AddValueChanged(col, ColumnWidth_Changed); ColumnDefinition columnDefinition = new ColumnDefinition { Width = new GridLength(col.ActualWidth >= col.Width.Value ? col.ActualWidth : col.Width.Value) }; ColumnDefinitions.Add(columnDefinition); colArr.Add(col.DisplayIndex, columnDefinition.Width.Value); columnDefinitions.Add(col.DisplayIndex, columnDefinition); //SumWidth += columnDefinition.Width.Value; } if(dataGrid.HeadersVisibility== DataGridHeadersVisibility.All|| dataGrid.HeadersVisibility == DataGridHeadersVisibility.Row) { RowHeaderWidth+= dataGrid.RowHeaderActualWidth; } //SumWidth += RowHeaderWidth; columnDefinitions[0].Width = new GridLength(columnDefinitions[0].Width.Value + RowHeaderWidth); colArr[0] = columnDefinitions[0].Width.Value; for (int i = 0; i < AddRow; i++) { RowDefinition rowDefinition = new RowDefinition { Height = GridLength.Auto }; RowDefinitions.Add(rowDefinition); } SetHeaderWidth(); dataGrid.SetValue(ColumnSpanProperty, dataGrid.Columns.Count); dataGrid.SetValue(RowProperty, RowDefinitions.Count - 1); } /// <summary> /// 设置所有表头宽度和边框 /// </summary> private void SetHeaderWidth() { foreach (var item in Children) { if (item is Label) { Label textBlock = item as Label; Thickness thickness = textBlock.BorderThickness; thickness.Left = thickness.Left == 0 ? 1 : thickness.Left; thickness.Top = thickness.Top == 0 ? 1 : thickness.Top; thickness.Right = thickness.Right == 0 ? 1 : thickness.Right; thickness.Bottom = thickness.Bottom == 0 ? 1 : thickness.Bottom; double Th = thickness.Right; int col = Convert.ToInt32(textBlock.GetValue(ColumnProperty)); int row = Convert.ToInt32(textBlock.GetValue(RowProperty)); int colspan = Convert.ToInt32(textBlock.GetValue(ColumnSpanProperty)); textBlock.Width = GetLabelWidth(Convert.ToInt32(textBlock.GetValue(ColumnProperty)), Convert.ToInt32(textBlock.GetValue(ColumnSpanProperty))); textBlock.BorderThickness = new Thickness(thickness.Left, thickness.Top * (row == 0 ? 1 : 0), thickness.Right * (col + colspan - 1 == dataGrid.Columns.Count - 1 ? 1 : 0), thickness.Bottom * (row == RowDefinitions.Count - 2 ? 0 : 1)); if (RightLabel is null || Convert.ToInt32(textBlock.GetValue(ColumnProperty)) > Convert.ToInt32(RightLabel.GetValue(ColumnProperty))) { RightLabel = textBlock; if (Th > 0) RightTh = thickness.Right; } } } RightLabel.BorderThickness = new Thickness(RightLabel.BorderThickness.Left, RightLabel.BorderThickness.Top, RightTh, RightLabel.BorderThickness.Bottom); } /// <summary> /// 计算某个表头宽度 /// </summary> /// <param name="col"></param> /// <param name="span"></param> /// <returns></returns> private double GetLabelWidth(int col, int span) { double result = 0; for (int i = 0; i < span; i++) { if(colArr.ContainsKey(col + i)) result += colArr[col + i]; } return result; } #endregion #region 事件 /// <summary> /// 监测控件子控件变化 /// </summary> /// <param name="visualAdded"></param> /// <param name="visualRemoved"></param> protected override void OnVisualChildrenChanged(DependencyObject visualAdded, DependencyObject visualRemoved) { if (visualAdded is DataGrid) { SetHeaderGrid(visualAdded as DataGrid); } base.OnVisualChildrenChanged(visualAdded, visualRemoved); } /// <summary> /// 表头宽度变化事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void ColumnWidth_Changed(object sender, EventArgs e) { DataGridColumn dataGridColumn = sender as DataGridColumn; columnDefinitions[dataGridColumn.DisplayIndex].Width = new GridLength(dataGridColumn.ActualWidth); if (!colArr.ContainsKey(dataGridColumn.DisplayIndex)) return; colArr[dataGridColumn.DisplayIndex] = dataGridColumn.ActualWidth; if (dataGridColumn.DisplayIndex == 0) { columnDefinitions[0].Width = new GridLength(dataGridColumn.ActualWidth + RowHeaderWidth); colArr[0] = columnDefinitions[0].Width.Value; } SetHeaderWidth(); } /// <summary> /// DataGrid加载后事件 /// </summary> /// <param name="sender"></param> /// <param name="e"></param> private void DataGridLoaded(object sender, RoutedEventArgs e) { Render(); } #endregion } }
帮助类:
using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows; using System.Windows.Markup; using System.Xml; namespace SelfControlWPF { public class Utils { /// <summary> /// 深拷贝控件 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="source"></param> /// <param name="destination"></param> public static void Copy<T>(T source, ref T destination) where T : class { string rectXaml = XamlWriter.Save(source); StringReader stringReader = new StringReader(rectXaml); XmlReader xmlReader = XmlReader.Create(stringReader); UIElement clonedChild = (UIElement)XamlReader.Load(xmlReader); destination = clonedChild as T; } /// <summary> /// 判断列表是否为空或者0条 /// </summary> /// <param name="list"></param> /// <returns></returns> public static bool ListIsNullOrEmpty(IList list) { return list == null || list.Count == 0; } } }