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.
I can offer thee solutions to the problem of grouping columns.
Using conventional grouping via the ICollectionView
of the source collection. This groups are vertical, means they share the same columns.
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.
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.
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);
}
}
}
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.
<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>
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>
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With