Search code examples
c#wpfdatagridtemplatecolumn

How to detect a DataContext of DataGridRow for DataGridTemplateColumn?


We are working on WPF app development. And one of our dialogs has a DataGrid with custom columns. We have one custom column class that inherits a DataGridTemplateColumn:

public class MultiEditComboBoxColumn : DataGridTemplateColumn
{
    public IEnumerable ItemsSource
    {
        get { return (IEnumerable)GetValue(ItemsSourceProperty); }
        set { SetValue(ItemsSourceProperty, value); }
    }

    public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", 
        typeof(IEnumerable), 
        typeof(MultiEditComboBoxColumn), 
        new PropertyMetadata(new PropertyChangedCallback(OnItemsSourcePropertyChanged)));
}

This column generates a dynamic template with combobox. Also, it has some customs events and a lot of custom backend logic. So that's why we don't use a default DataGridComboboxColumn.

To set some collection for this dynamic combobox we created a Dependency Property "ItemsSource" and bind it to "ItemsSource" property of the internal dynamic combobox.

So when we have some DataGrid and use our MultiEditComboBoxColumn inside - we can pass some collection to this property.

In our project, we have one dialog "MainView" and "MainViewModel" for it. This dialog has a DataGrid with MultiEditComboBoxColumn column. "MainViewModel" has some collection that are related to DataGrid. This collection contains a few objects of "PointViewModel" type that represent each row in DataGrid. "PointViewModel" has the property "AvailablePointTypes" - a list of strings. Each "PointViewModel" can have different values in this list. And I want to set this list to "ItemsSource" property of MultiEditComboBoxColumn:

public class PointViewModel : ViewModelBase
{
    public List<string> AvailablePointTypes { get; }

    public PointViewModel()
    {
        AvailablePointTypes = GetAvailablePointTypes();
    }

    private List<string> GetAvailablePointTypes()
    {
        // Get a list of available point types with different values for each PointViewModel object.
        ...
    }
}


public class MainViewModel : ViewModelBase
{
    public List<PointViewModel> Points { get; }

    public MainViewModel()
    {
        Points = ReadPoints();
    }

    private List<PointViewModel> ReadPoints()
    {
        // Get a collection of points to display in DataGrid.
        ...
    }
}

And here is how I try to set this collection to our custom column:

<DataGrid ItemsSource="{Binding Points}">
    <DataGrid.Columns>
        // Other columns ...
        <MultiEditComboBoxColumn ItemsSource="{Binding AvailablePointTypes}"/>
    </DataGrid.Columns>
</DataGrid>

But it doesn't work. When I run the app I see an error:

Cannot find governing FrameworkElement or FrameworkContentElement for the target element. BindingExpression:Path=AvailablePointTypes; DataItem=null; target element is 'MultiEditComboBoxColumn' (HashCode=29829272); target property is 'ItemsSource' (type 'IEnumerable')

It seems that it is unable to detect a DataContext of row in this column where I clicked on dynamic combobox. So that's why I see my combobox in the UI but it's always empty.

How can I fix that correctly?


Solution

  • A DataGridColumn is not part of the visual tree and doesn't inherit any DataContext.

    A common solution to this issue is to use a Freezable that captures the DataContext as explained and exemplified here:

    <DataGridTextColumn ... Visibility="{Binding Data.SomeProperty, Source={StaticResource proxy}}"/>
    

    Or you could create a default CellTemplate that simply binds a ComboBox to some property that you specify dynamically.