Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to group Column Header in WPF

Is it possible to do this in a WPF datagrid :

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

|-----A1Header----|----A2Header-----|-----B1Header-----|-----B2Header------|
|-----A1Data------|----A2 Data------|-----B1 Data------|-----B2 Data-------|
|-----A1Data------|----A2 Data------|-----B1 Data------|-----B2 Data-------|

Thanks.

like image 325
David Brunelle Avatar asked Jun 07 '11 13:06

David Brunelle


2 Answers

I can offer thee solutions to the problem of grouping columns.

Solution 1

Using conventional grouping via the ICollectionView of the source collection. This groups are vertical, means they share the same columns.


Solution 2

Create a nested data source. The idea is that each column binds to an individual data set that is displayed by a DataGrid that is added to the column's DataGridTemplateColumn. It's a DataGrid for each column group. The disadvantage of this solution is that the constraints for the data structure are very strict. No DataTable supported and no auto generation of columns. The effort increases if column sorting or reordering is allowed. but for simple displaying of a grouped table, this solution is fine enough.

Usage example

enter image description here

MainWindow.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>

It's recommended to disable table modifications via the DataGrid. To prettify the look like centering the group names or overriding the DataGridRow template to add row highlight for the unfocused grids is quite simple.

Implementation example

The data structure for the nested tables:

DataGridRowItem.cs
The root element. A single row item for the top-level DataGrid
that will display the column group headers. A column group for each DataGridColumnItem.

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

DataGridColumnItem.cs
Each DataGridColumnItem will make a column group.

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

Appointment.cs
The actual data model that is displayed in the group's DataGrid.

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

ViewModel.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) }
            }
          }
        }
      }
    };
  }
}

DataGridHelper.cs
An attached behavior that helps to synchronize the selected row across multiple DataGrid instances. the behavior was originally written for different problems, but can be reused in this scenario too. It allows to create synchronization groups of DataGrid elements.

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);
    }
  }
}

Solution 3

A more powerful solution is to implement a custom control. This way e.g., reorder/resize columns, add/remove rows and customization are really convenient.
The custom control GroupingDataGrid basically wraps a custom DataGrid into a Grid.
This solution supports auto generation as well as explicit column definitions. The column groups and the individual columns can be resized.
The DataGrid, that is hosted by the GroupingDataGrid, can be used without any restrictions. The layout is clean and the definition of column groups (using the GroupDefinition in the style of Grid definitions) is quite convenient.
To customize the group headers, define a Style that targets GroupingDataGridHeader (which is a ContenControl).
The GroupingDataGrid is an existing control from my library. I removed some code, mainly customization features like templating, from the sources to keep the post as concise as possible.

Usage example

![enter image description here

<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>

Source code

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;
    }
  }
}

GroupDefinition.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));
}

GroupDefinitionCollection.cs

public class GroupDefinitionCollection : Collection<GroupDefinition>
{ }

DataGridColumnRangeWidthToGroupHeaderWidthConverter.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;
  }
}

Generic.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>
like image 125
BionicCode Avatar answered Oct 16 '22 04:10

BionicCode


This Thread might help you achieve what you're trying to do.

It doesn't get the functionality directly from the DataGrid, but instead the DataGrid is wrapped in a regular Grid, and uses bound columns (with multi-columnspan) to add super headers.

Hopefully there's a nice easy way to do this directly from the DataGrid, but if not, maybe this will be an acceptable workaround for you.

like image 30
Scott Avatar answered Oct 16 '22 05:10

Scott