WPF DataGrid – Including Headers when Copying to Clipboard
The WPF data grid has a property that controls the how headers get handled when copied. ClipboardCopyMode, can be set it to “IncludeHeader” or “ExcludeHeader” which is the default. There is the possibility the grid can exclude the header even in IncludeHeader mode. Let me explain.
The data grid header is customizable. It can use a header template object to place whatever you can imagine into the header. Imagine you want dancing clown gifs in the header of the grid. In WPF you can do that. Beware though, this is the condition that breaks IncludeHeader mode. Your dancing clowns won't make it to the clipboard.
For my example the header template includes a unit of measure displayed next to the column name. The user can change the unit of measure so this can't be a static value. The XAML markup looks like this.
<UserControl.Resources>
<DataTemplate x:Key="FlowRate" DataType="DataGridColumnHeader">
<TextBlock Text="{Binding Source={x:Static units:UnitsContext.CurrentSymbols}, Path=FlowUnit, StringFormat=Flow-Rate ({0})}" />
</DataTemplate>
<DataTemplate x:Key="Pressure" DataType="DataGridColumnHeader">
<TextBlock Text="{Binding Source={x:Static units:UnitsContext.CurrentSymbols}, Path=PressureUnit, StringFormat=Pressure ({0})}" />
</DataTemplate>
</UserControl.Resources>
This creates a header in the table that looks like this. | Flow-Rated (BPD) | Pressure (psi) |
The issue is if we copy/paste from the grid into Excel we get this. | | |
The fix for this is actually relatively simple. The required text needs to get from the header template back up to the data grid. To do this we can use an attached property.
/// <summary>
/// This attached property works with a header template that includes one TextBlock. Text content from the templates TextBlock is copied to the
/// column header for the clipboard to grab.
/// </summary>
public static class TemplatedDataGridHeaderText {
private static readonly Type OwnerType = typeof(TemplatedDataGridHeaderText);
public static readonly DependencyProperty UseTextFromTemplateProperty = DependencyProperty.RegisterAttached("UseTextFromTemplate", typeof(bool), OwnerType, new PropertyMetadata(false, OnHeaderTextChanged));
public static bool GetUseTextFromTemplate(DependencyObject obj) {
return (bool)obj.GetValue(UseTextFromTemplateProperty);
}
public static void SetUseTextFromTemplate(DependencyObject obj, bool value) {
obj.SetValue(UseTextFromTemplateProperty, value);
}
private static void OnHeaderTextChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
var textColumn = d as DataGridTextColumn;
if (textColumn == null) return;
if (textColumn.HeaderTemplate == null) return;
var headerTemplateTexblockText = textColumn.HeaderTemplate.LoadContent().GetValue(TextBlock.TextProperty).ToString();
textColumn.Header = headerTemplateTexblockText;
}
}
In XAML this attached property can now be used to ensure the header text will make its way to the clipboard.
<DataGrid ItemsSource="{Binding }" AutoGenerateColumns="False" IsReadOnly="True" VerticalScrollBarVisibility="Auto" VerticalAlignment="Stretch">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding FlowRate.UserValue, StringFormat=N3}" HeaderTemplate="{StaticResource FlowRate}"
attachedProperties:TemplatedDataGridHeaderText.UseTextFromTemplate="True"/>
<DataGridTextColumn Binding="{Binding Pressure.UserValue, StringFormat=N3}" HeaderTemplate="{StaticResource Pressure}"
attachedProperties:TemplatedDataGridHeaderText.UseTextFromTemplate="True"/>
</DataGrid.Columns>
</DataGrid>
This is just one approach for solving this issue. Another might be to directly set the header text through an attached property.