如何在WPF中分组DataGrid列标题

dwbf0jvd  于 2022-11-18  发布在  其他
关注(0)|答案(2)|浏览(227)

是否可以在WPF数据网格中执行此操作:

|-------------- A header------------|---------------B Header---------------|

|-----A1Header----|----A2Header-----|-----B1Header-----|-----B2Header------|
|-----A1Data------|----A2 Data------|-----B1 Data------|-----B2 Data-------|
|-----A1Data------|----A2 Data------|-----B1 Data------|-----B2 Data-------|
  • 谢谢-谢谢
o2gm4chl

o2gm4chl1#

This Thread可能会帮助您实现您正在尝试做的事情。
它不直接从DataGrid获取功能,而是将DataGrid Package 在常规Grid中,并使用绑定列(具有多列跨度)添加超级标题。
希望有一个很好的简单方法可以直接从DataGrid中完成此操作,但如果没有,这可能是您可以接受的变通方法。

k7fdbhmy

k7fdbhmy2#

对于分组列的问题,我可以提供三种解决方案。

溶液1

通过源集合的ICollectionView使用常规分组。此组是垂直的,意味着它们共享相同的列。

溶液2

建立巢状数据来源。其想法是每一个数据行系结至个别的数据集,该数据集是由DataGrid显示,而DataGrid会加入至数据行的DataGridTemplateColumn。它是每个数据行群组的DataGrid。此解决方案的缺点是数据结构的条件约束非常严格。不支援DataTable,也不会自动产生数据行。如果允许对列进行排序或重新排序,则工作量会增加。但是对于分组表的简单显示,此解决方案已经足够好了。

用法示例

主窗口.xaml

<Window>
  <Window.DataContext>
    <ViewModel />
  </Window.DataContext>

  <!-- Toplevel DataGrid that displays the column group headers -->
  <DataGrid ItemsSource="{Binding Rows}"
            AutoGenerateColumns="False"
            CanUserAddRows="False">

    <!-- The grouped column definitions -->
    <DataGrid.Columns>
      <DataGridTemplateColumn>
        <DataGridTemplateColumn.Header>
          <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=Items[0].Columns[0].GroupHeader}"></TextBlock>
        </DataGridTemplateColumn.Header>

        <DataGridTemplateColumn.CellTemplate>
          <DataTemplate DataType="{x:Type local:DataGridRowItem}">
            <DataGrid ItemsSource="{Binding Columns[0].TableData}"
                      local:DataGridHelper.IsSynchronizeSelectedRowEnabled="True"
                      local:DataGridHelper.SynchronizeGroupKey="A"
                      RowHeaderWidth="0"
                      BorderThickness="0" />
          </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
        <DataGridTemplateColumn.CellEditingTemplate>
          <DataTemplate DataType="{x:Type local:DataGridRowItem}">
            <TextBox />
          </DataTemplate>
        </DataGridTemplateColumn.CellEditingTemplate>
      </DataGridTemplateColumn>
      <DataGridTemplateColumn>
        <DataGridTemplateColumn.Header>
          <TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=DataGrid}, Path=Items[0].Columns[1].GroupHeader}"></TextBlock>
        </DataGridTemplateColumn.Header>

        <DataGridTemplateColumn.CellTemplate>
          <DataTemplate DataType="{x:Type local:DataGridRowItem}">
            <DataGrid ItemsSource="{Binding Columns[1].TableData}"
                      local:DataGridHelper.IsSynchronizeSelectedRowEnabled="True"
                      local:DataGridHelper.SynchronizeGroupKey="A"
                      RowHeaderWidth="0"
                      BorderThickness="0" />
          </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
        <DataGridTemplateColumn.CellEditingTemplate>
          <DataTemplate DataType="{x:Type local:DataGridRowItem}">
            <TextBox />
          </DataTemplate>
        </DataGridTemplateColumn.CellEditingTemplate>
      </DataGridTemplateColumn>
    </DataGrid.Columns>
  </DataGrid>
</Window>

建议通过DataGrid禁用表格修改。要美化外观,比如将组名居中,或者覆盖DataGridRow模板为未聚焦的网格添加行高亮,这非常简单。

实现示例

嵌套表的数据结构:

数据网格行项.cs

根元素。顶级DataGrid的单行项
将显示列组标题。每个DataGridColumnItem对应一个列组。

public class DataGridRowItem
{
  public List<DataGridColumnItem> Columns { get; set; }
}

数据网格列项.cs

每个DataGridColumnItem将组成一个列组。

public class DataGridColumnItem
{
  public string GroupHeader { get; set; }
  public List<Appointment> TableData { get; set; }
}

约会.cs

显示在组的DataGrid中的实际数据模型。

public class Appointment
{
  public DateTime Start { get; set; }
  public DateTime End { get; set; }
}

视图模型.cs

public class TestViewModel : ViewModel
{
  public List<DataGridRowItem> Rows { get; }
  public ViewModel()
  {
    this.GroupingRow = new List<DataGridRowItem>
    {
      // The single row for the grouping top level DataGrid
      new DataGridRowItem()
      {  
        Columns = new List<DataGridColumnItem>()
        {
          // First column group
          new DataGridColumnItem()
          {
            GroupHeader = "Group 1",
            TableData = new List<Appointment>
            {
              new Appointment() { Start = DateTime.Now.AddDays(1), End = DateTime.Now.AddDays(2) },
              new Appointment() { Start = DateTime.Now.AddDays(5), End = DateTime.Now.AddDays(6) }
            }
          },

          // Second column group
          new DataGridColumnItem()
          {
            GroupHeader = "Group 2",
            TableData = new List<Appointment>
            {
              new Appointment() { Start = DateTime.Now.AddDays(3), End = DateTime.Now.AddDays(4) },
              new Appointment() { Start = DateTime.Now.AddDays(7), End = DateTime.Now.AddDays(8) }
            }
          }
        }
      }
    };
  }
}

数据网格助手.cs

一个附加的行为,可帮助在多个DataGrid示例之间同步所选行。该行为最初是为不同的问题编写的,但也可以在此方案中重用。它允许创建DataGrid元素的同步组。

public class DataGridHelper : DependencyObject
{
  public static object GetSynchronizeGroupKey(DependencyObject attachedElement)
    => (object)attachedElement.GetValue(SynchronizeGroupKeyProperty);
  public static void SetSynchronizeGroupKey(DependencyObject attachedElement, object value)
    => attachedElement.SetValue(SynchronizeGroupKeyProperty, value);

  public static readonly DependencyProperty SynchronizeGroupKeyProperty = DependencyProperty.RegisterAttached(
    "SynchronizeGroupKey",
    typeof(object),
    typeof(DataGridHelper),
    new PropertyMetadata(default(object), OnSynchronizeGroupKeyChanged));

  public static bool GetIsSynchronizeSelectedRowEnabled(DependencyObject attachedElement)
    => (bool)attachedElement.GetValue(IsSynchronizeSelectedRowEnabledProperty);
  public static void SetIsSynchronizeSelectedRowEnabled(DependencyObject attachedElement, bool value)
    => attachedElement.SetValue(IsSynchronizeSelectedRowEnabledProperty, value);

  public static readonly DependencyProperty IsSynchronizeSelectedRowEnabledProperty = DependencyProperty.RegisterAttached(
    "IsSynchronizeSelectedRowEnabled",
    typeof(bool),
    typeof(DataGridHelper),
    new PropertyMetadata(default(bool), OnIsSynchronizeSelectedRowEnabledChanged));

  private static Dictionary<object, IList<WeakReference<DataGrid>>> DataGridTable { get; } = new Dictionary<object, IList<WeakReference<DataGrid>>>();
  private static void OnIsSynchronizeSelectedRowEnabledChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
  {
    if (attachingElement is not DataGrid dataGrid)
    {
      throw new ArgumentException($"Attaching element must of type {typeof(DataGrid)}.", nameof(attachingElement));
    }

    if ((bool)e.NewValue)
    {
      RegisterDataGridForSelectedItemSynchronization(dataGrid);
    }
    else
    {
      UnregisterDataGridForSelectedItemSynchronization(dataGrid);
    }
  }

  private static void RegisterDataGridForSelectedItemSynchronization(DataGrid dataGrid)
    => WeakEventManager<DataGrid, SelectionChangedEventArgs>.AddHandler(dataGrid, nameof(DataGrid.SelectionChanged), SynchronizeSelectedItem_OnSelectionChanged);

  private static void UnregisterDataGridForSelectedItemSynchronization(DataGrid dataGrid)
    => WeakEventManager<DataGrid, SelectionChangedEventArgs>.RemoveHandler(dataGrid, nameof(DataGrid.SelectionChanged), SynchronizeSelectedItem_OnSelectionChanged);

  private static void OnSynchronizeGroupKeyChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
  {
    if (attachingElement is not DataGrid dataGrid)
    {
      throw new ArgumentException($"Attaching element must of type {typeof(DataGrid)}.", nameof(attachingElement));
    }
    if (e.NewValue == null)
    {
      throw new ArgumentNullException($"{null} is not a valid value for the attached property {nameof(SynchronizeGroupKeyProperty)}.", nameof(e.NewValue));
    }

    if (!DataGridTable.TryGetValue(e.NewValue, out IList<WeakReference<DataGrid>>? dataGridGroup))
    {
      dataGridGroup = new List<WeakReference<DataGrid>>();
      DataGridTable.Add(e.NewValue, dataGridGroup);
    }

    dataGridGroup.Add(new WeakReference<DataGrid>(dataGrid));
  }

  private static void SynchronizeSelectedItem_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
  {
    var synchronizationSourceDataGrid = sender as DataGrid;
    var synchronizationSourceDataGridGroupKey = GetSynchronizeGroupKey(synchronizationSourceDataGrid);
    if (!DataGridTable.TryGetValue(synchronizationSourceDataGridGroupKey, out IList<WeakReference<DataGrid>> dataGridGroup))
    {
      return;
    }

    var selectedIndices = synchronizationSourceDataGrid.SelectedItems
      .Cast<object>()
      .Select(synchronizationSourceDataGrid.Items.IndexOf)
      .ToList();
    foreach (WeakReference<DataGrid> dataGridReference in dataGridGroup)
    {
      if (!dataGridReference.TryGetTarget(out DataGrid dataGrid)
        || dataGrid == synchronizationSourceDataGrid
        || dataGrid.Items.Count == 0)
      {
        continue;
      }

      UnregisterDataGridForSelectedItemSynchronization(dataGrid);
      dataGrid.SelectedItems.Clear();
      foreach (int selectedItemIndex in selectedIndices)
      {
        var selectedItem = dataGrid.Items[selectedItemIndex];
        dataGrid.SelectedItems.Add(selectedItem);
      }
      RegisterDataGridForSelectedItemSynchronization(dataGrid);
    }
  }
}

溶液3

一个更强大的解决方案是实现一个自定义控件。这样,例如,重新排序/调整列的大小,添加/删除行和自定义都非常方便。
自定义控件GroupingDataGrid基本上将自定义的DataGrid Package 到Grid中。
此解决方案支持自动生成和显式列定义。可以调整列组和各个列的大小。
GroupingDataGrid托管的DataGrid可以不受任何限制地使用,布局简洁,列组的定义(使用Grid定义风格的GroupDefinition)非常方便。
若要自订群组信头,请定义以GroupingDataGridHeader(即ContenControl)为目的的Style
GroupingDataGrid是我的库中的一个现有控件。我从源代码中删除了一些代码,主要是模板等自定义功能,以使帖子尽可能简洁。

用法示例

<local:GroupingDataGrid>
  <local:GroupingDataGrid.GroupDefinitions>

    <!-- Group from column 0 to 3 -->
    <local:GroupDefinition ColumnSpan="4"
                           Header="Person" />

    <!-- Second group from column 4 to 5 -->
    <local:GroupDefinition Column="4"
                           ColumnSpan="2"
                           Header="Numbers" />

    <!-- Remaining columns are automatically added 
         to a common unnamed group -->
  </local:GroupingDataGrid.GroupDefinitions>

    <!-- Define DataGrid as usual -->
    <DataGrid ItemsSource="{Binding DataGridItems}" />
</local:GroupingDataGrid>

源代码

分组数据网格.cs

[ContentProperty(nameof(GroupingDataGrid.DataGrid))]
public class GroupingDataGrid : Control
{
  public GroupDefinitionCollection GroupDefinitions
  {
    get => (GroupDefinitionCollection)GetValue(GroupDefinitionsProperty);
    set => SetValue(GroupDefinitionsProperty, value);
  }

  public static readonly DependencyProperty GroupDefinitionsProperty = DependencyProperty.Register(
    "GroupDefinitions",
    typeof(GroupDefinitionCollection),
    typeof(GroupingDataGrid),
    new PropertyMetadata(default));

  public DataGrid DataGrid
  {
    get { return (DataGrid)GetValue(DataGridProperty); }
    set { SetValue(DataGridProperty, value); }
  }

  public static readonly DependencyProperty DataGridProperty = DependencyProperty.Register(
    "DataGrid",
    typeof(DataGrid),
    typeof(GroupingDataGrid),
    new PropertyMetadata(default(DataGrid), OnDataGridChanged));

  static GroupingDataGrid()
  {
    DefaultStyleKeyProperty.OverrideMetadata(typeof(GroupingDataGrid), new FrameworkPropertyMetadata(typeof(GroupingDataGrid)));
  }
  private static void OnDataGridChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    => (d as GroupingDataGrid).OnDataGridChanged(e.OldValue as DataGrid, e.NewValue as DataGrid);

  private bool IsDataGridLayoutDirty { get; set; }
  private Grid GroupHost { get; }
  private Dictionary<Thumb, GroupingDataGridHeader> ThumbToGroupingDataGridHeaderTable { get; }
  private Dictionary<GroupDefinition, GroupingDataGridHeader> GroupDefinitionToGroupingDataGridHeaderTable { get; }
  public GroupingDataGrid()
  {
    this.GroupDefinitions = new GroupDefinitionCollection();
    this.ThumbToGroupingDataGridHeaderTable = new Dictionary<Thumb, GroupingDataGridHeader>();
    this.GroupDefinitionToGroupingDataGridHeaderTable = new Dictionary<GroupDefinition, GroupingDataGridHeader>();
    this.GroupHost = new Grid();
    this.GroupHost.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
    this.GroupHost.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
  }

  public override void OnApplyTemplate()
  {
    base.OnApplyTemplate();
    var contentHost = GetTemplateChild("PART_DataGridHost") as ContentPresenter;
    if (contentHost != null)
    {
      contentHost.Content = this.GroupHost;
    }
  }

  protected virtual void OnDataGridChanged(DataGrid oldDataGrid, DataGrid newDataGrid)
  {
    if (oldDataGrid != null)
    {
      this.GroupHost.Children.Remove(oldDataGrid);
      oldDataGrid.ColumnDisplayIndexChanged -= OnColumnOrderChanged;
      oldDataGrid.AutoGeneratedColumns -= OnDataGridAutoGeneratedColumns;
    }

    if (newDataGrid == null)
    {
      return;
    }

    this.IsDataGridLayoutDirty = true;
    this.GroupHost.Children.Add(this.DataGrid);
    newDataGrid.ColumnDisplayIndexChanged += OnColumnOrderChanged;
    if (newDataGrid.AutoGenerateColumns && !newDataGrid.IsLoaded)
    {
      newDataGrid.AutoGeneratedColumns += OnDataGridAutoGeneratedColumns;
    }
    else
    {
      CreateVisualTree();
    }
  }

  private void OnColumnOrderChanged(object? sender, DataGridColumnEventArgs e)
    => CreateVisualTree();

  private void OnDataGridAutoGeneratedColumns(object sender, EventArgs e)
    => CreateVisualTree();

  private void CreateVisualTree()
  {
    CreateGroups();
    if (this.IsDataGridLayoutDirty)
    {
      LayoutDataGrid();
    }
  }

  private void CreateGroups()
  {
    this.ThumbToGroupingDataGridHeaderTable.Clear();
    this.GroupDefinitionToGroupingDataGridHeaderTable.Clear();
    ClearGroupHost();

    AddRowHeaderColumnGroup();

    List<DataGridColumn> sortedColumns = this.DataGrid.Columns
    .OrderBy(column => column.DisplayIndex)
    .ToList();
    int ungroupedColumnCount = sortedColumns.Count - this.GroupDefinitions.Sum(definition => definition.ColumnSpan);
    bool hasUngroupedColumns = ungroupedColumnCount > 0;
    for (int groupIndex = 0; groupIndex < this.GroupDefinitions.Count; groupIndex++)
    {
      GroupDefinition group = this.GroupDefinitions[groupIndex];
      int groupHeaderColumnIndex = groupIndex + 1;

      AddGridColumn();
      AddGroupHeader(group, groupHeaderColumnIndex, sortedColumns);
      if (groupHeaderColumnIndex > 1)
      {
        GroupDefinition previousGroup = this.GroupDefinitions[groupIndex - 1];
        AddColumnGrippers(previousGroup, groupHeaderColumnIndex - 1);
      }
    }

    if (hasUngroupedColumns)
    {
      AddGroupForRemainingColumns();
    }
  }

  private void AddGroupForRemainingColumns()
  {
    AddGridColumn(false);
    AddGroupHeader(null, this.GroupHost.ColumnDefinitions.Count - 1, new List<DataGridColumn>());

    if (this.GroupDefinitions.Any())
    {
      GroupDefinition previousGroup = this.GroupDefinitions.Last();
      AddColumnGrippers(previousGroup, this.GroupDefinitions.Count);
    }
  }

  private void CreateColumnGroupHeaderBinding(IList<DataGridColumn> sortedColumns, GroupingDataGridHeader groupHeaderHost)
  {
    GroupDefinition group = groupHeaderHost.GroupDefinition;
    var groupHeaderWidthMultiBinding = new MultiBinding
    {
      Mode = BindingMode.TwoWay,
      Converter = new DataGridColumnRangeWidthToGroupHeaderWidthConverter(sortedColumns),
      ConverterParameter = group
    };
    for (int columnIndex = group.Column; columnIndex < group.Column + group.ColumnSpan; columnIndex++)
    {
      DataGridColumn column = sortedColumns[columnIndex];
      var widthBinding = new Binding(nameof(DataGridColumn.Width))
      {
        Mode = BindingMode.TwoWay,
        Source = column
      };
      groupHeaderWidthMultiBinding.Bindings.Add(widthBinding);
    }
    groupHeaderHost.SetBinding(WidthProperty, groupHeaderWidthMultiBinding);
  }

  private GroupingDataGridHeader AddGroupHeader(GroupDefinition group, int groupHeaderColumnIndex, List<DataGridColumn> sortedColumns)
  {
    var groupHeaderHost = new GroupingDataGridHeader(group);
    Grid.SetColumn(groupHeaderHost, groupHeaderColumnIndex);
    Grid.SetRow(groupHeaderHost, 0);
    this.GroupHost.Children.Add(groupHeaderHost);
     
    if (group != null)
    {
      this.GroupDefinitionToGroupingDataGridHeaderTable.Add(group, groupHeaderHost);
      if (sortedColumns.Any())
      {
        CreateColumnGroupHeaderBinding(sortedColumns, groupHeaderHost);
      }
    }
    return groupHeaderHost;
  }

  private void AddGridColumn(bool isAutoWidth = true)
  {
    var gridColumnWidth = isAutoWidth 
      ? GridLength.Auto 
      : new GridLength(1, GridUnitType.Star);
    var groupHeaderHostColumnDefinition = new ColumnDefinition() { Width = gridColumnWidth };
    this.GroupHost.ColumnDefinitions.Add(groupHeaderHostColumnDefinition);
  }

  private void AddColumnGrippers(GroupDefinition groupDefinition, int groupHeaderColumnIndex)
  {
    GroupingDataGridHeader groupHeaderHost = this.GroupDefinitionToGroupingDataGridHeaderTable[groupDefinition];
    AddColumnGripper(groupHeaderColumnIndex, groupHeaderHost, true);
    AddColumnGripper(groupHeaderColumnIndex + 1, groupHeaderHost);
  }

  private void AddColumnGripper(int columnIndex, GroupingDataGridHeader groupHeader, bool isLeftColumnGripper = false)
  {
    var columnGripper = new Thumb()
    {
      HorizontalAlignment = isLeftColumnGripper
        ? HorizontalAlignment.Right
        : HorizontalAlignment.Left,
    };
    columnGripper.DragDelta += OnGroupHeaderResizing;
    this.ThumbToGroupingDataGridHeaderTable.Add(columnGripper, groupHeader);
    Grid.SetColumn(columnGripper, columnIndex);
    Grid.SetRow(columnGripper, 0);
    this.GroupHost.Children.Add(columnGripper);
  }

  private void LayoutDataGrid()
  {
    Grid.SetColumnSpan(this.DataGrid, this.GroupHost.ColumnDefinitions.Count);
    Grid.SetRow(this.DataGrid, 1);
    this.IsDataGridLayoutDirty = false;
  }

  private void AddRowHeaderColumnGroup()
  {
    AddGridColumn();
    GroupingDataGridHeader rowHeaderGroupHost = AddGroupHeader(null, 0, new List<DataGridColumn>());
    var rowHeaderWidthBinding = new Binding(nameof(DataGrid.RowHeaderActualWidth))
    {
      Source = this.DataGrid
    };
    rowHeaderGroupHost.SetBinding(WidthProperty, rowHeaderWidthBinding);
  }

  private void ClearGroupHost()
  {
    for (int childIndex = this.GroupHost.Children.Count - 1; childIndex >= 0; childIndex--)
    {
      var child = this.GroupHost.Children[childIndex];
      if (child != this.DataGrid)
      {
        this.GroupHost.Children.Remove(child);
      }
    }
  }

  private void OnGroupHeaderResizing(object sender, DragDeltaEventArgs e)
  {
    var thumb = sender as Thumb;
    if (this.ThumbToGroupingDataGridHeaderTable.TryGetValue(thumb, out GroupingDataGridHeader groupingDataGridHeader))
    {
      groupingDataGridHeader.Width += e.HorizontalChange;
    }
  }
}

分组数据网格标题.cs

public class GroupingDataGridHeader : ContentControl
{
  public GroupDefinition GroupDefinition { get; }

  public GroupingDataGridHeader() : this(new GroupDefinition())
  {      
  }

  public GroupingDataGridHeader(GroupDefinition groupDefinition)
  {
    this.GroupDefinition = groupDefinition;
    this.Content = this.GroupDefinition?.Header ?? string.Empty;
  }

  static GroupingDataGridHeader()
  {
    DefaultStyleKeyProperty.OverrideMetadata(typeof(GroupingDataGridHeader), new FrameworkPropertyMetadata(typeof(GroupingDataGridHeader)));
  }
}

组定义.cs

public class GroupDefinition : FrameworkContentElement
{
  public int Column
  {
    get => (int)GetValue(ColumnProperty);
    set => SetValue(ColumnProperty, value);
  }

  public static readonly DependencyProperty ColumnProperty = DependencyProperty.Register(
    "Column",
    typeof(int),
    typeof(GroupDefinition),
    new PropertyMetadata(default));

  public int ColumnSpan
  {
    get => (int)GetValue(ColumnSpanProperty);
    set => SetValue(ColumnSpanProperty, value);
  }

  public static readonly DependencyProperty ColumnSpanProperty = DependencyProperty.Register(
    "ColumnSpan",
    typeof(int),
    typeof(GroupDefinition),
    new PropertyMetadata(default));

  public object Header
  {
    get => (object)GetValue(HeaderProperty);
    set => SetValue(HeaderProperty, value);
  }

  public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register(
    "Header",
    typeof(object),
    typeof(GroupDefinition),
    new PropertyMetadata(default));
}

组定义集合.cs

public class GroupDefinitionCollection : Collection<GroupDefinition>
{ }

数据网格列范围宽度到组标头宽度转换器.cs

public class DataGridColumnRangeWidthToGroupHeaderWidthConverter : IMultiValueConverter
{
  private IList<DataGridColumn> DataGridColumns { get; }

  public DataGridColumnRangeWidthToGroupHeaderWidthConverter(IList<DataGridColumn> dataGridColumns)
  {
    this.DataGridColumns = dataGridColumns;
  }

  public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
    => values.Cast<DataGridLength>().Sum(gridLength => gridLength.DisplayValue);

  public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
  {
    var groupDefinition = (GroupDefinition)parameter;
    double currentGroupedColumnsWidth = this.DataGridColumns
      .Skip(groupDefinition.Column)
      .Take(groupDefinition.ColumnSpan)
      .Select(column => column.Width.DisplayValue)
      .Sum();

    var result = new object[groupDefinition.ColumnSpan];
    Array.Fill(result, Binding.DoNothing);
    DataGridColumn lastGroupColumn = this.DataGridColumns[groupDefinition.Column + groupDefinition.ColumnSpan - 1];
    var newColumnWidth = new DataGridLength(lastGroupColumn.Width.DisplayValue + (double)value - currentGroupedColumnsWidth, DataGridLengthUnitType.Pixel);
    result[result.Length - 1] = newColumnWidth;
    return result;
  }
}

一般.xaml

<ResourceDictionary>
  <Style TargetType="local:GroupingDataGrid">
    <Style.Resources>
      <Style TargetType="Thumb">
        <Setter Property="Width"
                Value="8" />
        <Setter Property="Background"
                Value="Transparent" />
        <Setter Property="Cursor"
                Value="SizeWE" />
        <Setter Property="BorderBrush"
                Value="Transparent" />
        <Setter Property="Template">
          <Setter.Value>
            <ControlTemplate TargetType="{x:Type Thumb}">
              <Border Background="{TemplateBinding Background}"
                      Padding="{TemplateBinding Padding}" />
            </ControlTemplate>
          </Setter.Value>
        </Setter>
      </Style>
    </Style.Resources>

    <Setter Property="BorderThickness"
            Value="0,0,1,0" />
    <Setter Property="BorderBrush"
            Value="Black" />
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="local:GroupingDataGrid">
          <Border BorderThickness="{TemplateBinding BorderThickness}"
                  BorderBrush="{TemplateBinding BorderBrush}"
                  Background="{TemplateBinding Background}"
                  Padding="{TemplateBinding Padding}">
            <ContentPresenter x:Name="PART_DataGridHost" />
          </Border>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>

  <Style TargetType="local:GroupingDataGridHeader">
    <Setter Property="BorderThickness"
            Value="0,0,1,0" />
    <Setter Property="BorderBrush"
            Value="Black" />
    <Setter Property="HorizontalContentAlignment"
            Value="Center" />
    <Setter Property="Template">
      <Setter.Value>
        <ControlTemplate TargetType="local:GroupingDataGridHeader">
          <Border BorderThickness="{TemplateBinding BorderThickness}"
                  BorderBrush="{TemplateBinding BorderBrush}"
                  Background="{TemplateBinding Background}"
                  Padding="{TemplateBinding Padding}">
            <ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
                              VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
          </Border>
        </ControlTemplate>
      </Setter.Value>
    </Setter>
  </Style>
</ResourceDictionary>

相关问题