102

I've got a DataGrid with a row that has an image. This image is bound with a trigger to a certain state. When the state changes I want to change the image.

The template itself is set on the HeaderStyle of a DataGridTemplateColumn. This template has some bindings. The first binding Day shows what day it is and the State changes the image with a trigger.

These properties are set in a ViewModel.

Properties:

public class HeaderItem
{
    public string Day { get; set; }
    public ValidationStatus State { get; set; }
}

this.HeaderItems = new ObservableCollection<HeaderItem>();
for (int i = 1; i < 15; i++)
{
    this.HeaderItems.Add(new HeaderItem()
    {
        Day = i.ToString(),
        State = ValidationStatus.Nieuw,
    });
}

Datagrid:

<DataGrid x:Name="PersoneelsPrestatiesDataGrid" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
              AutoGenerateColumns="False" SelectionMode="Single" ItemsSource="{Binding CaregiverPerformances}" FrozenColumnCount="1" >

    <DataGridTemplateColumn HeaderStyle="{StaticResource headerCenterAlignment}" Header="{Binding HeaderItems[1]}" Width="50">
        <DataGridTemplateColumn.CellEditingTemplate>
            <DataTemplate>
                <TextBox Text="{ Binding Performances[1].Duration,Converter={StaticResource timeSpanConverter},Mode=TwoWay}"/>
            </DataTemplate>
        </DataGridTemplateColumn.CellEditingTemplate>

        <DataGridTemplateColumn.CellTemplate>
            <DataTemplate>
                <TextBlock TextAlignment="Center" Text="{ Binding Performances[1].Duration,Converter={StaticResource timeSpanConverter}}"/>
            </DataTemplate>
        </DataGridTemplateColumn.CellTemplate>
    </DataGridTemplateColumn> 
</DataGrid>

Datagrid HeaderStyleTemplate:

<Style x:Key="headerCenterAlignment" TargetType="{x:Type DataGridColumnHeader}">
    <Setter Property="HorizontalContentAlignment" Value="Center"/>

    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type DataGridColumnHeader}">
                <Grid>
                    <Grid.RowDefinitions>
                        <RowDefinition />
                        <RowDefinition />
                    </Grid.RowDefinitions>

                    <TextBlock Grid.Row="0" Text="{Binding Day}" />
                    <Image x:Name="imageValidation" Grid.Row="1" Width="16" Height="16" Source="{StaticResource imgBevestigd}" />
                </Grid>

                <ControlTemplate.Triggers>
                    <MultiDataTrigger >
                        <MultiDataTrigger.Conditions>
                            <Condition Binding="{Binding State}" Value="Nieuw"/>                                 
                        </MultiDataTrigger.Conditions>
                        <Setter TargetName="imageValidation" Property="Source" Value="{StaticResource imgGeenStatus}"/>
                    </MultiDataTrigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

Now when I startup the project the images doesn't show and I get this error:

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=HeaderItems[0]; DataItem=null; target element is 'DataGridTemplateColumn' (HashCode=26950454); target property is 'Header' (type 'Object')

Why is this error showing?

Eliahu Aaron
  • 4,103
  • 5
  • 27
  • 37
KDP
  • 1,377
  • 3
  • 11
  • 13
  • 4
    I checked above answered solution, but it does not work in my case. When I switch to another solution as in link http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/. The idea is the same as solution, instead of using FrameworkElement, they created another class. Then it works for me. – leo5th Oct 07 '15 at 09:27
  • For others ending up here by searching for the error message: The answer of this similar question helped me to solve the problem fairly easily http://stackoverflow.com/a/18657986/4961688 – Tim Pohlmann Feb 06 '17 at 12:41

3 Answers3

182

Sadly any DataGridColumn hosted under DataGrid.Columns is not part of Visual tree and therefore not connected to the data context of the datagrid. So bindings do not work with their properties such as Visibility or Header etc (although these properties are valid dependency properties!).

Now you may wonder how is that possible? Isn't their Binding property supposed to be bound to the data context? Well it simply is a hack. The binding does not really work. It is actually the datagrid cells that copy / clone this binding object and use it for displaying their own contents!

So now back to solving your issue, I assume that HeaderItems is a property of the object that is set as the DataContext of your parent View. We can connect the DataContext of the view to any DataGridColumn via something we call a ProxyElement.

The example below illustrates how to connect a logical child such as ContextMenu or DataGridColumn to the parent View's DataContext

 <Window x:Class="WpfApplicationMultiThreading.Window5"
         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"        
         xmlns:vb="http://schemas.microsoft.com/wpf/2008/toolkit"
         Title="Window5" Height="300" Width="300" >
  <Grid x:Name="MyGrid">
    <Grid.Resources>
        <FrameworkElement x:Key="ProxyElement" DataContext="{Binding}"/>
    </Grid.Resources>
    <Grid.DataContext>
         <TextBlock Text="Text Column Header" Tag="Tag Columne Header"/>
    </Grid.DataContext>
    <ContentControl Visibility="Collapsed"
             Content="{StaticResource ProxyElement}"/>
    <vb:DataGrid AutoGenerateColumns="False" x:Name="MyDataGrid">
        <vb:DataGrid.ItemsSource>
            <x:Array Type="{x:Type TextBlock}">
                <TextBlock Text="1" Tag="1.1"/>
                <TextBlock Text="2" Tag="1.2"/>
                <TextBlock Text="3" Tag="2.1"/>
                <TextBlock Text="4" Tag="2.2"/>
            </x:Array>
        </vb:DataGrid.ItemsSource>
        <vb:DataGrid.Columns>
            <vb:DataGridTextColumn
                       Header="{Binding DataContext.Text,
                                     Source={StaticResource ProxyElement}}"
                       Binding="{Binding Text}"/>
            <vb:DataGridTextColumn
                       Header="{Binding DataContext.Tag,
                                     Source={StaticResource ProxyElement}}"
                       Binding="{Binding Tag}"/>
        </vb:DataGrid.Columns>
    </vb:DataGrid>
  </Grid>
</Window>

The view above encountered the same binding error that you have found if I did not have implemented the ProxyElement hack. The ProxyElement is any FrameworkElement that steals the DataContext from the main View and offers it to the logical child such as ContextMenu or DataGridColumn. For that it must be hosted as a Content into an invisible ContentControl which is under the same View.

I hope this guides you in correct direction.

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
WPF-it
  • 19,625
  • 8
  • 55
  • 71
  • 32
    I find having to use this hacky proxy stuff really disapointing but I cannot find another way to achieve the same functionality otherwise... Thank you. – Alex Hope O'Connor Nov 14 '11 at 02:24
  • 2
    This didn't work for me but after reading Josh Smith's article about Virtual Branches I tried adding the OneWayToSource binding on my root control to set the "ProxyElement" DataContext and that worked. – jpierson Nov 12 '12 at 16:50
  • @jpierson, are you using `Silverlight`? Coz it works for me in `WPF`. For silverlight the implementation is different. – WPF-it Nov 15 '12 at 08:22
  • I'm using WPF but I'm using .NET 3.5 if that matters. – jpierson Dec 05 '12 at 05:36
  • 1
    Nope. The solution above fits for .NET 3.5 very well. – WPF-it Dec 05 '12 at 12:55
  • 1
    This answer is old, but still useful against .NET 4.0. Lots of the answers around involving copying the DataContext to the column don't seem to work. I needed to show/hide a column depending on a view model property and this solution worked well. And with no code behind won't cause a diplomatic incident in the code review. – James_UK_DEV Feb 06 '14 at 10:13
  • I can't tell if this is the DataGrid in WPFToolkit or the one built-in to .NET 4.0, but I'm using a DataGridTextColumn and the Visibility binding works fine for me without using this workaround. However, at runtime I get the same warning as KDP and can't figure out how to get rid of it. – Dave Jun 11 '14 at 13:59
  • 3
    FYI Context menu is not the same and has a non-proxy work around. Context menu has an exposed property `Parent` whereas the `DataGridTextColumn` does not expose its `DataGridOwner` property. See how a context items binding is accomplished via RelativeSource binding in my answer [Context Menu Binding to Parent Window's Datacontext](http://stackoverflow.com/questions/26359091/context-menu-binding-to-parent-windows-datacontext/26383463#26383463) – ΩmegaMan Oct 16 '14 at 12:10
  • 1
    In your solution yopu have specified ContextMenu` under a `FrameworkElement` in XAML.... But MSDN says... "Parent may be a null reference (Nothing in Visual Basic) in cases where an element was instantiated, but is not attached to any **logical** tree that eventually connects to the page level root element, or the application object." ... Due to this if your context menu is a WPF `Resource` it wont have `Parent` but will still have a `DataContext` due to `ProxyElement`. So the point is `ProxyElement` works in Popups, DataGridColumns, `ContextMenu` etc. – WPF-it Oct 17 '14 at 06:33
  • +1 :) this is golden (even as a hack) - this works on very different scenarios described by @WPF-it, as long as the error is the same - e.g. in one case I used a proxy inside a `ListBox.ItemTemplate` as this is also typical for `ItemsControl` (it involves a very custom binding scenario - flags generated checkbox list) - great and thanks, you learn something every day. – NSGaga-mostly-inactive Apr 13 '16 at 12:10
  • 1
    This answer did not work for me. Thanks @leo5th Here's a similar solution that works: http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/ – Karmacon Nov 03 '16 at 17:56
  • Man.. what a *hack*... it worked when I had to bind `CollectionContainer.Collection`, thanks – JobaDiniz Jan 31 '17 at 11:18
19

A slightly shorter alternative to using a StaticResource as in the accepted answer is x:Reference:

<StackPanel>

    <!--Set the DataContext here if you do not want to inherit the parent one-->
    <FrameworkElement x:Name="ProxyElement" Visibility="Collapsed"/>

    <DataGrid>
        <DataGrid.Columns>
            <DataGridTextColumn
                Header="{Binding DataContext.Whatever, Source={x:Reference ProxyElement}}"
                Binding="{Binding ...}" />
        </DataGrid.Columns>
    </DataGrid>

</StackPanel>

The main advantage of this is: if you already have an element which is not a DataGrid's ancestor (i.e. not the StackPanel in the example above), you can just give it a name and use it as the x:Reference instead, hence not needing to define any dummy FrameworkElement at all.

If you try referencing an ancestor, you will get a XamlParseException at run-time due to a cyclical dependency.

FernAndr
  • 1,448
  • 1
  • 14
  • 26
0

The way without a proxy is to set bindings in the constructor:

var i = 0;
var converter = new BooleanToVisibilityConverter();
foreach(var column in DataGrid.Columns)
{
    BindingOperations.SetBinding(column, DataGridColumn.VisibilityProperty, new Binding($"Columns[{i++}].IsSelected")
    { 
        Source = ViewModel,
        Converter = converter,
    });
}
Konstantin S.
  • 1,307
  • 14
  • 19