Parth Shah
Parth Shah

Reputation: 2130

DataGridColumnHeader of a DataGrid defined in a DataTemplate for a ViewModel

I have successfully used ProxyElement to pass Data Context of my Data Grid to DataGridColumnHeaders. However, I am trying out something new and I just can't figure out what I am doing wrong over here.

Here is what I am trying to do: I am creating a UserControl and associating it to my ViewModel in my Resources file (see Resources.xaml code snippet below).

Resources.xaml:

<ResourceDictionary
    xmlns:myVm="clr-namespace:..."
    xmlns:myUserControl="clr-namespace:...">
    <DataTemplate DataType={x:Type myVm:DummyModel}">
        <myUserControl:DummyUserControl />
    </DataTemplate>
</ResourceDictionary>

Now in my UserControl, I have a DataGrid with a DataGridComboBoxColumn. I am trying to access my data context to set its item source and in the past I was able to do it using proxy element. This time however I am not able to do so. (See DummyUserControl.xaml code snippet below)

DummyUserControl.xaml:

<UserControl x:Class="Client.MyControl.DummyUserControl"
    ...>
    <UserControl.Resources>
        <FrameworkElement x:Key="ProxyElement" x:Name="ProxyElement" 
            DataContext="{Binding}" />
    </UserControl.Resources>

    <DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Products}">
        <DataGridComboBoxColumn
            Header="Company"
            ItemsSource="{Binding Path=DataContext.ProductCompanies, 
                Source={StaticResource ProxyElement}}"
            DisplayMemberPath="Name" SelectedValuePath="Id"
            SelectedValueBinding="{Binding CompanyId}" />
    </DataGrid>
</UserControl>

When I do this, my binding fails with the following message:

System.Windows.Data Error: 3 : Cannot find element that provides DataContext.
    BindingExpression:(no path); DataItem=null; target element is 'FrameworkElement'
    (Name='ProxyElement'); target property is 'DataContext' (type 'Object')

I have no idea what to do here. I remember reading that the datacontext for a datatemplate is automatically set, so I have no idea why the data context is null in this case. To prove it is null, I also tried setting the binding in the code-behind file and added a breakpoint to check its value (which was null).

Can anyone suggest what to do here?

Edit 1

I have also tried the following approaches:

All of the alternate bindings gave me same error as the error above.

Upvotes: 2

Views: 624

Answers (2)

Parth Shah
Parth Shah

Reputation: 2130

Here is a (working but not preferred) solution to this problem. You will realize why it is not preferred by the end of it.

The key to this problem is understanding what and how data context of a data template works. Whenever you define a Data Template for a View Model, the data context for the view that follows, whether it is a user control or just xaml itself, is the View Model! This shouldn't be a surprise to anyone.

But this will surprise people: if you specify a User Control, the Data Context of the User Control is not set during construction of the User Control! In other words, in the constructor of User Control, Data Context is going to be null. Furthermore, any XAML code that relies on the Data Context at construction time, which in this case was my FrameworkElement resource called ProxyElement got its DataContext set to null because it gets constructed at construction time of the User Control!

So when does the DataContext get set? Simply after the User Control is created. In pseudo code, this following describes the logic behind drawing a ViewModel:

  1. Draw ViewModel x;
  2. DataTemplate in ResourceDictionary says ViewModel x can be drawn using UserControl abc
  3. Let's create a new instance of UserControl abc
  4. Let's now assign the DataContext of abc to the ViewModel itself.
  5. Let's return the newly created instance of UserControl abc

So what do we do to solve the problem in this question?

UserControl.xaml:

<UserControl ...
    DataContextChanged="DaCoHasChanged">
    <UserControl.Resources>
        <FrameworkElement x:Key="ProxyElement" /> <!--Remove DataContext="{Binding}"-->
    </UserControl.Resources>
    <DataGrid ...>
        <DataGridComboBoxColumn
            ItemsSource="{Binding Path=DataContext.ProductCompanies, 
                Source={StaticSource ProxyElement}}"
            ... />
    </DataGrid>
</UserControl>

UserControl.xaml.cs:

private void DaCoHasChanged(object sender, DependencyPropertyChangedEventArgs e)
{
    var proxyElement = Resources["ProxyElement"] as FrameworkElement;
    proxyElement.DataContext = e.NewValue; // instead of e.NewValue, you could 
                                           // also say this.DataContext
}

I am trying to figure out a way of getting rid of the code in the code-behind file. But till then, if someone else hits this problem, then they might be able to get inspired from this solution.

Credit to the concept behind this solution goes to: How to set the DataContext for a View created in DataTemplate from ViewModel

Upvotes: 1

yo chauhan
yo chauhan

Reputation: 12295

Try this

<DataGrid AutoGenerateColumns="False" ItemsSource="{Binding Products}">
    <DataGridTemplateColumn Header="Company">
       <DataGridTemplateColumn.CellTemplate>
        <DataTemplate>
          <ComboBox 
        ItemsSource="{Binding Path="{Binding DataContext.ProductCompanies,RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}"
        DisplayMemberPath="Name" SelectedValuePath="Id"
        SelectedValueBinding="{Binding CompanyId}" />
  </DataTemplate>
  </DataGridTemplateColumn.CellTemplate>
  </DataGridTemplateColumn>
  </DataGrid.Columns>
  </DataGrid>

Now as I got your problem I think the problem is in DataGridComboBoxColumn I dont know why it not Binding using RelativeResource . Try it with DataGridTemplateColumn and you would not require any ProxyElement I hope this will help.

Upvotes: 0

Related Questions