The column header of the DataGrid is not a FrameWork element for some reason, and so you cannot use bindings to set things like the header text. Please correct me if that is
I solved this by using the HeaderTemplate
and binding to the DataContext
of the DataGrid
, using RelativeSource
.
<DataGrid ItemsSource="{Binding Items}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Value1}">
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding DataContext.ColumnTitel1, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"/>
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
The same binding within the Header
property did not work out.
I know this post is old, but when I looked up how to do this, this is the first entry that came up. I did not like this answer because it seemed like overkill. After more searching, I ran across this link the showed how to do this in the markup, using a template column.
<DataGridTemplateColumn>
<DataGridTemplateColumn.HeaderTemplate>
<DataTemplate>
**<TextBlock Text="{Binding DataContext.HeaderTitle, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />**
</DataTemplate>
</DataGridTemplateColumn.HeaderTemplate>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock Text="{Binding}" Width="200" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
**EDIT :-
You can style the DataGridColumnHeader and do some funky bindings. try here and download the ColumnHeaderBindings.zip, it has a little test project, that is a bit of a hack, but it works
**End Edit
The Binding on the column happens on a per row basis, the column is not part of the visual tree, the binding gets applied to each item in the grid, from the grids source code you can see that the property Binding has these comments
/// <summary>
/// The binding that will be applied to the generated element.
/// </summary>
/// <remarks>
/// This isn't a DP because if it were getting the value would evaluate the binding.
/// </remarks>
So binding to the columns does not make much sense, because as you have found out, when you are not part of the visual tree you have no data context.
The same problem exists with the ComboBoxColumn when you want to bind to the items source. You can bind to a StaticResource, but StaticResources dont have a data context either. You could use an object data provider or just instantiate directly in xaml.
but i would just create the columns in code, and set the header. this problem would just go away then.
there is a good article here on the visual layout.
@mmichtch's answer works well for me, you just have to create a local namespace(xmlns), which contains reference to your project as follows:
xmlns:local="clr-namespace:your_project_name"
and along with it don't forget to mention the property you want to bind:
<DataGridTextColumn Width="Auto">
<DataGridTextColumn.Header>
<TextBlock Text="{Binding DataContext.PropertyNameYouWantToBind,
RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}"/>
</DataGridTextColumn.Header>
</DataGridTextColumn>
it works well with VS 2010 and .net version 4.
My solution allows writing a single line in DataGridColumn
with the name of the property that need to bind. He has the following features:
DataGridTextColumn
DataGridTemplateColumn
StringFormat
for each column StringFormat
Example, which is below, includes StringFormat
(he should stand before the PropertyPath
):
<DataGridTextColumn Behaviors:DataGridHeader.StringFormat="StringFormat: {0:C}"
Behaviors:DataGridHeader.PropertyPath="HeaderValueOne" ... />
Equivalent to a this line:
<DataGridTextColumn HeaderStringFormat="{0:C}"
Header="{Binding Path=HeaderValueOne}" ... />
Who need more examples of solutions and features, please read below.
Link for the sample project.
Notes about the solution
From all the solutions that I have seen earlier, the easiest for me turned out to be this example:
<DataGridTextColumn Binding="{Binding Name}">
<DataGridTextColumn.HeaderTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path=DataContext.YourPropertyName,
RelativeSource={RelativeSource AncestorType={x:Type SomeControl}}" />
</DataTemplate>
</DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
Please pay attention to DataGridTextColumn.HeaderTemplate
, if was used DataGridTextColumn.Header
, then for .NET framework below version 4.5 and for Silverlight would produce an exception:
Header property does not support UIElements
It would seem that what is necessary? I wanted to find a solution that would allow to write a single line in DataGridColumn
with the name of the property that need to bind.
And here's what happened:
<DataGridTextColumn Behaviors:DataGridHeader.PropertyPath="HeaderValueOne" // Attached dependency property
This construction similar to this:
<DataGridTextColumn Header="{Binding Path=HeaderValueOne}" ... />
Also is possible to use StringFormat
for each column like this:
<DataGridTextColumn Behaviors:DataGridHeader.StringFormat="StringFormat: {0:C}"
Behaviors:DataGridHeader.PropertyPath="TestStringFormatValue" ... />
And there is the ability to specify a static value for the StringFormat
:
<DataGridTextColumn Behaviors:DataGridHeader.StringFormat="{x:Static Member=this:TestData.TestStaticStringFormatValue}" // public static string TestStaticStringFormatValue = "Static StringFormat: {0}$";
Behaviors:DataGridHeader.PropertyPath="TestStringFormatValue"
Here is the original DataTemplate
, which is dynamically set to the column:
<DataTemplate>
<TextBlock Text="{Binding Path=DataContext.YourPropertyName,
StringFormat="YourStringFormat",
RelativeSource={RelativeSource AncestorType={x:Type DataGridCellsPanel}}}" />
</DataTemplate>
In order to RelativeSource
did not depend on the type of DataContext
, I took great solution from Mr.Bruno.
In this case, DataGridCellsPanel
contains the correct DataContext, which is set for a parent DataGrid.
Below is the basic code that is performed all the magic:
IsSetHeader PropertyChanged handler
private static void IsSetHeader(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var textColumn = sender as DataGridTextColumn;
var templateColumn = sender as DataGridTemplateColumn;
string path = e.NewValue as string;
if ((textColumn == null) & (templateColumn == null))
{
return;
}
if (String.IsNullOrEmpty(path) == false)
{
currentStringFormat = ReturnStringFormat(textColumn, templateColumn);
dataTemplate = CreateDynamicDataTemplate(path, currentStringFormat);
if (dataTemplate != null)
{
if (textColumn != null)
textColumn.HeaderTemplate = dataTemplate;
if (templateColumn != null)
templateColumn.HeaderTemplate = dataTemplate;
}
}
}
CreateDynamicDataTemplate
private static DataTemplate CreateDynamicDataTemplate(string propertyPath, string stringFormat)
{
var pc = new ParserContext();
MemoryStream sr = null;
string xaml = GetXamlString(propertyPath, stringFormat);
sr = new MemoryStream(Encoding.ASCII.GetBytes(xaml));
pc.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
pc.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
return XamlReader.Load(sr, pc) as DataTemplate;
}
GetXamlString
private static string GetXamlString(string propertyPath, string stringFormat)
{
#region Original PropertyPath for TextBlock
// {Binding Path=DataContext.YourProperty, RelativeSource={RelativeSource AncestorType={x:Type DataGridCellsPanel}}}"
// Thanks to Bruno (https://stackoverflow.com/users/248118/bruno) for this trick
#endregion
var sb = new StringBuilder();
sb.Append("<DataTemplate><TextBlock Text=\"{Binding Path=DataContext.");
sb.Append(propertyPath);
sb.Append(", StringFormat=");
sb.Append(stringFormat);
sb.Append(", RelativeSource={RelativeSource AncestorType={x:Type DataGridCellsPanel}}}\" /></DataTemplate>");
return sb.ToString();
}
StringFormat
must appear before the PropertyPath, because it is optional. In order to for columns, who did not have it is not an exception occurs, I registered try-catch in GetStringFormat
:
public static string GetStringFormat(DependencyObject DepObject)
{
try
{
return (string)DepObject.GetValue(StringFormatProperty);
}
catch
{
return String.Empty;
}
}
Plus: do not write in methods try-catch block, that are trying to get the value.
Minus: The minus for every missed StringFormat
exception will be generated once when the program starts. If it is critical for you, you can always specify the StringFormat="null"
for the column.
Just in case, show the full code of project:
public static class DataGridHeader
{
#region Private Section
private static string textColumnStringFormat = null;
private static string templateColumnStringFormat = null;
private static string currentStringFormat = null;
private static DataTemplate dataTemplate = null;
#endregion
#region PropertyPath DependencyProperty
public static readonly DependencyProperty PropertyPathProperty;
public static void SetPropertyPath(DependencyObject DepObject, string value)
{
DepObject.SetValue(PropertyPathProperty, value);
}
public static string GetPropertyPath(DependencyObject DepObject)
{
return (string)DepObject.GetValue(PropertyPathProperty);
}
#endregion
#region StringFormat DependencyProperty
public static readonly DependencyProperty StringFormatProperty;
public static void SetStringFormat(DependencyObject DepObject, string value)
{
DepObject.SetValue(StringFormatProperty, value);
}
public static string GetStringFormat(DependencyObject DepObject)
{
try
{
return (string)DepObject.GetValue(StringFormatProperty);
}
catch
{
return String.Empty;
}
}
#endregion
#region Constructor
static DataGridHeader()
{
PropertyPathProperty = DependencyProperty.RegisterAttached("PropertyPath",
typeof(string),
typeof(DataGridHeader),
new UIPropertyMetadata(String.Empty, IsSetHeader));
StringFormatProperty = DependencyProperty.RegisterAttached("StringFormat",
typeof(string),
typeof(DataGridHeader),
new UIPropertyMetadata(String.Empty));
}
#endregion
#region IsSetHeader PropertyChanged Handler
private static void IsSetHeader(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var textColumn = sender as DataGridTextColumn;
var templateColumn = sender as DataGridTemplateColumn;
string path = e.NewValue as string;
if ((textColumn == null) & (templateColumn == null))
{
return;
}
if (String.IsNullOrEmpty(path) == false)
{
currentStringFormat = ReturnStringFormat(textColumn, templateColumn);
dataTemplate = CreateDynamicDataTemplate(path, currentStringFormat);
if (dataTemplate != null)
{
if (textColumn != null)
textColumn.HeaderTemplate = dataTemplate;
if (templateColumn != null)
templateColumn.HeaderTemplate = dataTemplate;
}
}
}
#endregion
#region ReturnStringFormat Helper
private static string ReturnStringFormat(DependencyObject depObject1, DependencyObject depObject2)
{
textColumnStringFormat = GetStringFormat(depObject1) as string;
templateColumnStringFormat = GetStringFormat(depObject2) as string;
if (String.IsNullOrEmpty(textColumnStringFormat) == false)
{
return textColumnStringFormat;
}
if (String.IsNullOrEmpty(templateColumnStringFormat) == false)
{
return templateColumnStringFormat;
}
return "null";
}
#endregion
#region CreateDynamicDataTemplate Helper
private static DataTemplate CreateDynamicDataTemplate(string propertyPath, string stringFormat)
{
var pc = new ParserContext();
MemoryStream sr = null;
string xaml = GetXamlString(propertyPath, stringFormat);
sr = new MemoryStream(Encoding.ASCII.GetBytes(xaml));
pc.XmlnsDictionary.Add("", "http://schemas.microsoft.com/winfx/2006/xaml/presentation");
pc.XmlnsDictionary.Add("x", "http://schemas.microsoft.com/winfx/2006/xaml");
return XamlReader.Load(sr, pc) as DataTemplate;
}
#endregion
#region GetXamlString Helper
private static string GetXamlString(string propertyPath, string stringFormat)
{
#region Original PropertyPath for TextBlock
// {Binding Path=DataContext.YourProperty, RelativeSource={RelativeSource AncestorType={x:Type DataGridCellsPanel}}}"
// Thanks to Bruno (https://stackoverflow.com/users/248118/bruno) for this trick
#endregion
var sb = new StringBuilder();
sb.Append("<DataTemplate><TextBlock Text=\"{Binding Path=DataContext.");
sb.Append(propertyPath);
sb.Append(", StringFormat=");
sb.Append(stringFormat);
sb.Append(", RelativeSource={RelativeSource AncestorType={x:Type DataGridCellsPanel}}}\" /></DataTemplate>");
return sb.ToString();
}
#endregion
}
XAML
<Window x:Class="BindingHeaderInDataGrid.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:this="clr-namespace:BindingHeaderInDataGrid"
xmlns:Behaviors="clr-namespace:BindingHeaderInDataGrid.AttachedBehaviors"
WindowStartupLocation="CenterScreen"
Title="MainWindow" Height="220" Width="600">
<Window.DataContext>
<this:TestData />
</Window.DataContext>
<Grid Name="TestGrid">
<DataGrid Name="TestDataGrid"
Width="550"
Height="100"
Margin="10"
VerticalAlignment="Top"
Background="AliceBlue">
<DataGrid.Columns>
<DataGridTextColumn Behaviors:DataGridHeader.StringFormat="StringFormat: {0:C}"
Behaviors:DataGridHeader.PropertyPath="TestStringFormatValue"
Width="100"
IsReadOnly="False">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Height" Value="20" />
<Setter Property="Background" Value="Pink" />
<Setter Property="Margin" Value="2,0,0,0" />
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
<DataGridTextColumn Behaviors:DataGridHeader.StringFormat="{x:Static Member=this:TestData.TestStaticStringFormatValue}"
Behaviors:DataGridHeader.PropertyPath="TestStringFormatValue"
Width="2*"
IsReadOnly="False">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Height" Value="20" />
<Setter Property="Background" Value="CadetBlue" />
<Setter Property="Margin" Value="2,0,0,0" />
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
<DataGridTextColumn Behaviors:DataGridHeader.PropertyPath="TestUsualHeaderValue"
Width="1.5*"
IsReadOnly="False">
<DataGridTextColumn.HeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Height" Value="20" />
<Setter Property="Background" Value="Gainsboro" />
<Setter Property="Margin" Value="2,0,0,0" />
</Style>
</DataGridTextColumn.HeaderStyle>
</DataGridTextColumn>
<DataGridTemplateColumn Behaviors:DataGridHeader.PropertyPath="TestTemplateColumnValue"
Width="150"
IsReadOnly="False">
<DataGridTemplateColumn.HeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Height" Value="20" />
<Setter Property="Background" Value="Beige" />
<Setter Property="Margin" Value="2,0,0,0" />
</Style>
</DataGridTemplateColumn.HeaderStyle>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
<Button Name="ChangeHeader"
Width="100"
Height="30"
VerticalAlignment="Bottom"
Content="ChangeHeader"
Click="ChangeHeader_Click" />
</Grid>
</Window>
Code-behind
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
private void ChangeHeader_Click(object sender, RoutedEventArgs e)
{
TestData data = this.DataContext as TestData;
data.TestStringFormatValue = "777";
data.TestUsualHeaderValue = "DynamicUsualHeader";
data.TestTemplateColumnValue = "DynamicTemplateColumn";
}
}
public class TestData : NotificationObject
{
#region TestStringFormatValue
private string _testStringFormatValue = "1";
public string TestStringFormatValue
{
get
{
return _testStringFormatValue;
}
set
{
_testStringFormatValue = value;
NotifyPropertyChanged("TestStringFormatValue");
}
}
#endregion
#region TestStaticStringFormatValue
public static string TestStaticStringFormatValue = "Static StringFormat: {0}$";
#endregion
#region TestUsualHeaderValue
private string _testUsualHeaderValue = "UsualHeader";
public string TestUsualHeaderValue
{
get
{
return _testUsualHeaderValue;
}
set
{
_testUsualHeaderValue = value;
NotifyPropertyChanged("TestUsualHeaderValue");
}
}
#endregion
#region TestTemplateColumnValue
private string _testTemplateColumnValue = "TemplateColumn";
public string TestTemplateColumnValue
{
get
{
return _testTemplateColumnValue;
}
set
{
_testTemplateColumnValue = value;
NotifyPropertyChanged("TestTemplateColumnValue");
}
}
#endregion
}
This is the easy way to bind the DataGridTextColumn header to the data context:
<DataGrid x:Name="summaryGrid" Grid.Row="3" AutoGenerateColumns="False" IsReadOnly="True" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Hard Coded Title" Width="*"/>
<DataGridTextColumn Width="100">
<DataGridTextColumn.Header>
<TextBlock Text="{Binding DataContext.SecondColumnTitle,
RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}"/>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Width="150">
<DataGridTextColumn.Header>
<TextBlock Text="{Binding DataContext.ThirdColumnTitle,
RelativeSource={RelativeSource AncestorType={x:Type local:MainWindow}}}"/>
</DataGridTextColumn.Header>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
You will obviously need to have properties: SecondColumnTitle
and ThirdColumnTitle
implemented on your data context class.
I have this solution working in .net 4.5 and did not have a chance nor reason to try it in earlier versions of the framework.