My solution allows writing a single line in DataGridColumn
with the name of the property that need to bind. He has the following features:
- There is support for
DataGridTextColumn
- There is support for
DataGridTemplateColumn
- Set
StringFormat
for each column
- Specify a static value for the
StringFormat
- Fully complies with MVVM pattern
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
}
behavior
keyword is not recognized. The blog entry you're linking to does not mention it at all. What am I missing? :( – Konrad Morawski<Page x:Class="MyClass" ... xmlns:behavior="clr-namespace:WpfApplication1.MyDataContextSpyNamespace">
– transistor1