In WPF I'm using custom ColumnHeaderStyle ...
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Border Background="#3ec9ed"
BorderBrush="Black"
BorderThickness="0"
Padding="10"
CornerRadius="25">
<ContentPresenter HorizontalAlignment="Center"
VerticalAlignment="Center"
Margin="5,0,5,0" />
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Setter Property="Foreground"
Value="White" />
<Setter Property="HorizontalContentAlignment"
Value="Center" />
<Setter Property="Padding"
Value="15" />
<Setter Property="SeparatorBrush"
Value="Red" />
<Setter Property="SeparatorVisibility"
Value="Visible" />
</Style>
</DataGrid.ColumnHeaderStyle>
Now it looks like this.
I want to add a little space between every column header. Like this
What can I do to achieve this?
The DataGrid
wraps all column headers into a common top level DataGridColumnHeader
container (it's basically a big single column that contains the individual column headers).
When you set the DataGridColumnHeader.Background
via the global Style
the value also applies to this outer container which will in effect make the gap between the column headers disappear (because they share the same Background
.
The solution is to set the Background
for this top level column to e.g. Brushes.Transparent
. The top level column usually has a DataGridColumnHeader.DisplayIndex
of -1
.
This allows us to address this special column using a template Trigger
.
You must also wire your template elements to the templated parent properly in order to allow the DataGridColumnHeader
properties like Background
or Margin
to behave as expected (i.e. to have any effect on the layout). You usually use the TemplateBinding
markup extension for this.
To enable styling of the first and last item/column border individually (to apply the round corners), you need a MultiBinding
with a custom IMultiValueConverter
. The purpose of this converter is to detect the last item/column.
The ItemIndexComparerConverter
from the below example allows to specify the index of the item of interest using the Index notation (for example ^1
to reference the last item or 1
to reference the second item). Simply pass the index value of the desired column to the MultiBinding.ConverterParameter
property.
The fixed and improved Style
that applies a gap between the columns of 10 DIP (Margin
left and right of 5 DIP) could look as follows:
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Margin"
Value="5,0" />
<Setter Property="Background"
Value="#3ec9ed" />
<Setter Property="BorderBrush"
Value="Black" />
<Setter Property="BorderThickness"
Value="0" />
<Setter Property="Padding"
Value="10" />
<Setter Property="Foreground"
Value="White" />
<Setter Property="HorizontalContentAlignment"
Value="Center" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<Border>
<Border x:Name="Border"
Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Padding="{TemplateBinding Padding}"
CornerRadius="0">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>
</Border>
<ControlTemplate.Triggers>
<!-- Set the top level column header that contains the individual table column headers
to have a Transparent background in order to make the gaps visible -->
<Trigger Property="DisplayIndex"
Value="-1">
<Setter Property="Background"
Value="Transparent" />
</Trigger>
<!-- Round corners on the left for first column's border -->
<Trigger Property="DisplayIndex"
Value="0">
<Setter TargetName="Border"
Property="CornerRadius"
Value="25,0,0,25" />
</Trigger>
<!-- Round corners on the right for last column's border -->
<DataTrigger Value="True">
<DataTrigger.Binding>
<!-- Pass '^1' as ConverterParameter to indicate that the Converter has to find the last column -->
<MultiBinding ConverterParameter="^1">
<MultiBinding.Converter>
<local:ItemIndexComparerConverter />
</MultiBinding.Converter>
<Binding RelativeSource="{RelativeSource Self}"
Path="DisplayIndex" />
<Binding RelativeSource="{RelativeSource AncestorType=ItemsControl}" />
</MultiBinding>
</DataTrigger.Binding>
<Setter TargetName="Border"
Property="CornerRadius"
Value="0,25,25,0" />
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
ItemIndexComparerConverter.cs
Converter that returns true
when the Index
reference value of the MultiBinding.ConverterParameter
equals the item's index, otherwise false
.
Required input (via MultiBinding
) is the current item's index and the ItemsControl
of the related items source collection.
public class ItemIndexComparerConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (parameter is not string referenceIndexText
|| values.FirstOrDefault(item => item is int) is not int indexOfCurrentItem
|| values.FirstOrDefault(item => item is ItemsControl) is not ItemsControl itemsControl)
{
return false;
}
Index referenceIndex = referenceIndexText.StartsWith('^')
? Index.FromEnd(int.Parse(referenceIndexText[1..]))
: Index.FromStart(int.Parse(referenceIndexText));
int trueReferenceIndex = referenceIndex.GetOffset(itemsControl.Items.Count);
return indexOfCurrentItem == trueReferenceIndex;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
=> throw new NotSupportedException();
}