What is my goal:
I want to create a custom template for a column header in DataGrid - DataGridColumnHeader. This template is supposed to fetch data from the attached property (in a real program, these will be properties such as: indicator whether the column is filtered, column title rotation angle, and so on)
Since DataGrid columns don't inherit DataContext I use method with BindingProxy class (description here: https://thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/).
Everything looks OK, but something is not right - if I use the attachet property in the DataGridColumnHeader template, notifications about changing this property do not work.
In the example below, I have bound the same property in different ways and everything works except for the DataGridColumnHeader template.
Does anyone know what I'm doing wrong and how to fix it?
Reploduce problem:
The project is written in .Net Framework 4.7.1 To reproduce the problem, please create a new WPF project named "MyApp" for .Net Framework and add the following files to it:
File: MainWindow.xaml - window definition
<Window x:Class="MyApp.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:MyApp"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="950">
<Window.Resources>
<ResourceDictionary>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="Styles.xaml" />
</ResourceDictionary.MergedDictionaries>
<!--Proxy to "transfer" DataContext to -->
<local:BindingProxy x:Key="proxy" MyData="{Binding}" />
</ResourceDictionary>
</Window.Resources>
<StackPanel Margin="10">
<CheckBox IsChecked="{Binding SomeIndicator}" Content="- regular binding (without proxy object) - it works"/>
<CheckBox IsChecked="{Binding MyData.SomeIndicator, Source={StaticResource proxy}}" Content="- binding via proxy object - it works"/>
<CheckBox local:AttProp.MyAttProp="{Binding MyData.SomeIndicator, Source={StaticResource proxy}}"
Style="{StaticResource SimpleCheckBoxStyle}"/>
<DataGrid ItemsSource="{Binding SomeData}" AutoGenerateColumns="False" Margin="0,20,0,20">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding}">
<DataGridTextColumn.Header>
<CheckBox IsChecked="{Binding MyData.SomeIndicator, Source={StaticResource proxy}}"
Content="- binding via proxy object (DataGrid header) - it works"/>
</DataGridTextColumn.Header>
</DataGridTextColumn>
<DataGridTextColumn Binding="{Binding}"
HeaderStyle="{StaticResource SimpleHeaderStyle}"
local:AttProp.MyAttProp="{Binding MyData.SomeIndicator, Source={StaticResource proxy}}"/>
</DataGrid.Columns>
</DataGrid>
<WrapPanel HorizontalAlignment="Right">
<Label Content="Use this button, please ===>" Foreground="Red"/>
<Button Content="Toggle False/True" Command="{Binding ButtonClick}" Height="30" Width="150"/>
</WrapPanel>
</StackPanel>
</Window>
File Styles.xaml - extremely simplified styles
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyApp">
<!--Custom style for DataGridColumnHeader-->
<Style x:Key="SimpleHeaderStyle" TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
<CheckBox IsChecked="{TemplateBinding local:AttProp.MyAttProp}" Foreground="Red"
Content="- binding via attached property, proxy object and custom DataGridColumnHeader style - it doesn't work :( "
VerticalAlignment="Center"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!--Custom style for CheckBox-->
<Style x:Key="SimpleCheckBoxStyle" TargetType="{x:Type CheckBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type CheckBox}">
<CheckBox IsChecked="{TemplateBinding local:AttProp.MyAttProp }"
Content="- binding via attached property, proxy object and custom CheckBox style - it works"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary>
File AttProp.cs - definition my attached property
using System.Windows;
namespace MyApp
{
public class AttProp
{
//Create attached property
public static readonly DependencyProperty MyAttPropProperty
= DependencyProperty.RegisterAttached("MyAttProp", typeof(bool), typeof(AttProp), new PropertyMetadata(default(bool)));
public static void SetMyAttProp(DependencyObject target, bool value)=>target.SetValue(MyAttPropProperty, value);
public static bool GetMyAttProp(DependencyObject target) => (bool)target.GetValue(MyAttPropProperty);
}
}
File BindingProxy.cs - definition of proxy class
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
namespace MyApp
{
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object MyData
{
get { return (object)GetValue(MyDataProperty); }
set { SetValue(MyDataProperty, value); }
}
public static readonly DependencyProperty MyDataProperty =
DependencyProperty.Register("MyData", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
}
File ViewModel.cs - view model :)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows.Input;
namespace MyApp
{
public class ViewModel : ICommand, INotifyPropertyChanged
{
#region Properties
private bool _someIndicator = false;
public bool SomeIndicator
{
get => _someIndicator;
set
{
_someIndicator = value;
OnPropertyChanged();
}
}
public List<string> SomeData { get; set; } = new List<string>() { "AAA", "BBB", "CCC", "DDD" };
public ICommand ButtonClick { get; set; }
#endregion
#region Constructor
public ViewModel() => ButtonClick = this;
#endregion
#region INotifyPropertyChanged interface implementation
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged([CallerMemberName] String propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
#endregion
#region ICommand interface implementation
public event EventHandler CanExecuteChanged;
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => SomeIndicator =! SomeIndicator;
#endregion
}
}
I tried various ways of binding data in the template, but none of them brought the expected effect - changing the property does not affect the change of the checkbox in the header of the column to which the template is applied.