I have a DataGrid in a WPF form with a DataGridCheckBoxColumn, but I did not find any click event, Checked and unchecked for it...
Are these events available for the DataGridCheckBoxColumn? If not please suggest some workaround I could use.
Quoted from William Han's answer here: http://social.msdn.microsoft.com/Forums/ar/wpf/thread/9e3cb8bc-a860-44e7-b4da-5c8b8d40126d
It simply adds an event to the column. It is a good simple solution.
Perhaps you can use EventSetter as example below:
Markup:
<Window x:Class="DataGridCheckBoxColumnTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DataGridCheckBoxColumnTest"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<local:People x:Key="People"/>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{StaticResource People}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding Path=Name}" Header="Name"/>
<DataGridCheckBoxColumn Binding="{Binding Path=LikeCar}" Header="LikeCar">
<DataGridCheckBoxColumn.CellStyle>
<Style>
<EventSetter Event="CheckBox.Checked" Handler="OnChecked"/>
</Style>
</DataGridCheckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
Code:
using System;
using System.Windows;
namespace DataGridCheckBoxColumnTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
void OnChecked(object sender, RoutedEventArgs e)
{
throw new NotImplementedException();
}
}
}
namespace DataGridCheckBoxColumnTest
{
public class Person
{
public Person(string name, bool likeCar)
{
Name = name;
LikeCar = likeCar;
}
public string Name { set; get; }
public bool LikeCar { set; get; }
}
}
using System.Collections.Generic;
namespace DataGridCheckBoxColumnTest
{
public class People : List<Person>
{
public People()
{
Add(new Person("Tom", false));
Add(new Person("Jen", false));
}
}
}
Expanding on the DataGridCell concept noted above, this is what we used to get it working.
...XAML...
<DataGrid Grid.ColumnSpan="2" Name="dgMissingNames" ItemsSource="{Binding Path=TheMissingChildren}" Style="{StaticResource NameListGrid}" SelectionChanged="DataGrid_SelectionChanged">
<DataGrid.Columns>
<DataGridTemplateColumn CellStyle="{StaticResource NameListCol}">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<CheckBox IsChecked="{Binding Path=Checked, UpdateSourceTrigger=PropertyChanged}" Name="theCheckbox" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Binding="{Binding Path=SKU}" Header="Album" />
<DataGridTextColumn Binding="{Binding Path=Name}" Header="Name" "/>
<DataGridTextColumn Binding="{Binding Path=Pronunciation}" Header="Pronunciation" />
</DataGrid.Columns>
</DataGrid>
TheMissingChildren is an ObservableCollection object that contains the list of data elements including a boolean field "Checked" that we use to populate the datagrid.
The SelectionChanged code here will set the checked boolean in the underlying TheMissingChildren object and fire off a refresh of the items list. That ensures that the box will get checked off & display the new state no matter where you click on the row. Clicking the checkbox or somewhere in the row will toggle the check on/off.
private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
DataGrid ThisGrid = (DataGrid)sender;
CheckedMusicFile ThisMusicfile = (CheckedMusicFile)ThisGrid.SelectedItem;
ThisMusicfile.Checked = !ThisMusicfile.Checked;
ThisGrid.Items.Refresh();
}
<wpf:DataGridCheckBoxColumn Header="Cool?" Width="40" Binding="{Binding IsCool}"/>
How about something like this.
partial class SomeAwesomeCollectionItems : INotifyPropertyChanged
{
public event PropertyChanged;
protected void OnPropertyChanged(string property)
{
if (PropertyChanged != null) PropertyChanged(this, new PropertyChangedEventArgs(property);
}
private bool _IsSelected;
public bool IsSelected { get { return _IsSelected; } set { _IsSelected = Value; OnPropertyChanged("IsSelected"); } }
}
Then in XAML
<DataGrid ItemsSource="{Binding Path=SomeAwesomeCollection"} SelectionMode="Single">
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}"
BasedOn="{StaticResource {x:Type DataGridRow}}">
<!--Note that you will probably need to base on other style if you have stylized your DataGridRow-->
<Setter Property="IsSelected" Value="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}" />
</Style>
</DataGrid.Resources
<DataGrid.Columns>
<DataGridCheckBoxColumn Binding="{Binding Path=IsSelected, UpdateSourceTrigger=PropertyChanged}" />
<!--More Columns-->
</DataGrid.Columns>
</DataGrid>
One note with this approach, however, is you may run into issues with virtualization and checked items not clearing (not sure, haven't tested with SelectionMode="Single"). If that is the case, the simplest workaround I have found to work is to turn virtualization off, but perhaps there is a better way to get around that particular issue.
If you do not want to add the event to your style you can also do it this way.
<DataGridCheckBoxColumn x:Name="name" Header="name?" Binding="{Path=Name}"
<DataGridCheckBoxColumn.CellStyle>
<Style TargetType="DataGridCell" BasedOn="{StaticResource {x:Type DataGridCell}}">
<EventSetter Event="CheckBox.Checked" Handler="Checked"/>
</Style>
</DataGridCheckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>eckBoxColumn.CellStyle>
</DataGridCheckBoxColumn>
Try this in xml
<DataGridCheckBoxColumn Header="" IsThreeState="False" Binding="{Binding isCheck, NotifyOnTargetUpdated=True, UpdateSourceTrigger=PropertyChanged}" />
And in c#
public partial class UploadWindow : Window
{
ObservableCollection<Items> pl = new ObservableCollection<Items>();
class Items
{
public bool isCheck { get; set; }
}
public UploadWindow(Dictionary<string, object> ipDictionary)
{
InitializeComponent();
GridView1.ItemsSource = pl;
}
}
In my case, I need to find all checked checkboxes
var allChecked = pl.Where(x => x.isCheck == true);
Related
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 wrong of if that has changed with .NET 4.0 (I am using the latest WPFToolkit from CodePlex now).
I am trying to use the DataGrid for a time sheet presentation where the day's date should be part of the header text (ie, "Sun, Nov 01"), and I have the following in my XAML:
<dg:DataGrid.Columns>
<dg:DataGridTextColumn Header="Description" Width="Auto" Binding="{Binding Description}" IsReadOnly="True"/>
<dg:DataGridTextColumn Header="Mon" Width="50" Binding="{Binding Allocations[0].Amount}" />
... every other day of the week ....
<dg:DataGridTextColumn Header="Sun" Width="50" Binding="{Binding Allocations[6].Amount}" />
<dg:DataGridTextColumn Header="Total" MinWidth="50" Binding="{Binding TotalAllocatedAmount}" IsReadOnly="True" />
</dg:DataGrid.Columns>
I'd like to use the same AllocationViewModel I am using for data (ie, "{Binding Allocations[0].Amount}" and bind it's DisplayName property to the header text. Can someone show me how to do that? If I have to use a static resource, how can I get the DataContext in there?
EDIT ---------------- PREFERRED WORK-AROUND
Josh Smith had posted about a DataContextSpy awhile back, and it is the cleanest workaround I have come across to this problem. Here is the class that makes it work:
/// <summary>
/// Workaround to enable <see cref="DataContext"/> bindings in situations where the DataContext is not redily available.
/// </summary>
/// <remarks>http://blogs.infragistics.com/blogs/josh_smith/archive/2008/06/26/data-binding-the-isvisible-property-of-contextualtabgroup.aspx</remarks>
public class DataContextSpy : Freezable
{
public DataContextSpy()
{
// This binding allows the spy to inherit a DataContext.
BindingOperations.SetBinding(this, DataContextProperty, new Binding());
}
public object DataContext
{
get { return GetValue(DataContextProperty); }
set { SetValue(DataContextProperty, value); }
}
// Borrow the DataContext dependency property from FrameworkElement.
public static readonly DependencyProperty DataContextProperty = FrameworkElement
.DataContextProperty.AddOwner(typeof (DataContextSpy));
protected override Freezable CreateInstanceCore()
{
// We are required to override this abstract method.
throw new NotImplementedException();
}
}
With this in place, I can hijack the DC I need in xaml:
<dg:DataGrid.Resources>
<behavior:DataContextSpy x:Key="spy" DataContext="{Binding Allocations}" />
</dg:DataGrid.Resources>
And then apply as needed via binding:
<dg:DataGridTextColumn Header="{Binding Source={StaticResource spy}, Path=DataContext[0].DisplayName}"
Width="50" Binding="{Binding Allocations[0].Amount}" />
Suh-weet!
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.
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>
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.
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
}
BTW, in Silverlight (tested with SL 3.0) you can simply use the Header property as the DataContext for the ControlTemplate set via HeaderStyle (see my related question on SO).
I just tried this solution in WPF 3.5 using the WPF Toolkit DataGrid and it works!
**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.
an even better solution would be to set the binding in the header's style and pass the column as the header's dataContext... (or even better: to set up an object representing the header's dataContext and pass it)
see there for a way to do this :
How to set the DataContext on a DataGrid Column Header
#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.
I used it to populate the DataGrid Column Header.
Trick is in the binding mode. Its' "Mode" needs to be set to "OneWay". Otherwise it is no good.
Example:
<DataGridTextColumn Binding="{Binding RowData}" Header="{Binding Mode=OneWay, Source={StaticResource spy},Path=DataContext.HeaderText, FallbackValue= header text}"/>
I used a lowercase fallback value, and the value from DataContext was capitalized to assure me that the resource was not null. Also, the value from DataContext was only showing up for me at run time, during design time it displayed the fallback value. Hope this helps.
In my TreeView I use two differnt classes for binding. For example, I have a Group what can have ChildGroup and can have Items.
Example code of this classes:
using System.Collections.Generic;
using System.Collections.ObjectModel;
namespace WpfApplication1
{
public class Group
{
public Group(string name)
{
Name = name;
items = new ObservableCollection<Item>();
groups = new ObservableCollection<Group>();
}
public string Name { get;
set;
}
private ObservableCollection<Item> items;
private ObservableCollection<Group> groups;
public ObservableCollection<Item> Items
{
get { return items; }
}
public ObservableCollection<Group> Groups
{
get { return groups; }
}
public IEnumerable<object> AllItems
{
get
{
foreach (var group in groups)
{
yield return group;
}
foreach (var item in items)
{
yield return item;
}
}
}
}
public class Item
{
public Item(string name)
{
ItemName = name;
}
public string ItemName
{
get;
set;
}
}
}
To bind it to TreeView I use following template
<Grid>
<TreeView Name="treeView">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type WpfApplication1:Group}"
ItemsSource="{Binding AllItems}">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
<DataTemplate DataType="{x:Type WpfApplication1:Item}">
<TextBlock Text="{Binding ItemName}" FontStyle="Italic"/>
</DataTemplate>
</TreeView.Resources>
</TreeView>
</Grid>
It is easy.
The problem is that I need to change ItemTemplate when Is selected. And I need to change only then Item class selected.
I can do it if only one class use for binding. It also easy using Style and Trigger, like this:
<TreeView Name="treeView1" Grid.Column="1">
<TreeView.Resources>
<HierarchicalDataTemplate DataType="{x:Type WpfApplication1:Group}"
ItemsSource="{Binding AllItems}"
x:Key="groupTemplate">
<TextBlock Text="{Binding Name}"/>
</HierarchicalDataTemplate>
<HierarchicalDataTemplate DataType="{x:Type WpfApplication1:Group}"
ItemsSource="{Binding AllItems}"
x:Key="selectedGroupTemplate">
<TextBlock Text="{Binding Name}" FontStyle="Italic" FontWeight="Bold" FontSize="14"/>
</HierarchicalDataTemplate>
</TreeView.Resources>
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<Setter Property="HeaderTemplate" Value="{StaticResource groupTemplate}"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="HeaderTemplate" Value="{StaticResource selectedGroupTemplate}"/>
</Trigger>
</Style.Triggers>
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
But I have a trouble for multiclass binding.
How can I change SelectedItem template then multiclass binding using? Any ideas?
My code behind sample:
using System.Collections.ObjectModel;
using System.Windows;
namespace WpfApplication1
{
/// <summary>
/// Interaction logic for Window2.xaml
/// </summary>
public partial class Window2 : Window
{
private ObservableCollection<Group> _groups;
public ObservableCollection<Group> Groups
{
get { return _groups; }
}
public Window2()
{
InitializeComponent();
InitGroups();
treeView.ItemsSource = _groups;
treeView1.ItemsSource = _groups;
}
private void InitGroups()
{
_groups = new ObservableCollection<Group>();
Group group1 = new Group("Group1");
group1.Groups.Add(new Group("Group1.1"));
group1.Groups.Add(new Group("Group1.2"));
group1.Groups.Add(new Group("Group1.3"));
group1.Items.Add(new Item("Item1.1"));
group1.Items.Add(new Item("Item1.2"));
group1.Groups[1].Items.Add(new Item("Item1.2.1"));
group1.Groups[1].Items.Add(new Item("Item1.2.2"));
_groups.Add(group1);
Group group2 = new Group("Group2");
group2.Groups.Add(new Group("Group2.1"));
group2.Groups.Add(new Group("Group2.2"));
group2.Items.Add(new Item("Item2.1"));
group2.Groups[0].Items.Add(new Item("Item2.1.1"));
group2.Groups[0].Items.Add(new Item("Item2.1.1"));
_groups.Add(group2);
}
}
}
Result
Now I think to use TreeView.HeaderTemplateSelector, but may be exists way to use only xaml.
Thanks.
There are a number of ways to acheive your desired result. If you are sure that your DataTemplate will only be used in TreeViewItem objects, then the easiest is simply to bind directly to the TreeViewItem.IsSelected property and then react to the change in your DataTemplate:
<DataTemplate DataType="{x:Type WpfApplication1:Item}">
<TextBlock Text="{Binding ItemName}">
<TextBlock.Style>
<Style>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSelected, RelativeSource=
{RelativeSource FindAncestor, AncestorType={x:Type TreeViewItem}}, FallbackValue=False}"
Value="True">
<Setter Property="TextBlock.FontStyle" Value="Italic" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</DataTemplate>
I have a WPF application which consists of a TabControl. I have made a simple version of it for the purpose of this question:
In the first tab I have combobox which fills a datagrid. If I selected a row in the datagrid it gets bound a couple of textboxes and the user may edit its contents.
My objects in the datagrid implements the IDataErrorInfo interface and my textboxes has ValidatesOnDataErrors=True set in the {binding}. So if I erase the contents of the Name textbox it gets invalid (after the textbox loses focus):
Now, if it is invalid I don't want the user to be able to select another row in the datagrid, or select another row in the combobox (which would repopulate the datagrid). Basically I want the user to correct the name before he/she continues. Although, I would prefer if the user could switch tab.
So I either need to disable the controls to the left if the bound object is invalid or I need to set focus to the invalid textbox if I click on the controls to the left. I havn't found any suitable events or bindings for this. All ideas are appreciated.
Here is my XAML:
<Window x:Class="WpfValidationTest.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="350">
<TabControl>
<TabItem Header="Tab 1">
<StackPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical">
<ComboBox>
<ComboBox.Items>
<ComboBoxItem Content="Friends"/>
<ComboBoxItem Content="Business"/>
</ComboBox.Items>
</ComboBox>
<DataGrid Name="dg" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Name" Binding="{Binding Name}" />
<DataGridTextColumn Header="Address" Binding="{Binding Address}" />
</DataGrid.Columns>
</DataGrid>
</StackPanel>
<StackPanel Orientation="Vertical" Width="200" Margin="10,0,0,0">
<TextBlock Text="Edit" FontWeight="Bold"/>
<TextBlock Text="Name:"/>
<TextBox Text="{Binding Path=SelectedItem.Name, ElementName=dg, ValidatesOnDataErrors=True}" />
<TextBlock Text="Address:"/>
<TextBox Text="{Binding Path=SelectedItem.Address, ElementName=dg, ValidatesOnDataErrors=True}" />
</StackPanel>
</StackPanel>
</TabItem>
<TabItem Header="Tab 2">
<TextBlock Text="The user should be able to navigate to this tab even if there are validation errors" TextWrapping="Wrap" />
</TabItem>
</TabControl>
</Window>
And here is the code behind:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel;
namespace WpfValidationTest
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
List<Person> persons = new List<Person>()
{
new Person(){Name="John Doe", Address="My street 203"},
new Person(){Name="Jane Doe", Address="Your street 43"}
};
dg.ItemsSource = persons;
}
}
public class Person : INotifyPropertyChanged, IDataErrorInfo
{
public event PropertyChangedEventHandler PropertyChanged;
public string Error
{
get { throw new NotImplementedException(); }
}
public string this[string columnName]
{
get
{
switch (columnName)
{
case "Name":
if (string.IsNullOrEmpty(Name))
return "Name must be entered";
break;
case "Address":
if (string.IsNullOrEmpty(Address))
return "Address must be entered";
break;
}
return null;
}
}
private string _name;
public string Name
{
get { return _name; }
set
{
_name = value;
NotifyPropertyChanged("Name");
}
}
private string _address;
public string Address
{
get { return _address; }
set
{
_address = value;
NotifyPropertyChanged("Address");
}
}
private void NotifyPropertyChanged(string propName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
}
You can use a trigger to disable the control
<Style x:Key="disableOnValidation"
BasedOn="{StaticResource {x:Type DataGrid}}"
TargetType="{x:Type DataGrid}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=nameTextBox, Path=Validation.HasError}" Value="True">
<Setter Propert="IsEnabled" Value="False" />
</DataTrigger>
<DataTrigger Binding="{Binding ElementName=addressTextbox, Path=Validation.HasError}" Value="True">
<Setter Propert="IsEnabled" Value="False" />
</DataTrigger>
</Style.Triggers>
</Style>
I just noticed some strange behaviour of WPF's TreeView. I added both ItemContainerStyle to bind to "IsSelected" of my ViewModel and an ItemsTemplated for custom display of my data. But now the user cannot change the selected node anymore. For testing purposes I created a similar UI using ListView and Expander. This version works as excepted. Any tips why TreeView does fail?
<TreeView ItemsSource="{Binding ElementName=frame, Path=list}">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" >
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate>
<TreeViewItem Header="{Binding}">
<TextBlock Text="{Binding Path= Item.SomeData}"/>
</TreeViewItem>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
EDIT: My data are not hierachical. I just want to get the "collapse" feature on displaying a list. Item.SomeData is not a list. Display of data is as desired. Only selection by mouse fails!
alt text http://img682.imageshack.us/img682/3702/bildy.png
TreeViews work differently. The Items inside a HierarchicalDataTemplate are TreeViewItems and any control you specify inside the HierarchicalDataTemplate will function as its Header. So, basically you are specifying that the Items in your TreeView are TreeViewItems with TreeViewItems as their headers! Instead try this:
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<Label Content="{Binding}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path= Item.SomeData}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
EDIT: I could not reproduce a DataSource that produces the properties you want to bind to, so I wrote some simple code of my own that shows how it all works. Hopefully you will be able to adapt it to your needs:
<TreeView ItemsSource="{Binding}" Name="Tree">
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}" >
<Setter Property="IsSelected" Value="{Binding Mode=TwoWay, Path=IsSelected}"/>
</Style>
</TreeView.ItemContainerStyle>
<TreeView.ItemTemplate>
<HierarchicalDataTemplate ItemsSource="{Binding Items}">
<Label Content="{Binding Name}"/>
<HierarchicalDataTemplate.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Path= SomeData}"/>
</DataTemplate>
</HierarchicalDataTemplate.ItemTemplate>
</HierarchicalDataTemplate>
</TreeView.ItemTemplate>
</TreeView>
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows;
namespace TreeViewSpike
{
public partial class Window1 : Window
{
public Window1()
{
InitializeComponent();
List = new List<ItemList>
{
new ItemList
{
Name = "MyList",
Items = new List<Item> {new Item("1"),
new Item("2")}
},
new ItemList
{
Name = "MySecondList",
Items = new List<Item> {new Item("3"),
new Item("4")}
}
};
Tree.DataContext = List;
List[1].IsSelected = true;
}
public List<ItemList> List { get; set; }
}
public class ItemList: INotifyPropertyChanged
{
public string Name{ get; set;}
private bool _isSelected;
public bool IsSelected
{
get { return _isSelected; }
set
{
_isSelected = value;
if (PropertyChanged != null)
PropertyChanged(this,
new PropertyChangedEventArgs("IsSelected"));
if(_isSelected)
MessageBox.Show(Name + " selected");
}
}
public List<Item> Items { get; set; }
public event PropertyChangedEventHandler PropertyChanged;
}
public class Item
{
public string SomeData { get; set; }
public Item(string data)
{
SomeData = data;
}
}
}
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 wrong of if that has changed with .NET 4.0 (I am using the latest WPFToolkit from CodePlex now).
I am trying to use the DataGrid for a time sheet presentation where the day's date should be part of the header text (ie, "Sun, Nov 01"), and I have the following in my XAML:
<dg:DataGrid.Columns>
<dg:DataGridTextColumn Header="Description" Width="Auto" Binding="{Binding Description}" IsReadOnly="True"/>
<dg:DataGridTextColumn Header="Mon" Width="50" Binding="{Binding Allocations[0].Amount}" />
... every other day of the week ....
<dg:DataGridTextColumn Header="Sun" Width="50" Binding="{Binding Allocations[6].Amount}" />
<dg:DataGridTextColumn Header="Total" MinWidth="50" Binding="{Binding TotalAllocatedAmount}" IsReadOnly="True" />
</dg:DataGrid.Columns>
I'd like to use the same AllocationViewModel I am using for data (ie, "{Binding Allocations[0].Amount}" and bind it's DisplayName property to the header text. Can someone show me how to do that? If I have to use a static resource, how can I get the DataContext in there?
EDIT ---------------- PREFERRED WORK-AROUND
Josh Smith had posted about a DataContextSpy awhile back, and it is the cleanest workaround I have come across to this problem. Here is the class that makes it work:
/// <summary>
/// Workaround to enable <see cref="DataContext"/> bindings in situations where the DataContext is not redily available.
/// </summary>
/// <remarks>http://blogs.infragistics.com/blogs/josh_smith/archive/2008/06/26/data-binding-the-isvisible-property-of-contextualtabgroup.aspx</remarks>
public class DataContextSpy : Freezable
{
public DataContextSpy()
{
// This binding allows the spy to inherit a DataContext.
BindingOperations.SetBinding(this, DataContextProperty, new Binding());
}
public object DataContext
{
get { return GetValue(DataContextProperty); }
set { SetValue(DataContextProperty, value); }
}
// Borrow the DataContext dependency property from FrameworkElement.
public static readonly DependencyProperty DataContextProperty = FrameworkElement
.DataContextProperty.AddOwner(typeof (DataContextSpy));
protected override Freezable CreateInstanceCore()
{
// We are required to override this abstract method.
throw new NotImplementedException();
}
}
With this in place, I can hijack the DC I need in xaml:
<dg:DataGrid.Resources>
<behavior:DataContextSpy x:Key="spy" DataContext="{Binding Allocations}" />
</dg:DataGrid.Resources>
And then apply as needed via binding:
<dg:DataGridTextColumn Header="{Binding Source={StaticResource spy}, Path=DataContext[0].DisplayName}"
Width="50" Binding="{Binding Allocations[0].Amount}" />
Suh-weet!
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.
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>
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.
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
}
BTW, in Silverlight (tested with SL 3.0) you can simply use the Header property as the DataContext for the ControlTemplate set via HeaderStyle (see my related question on SO).
I just tried this solution in WPF 3.5 using the WPF Toolkit DataGrid and it works!
**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.
an even better solution would be to set the binding in the header's style and pass the column as the header's dataContext... (or even better: to set up an object representing the header's dataContext and pass it)
see there for a way to do this :
How to set the DataContext on a DataGrid Column Header
#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.
I used it to populate the DataGrid Column Header.
Trick is in the binding mode. Its' "Mode" needs to be set to "OneWay". Otherwise it is no good.
Example:
<DataGridTextColumn Binding="{Binding RowData}" Header="{Binding Mode=OneWay, Source={StaticResource spy},Path=DataContext.HeaderText, FallbackValue= header text}"/>
I used a lowercase fallback value, and the value from DataContext was capitalized to assure me that the resource was not null. Also, the value from DataContext was only showing up for me at run time, during design time it displayed the fallback value. Hope this helps.