2
votes

I have stripped down my problem to a minimal example: My DataGrid is defined by a ControlTemplate and in that ControlTemplate I cannot bind to anything from my ViewModel.

This is the simplified ViewModel:

public class ViewModel : PropertyChangedBase
{
    private string text1;

    public ViewModel()
    {
        this.text1 = "tx1!";
    }

    public string Text1
    {
        get { return text1; }
        set
        {
            if (value == text1) return;
            text1 = value;
            NotifyOfPropertyChange(() => Text1);
        }
    }
}

I tried to bind to Text1 with several methods. Most things I find via Google suggest FindAncestor but it did not work either:

<Window x:Class="DataGridTemplateBinding.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:local="clr-namespace:DataGridTemplateBinding"
        Title="MainWindow" Height="350" Width="525"
        x:Name="LayoutRoot"
        >
    <Window.Resources>
        <ControlTemplate x:Key="TableTemplate">
            <DataGrid>
                <DataGrid.Columns>
                    <DataGridTextColumn Header="Column 1" />
                    <!-- by ElementName -->
                    <DataGridTextColumn Header="{Binding DataContext.Text1, ElementName=LayoutRoot}" />
                    <DataGridTextColumn Header="{Binding Text1, ElementName=LayoutRoot}" />

                    <!-- by Ancestor type (Window) -->
                    <DataGridTextColumn Header="{Binding DataContext.Text1, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />
                    <DataGridTextColumn Header="{Binding Text1, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}}" />

                    <!-- by templated parent -->
                    <DataGridTextColumn Header="{Binding DataContext.Text1, RelativeSource={RelativeSource TemplatedParent}}" />
                    <DataGridTextColumn Header="{Binding Text1, RelativeSource={RelativeSource TemplatedParent}}" />

                    <!-- by self?? -->
                    <DataGridTextColumn Header="{Binding DataContext.Text1, RelativeSource={RelativeSource Self}}" />
                    <DataGridTextColumn Header="{Binding Text1, RelativeSource={RelativeSource Self}}" />

                    <DataGridTextColumn Header="{Binding Content.DataContext.Text1, RelativeSource={RelativeSource TemplatedParent}}" />
                    <DataGridTextColumn Header="{Binding Content.Text1, RelativeSource={RelativeSource TemplatedParent}}" />

                    <DataGridTextColumn Header="{Binding DataContext.Text1}" />
                    <DataGridTextColumn Header="{Binding Text1}" />

                    <DataGridTextColumn Header="Column End" />
                </DataGrid.Columns>
            </DataGrid>
        </ControlTemplate>
    </Window.Resources>
    <Window.DataContext>
        <local:ViewModel />
    </Window.DataContext>
    <Grid>
        <DataGrid Template="{StaticResource TableTemplate}" />
    </Grid>
</Window>

How can a column in the template bind to anything in the ViewModel? I cannot image this would not be possible.


For completeness, these are the errors logged to output. Their order does not correspond with the order of the XAML.

System.Windows.Data Error: 40 : BindingExpression path error: 'DataContext' property not found on 'object' ''DataGridTextColumn' (HashCode=28365320)'. BindingExpression:Path=DataContext.Text1; DataItem='DataGridTextColumn' (HashCode=28365320); target element is 'DataGridTextColumn' (HashCode=28365320); target property is 'Header' (type 'Object')

System.Windows.Data Error: 40 : BindingExpression path error: 'Text1' property not found on 'object' ''DataGridTextColumn' (HashCode=54811268)'. BindingExpression:Path=Text1; DataItem='DataGridTextColumn' (HashCode=54811268); target element is 'DataGridTextColumn' (HashCode=54811268); target property is 'Header' (type 'Object')

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=DataContext.Text1; DataItem=null; target element is 'DataGridTextColumn' (HashCode=55195297); target property is 'Header' (type 'Object')

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Text1; DataItem=null; target element is 'DataGridTextColumn' (HashCode=38845522); target property is 'Header' (type 'Object')

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=DataContext.Text1; DataItem=null; target element is 'DataGridTextColumn' (HashCode=2399386); target property is 'Header' (type 'Object')

System.Windows.Data Error: 4 : Cannot find source for binding with reference 'RelativeSource FindAncestor, AncestorType='System.Windows.Window', AncestorLevel='1''. BindingExpression:Path=Text1; DataItem=null; target element is 'DataGridTextColumn' (HashCode=31265986); target property is 'Header' (type 'Object')

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=DataContext.Text1; DataItem=null; target element is 'DataGridTextColumn' (HashCode=6837024); target property is 'Header' (type 'Object')

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Text1; DataItem=null; target element is 'DataGridTextColumn' (HashCode=17422861); target property is 'Header' (type 'Object')

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Content.DataContext.Text1; DataItem=null; target element is 'DataGridTextColumn' (HashCode=32669489); target property is 'Header' (type 'Object')

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Content.Text1; DataItem=null; target element is 'DataGridTextColumn' (HashCode=22361045); target property is 'Header' (type 'Object')

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=DataContext.Text1; DataItem=null; target element is 'DataGridTextColumn' (HashCode=7953856); target property is 'Header' (type 'Object')

System.Windows.Data Error: 2 : Cannot find governing FrameworkElement or FrameworkContentElement for target element. BindingExpression:Path=Text1; DataItem=null; target element is 'DataGridTextColumn' (HashCode=65911433); target property is 'Header' (type 'Object')

2
A simple {Binding Text1} should do it. Are you setting the DataContext from anywhere else?Mike Eason
So your DataGrid has in it's template another DataGrid ?Novitchi S
@MikeEason Unfortunately a simple {Binding Text1} does not do the trick. DataContext is only set for the Window. App.xaml & co. are standard, this is nearly all the code in the minimal example project.ZoolWay
@NovitchiS The template for the DataGrid is coming from a ResourceDictionary in the full-blown application. But even this minimalistic XAML does not work.ZoolWay

2 Answers

4
votes

Set the HeaderTemplate for DataGridTextColumn as shown below

<DataGridTextColumn  >
    <DataGridTextColumn.HeaderTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.Text1}"></TextBlock>
        </DataTemplate>
    </DataGridTextColumn.HeaderTemplate>
</DataGridTextColumn>
0
votes

With all the hints I found out that DataGridTextColumn is neither part of the visual tree nor of the logical tree. That should be the reason why ElementName and RelativeSource do not work. This answer regarding DataGridTextColumn explains that and gives a possible solution with Source and x:Reference: DataGridTextColumn Visibility Binding

The answer of @Anand Murali works but cannot be applied to Visibility - that was not part of the question because I minimalized that away. So I accept that one and will give more information in this one.

Using x:Reference for Visibility it can turn out like:

<DataGridTextColumn Binding="{Binding Data.OrderNumber}" Header="Order Number" Visibility="{Binding DataContext.ShowColumnOrderNumber, Source={x:Reference LayoutRoot}, Converter={StaticResource BooleanToVisibilityConverter}}" />

But: I my example I use a ControlTemplate and to have x:Reference to work this template must be within a .Resources XAML part in the same file and cannot be in an external ResourceDictionary. In the latter case the reference will not work because it cannot be resolved. (If someone knows a solution to that it would be welcome)