Search code examples
c#wpfxamldatagridtemplatecolumn

Datacontext not available in DataGridTemplateColumn


I have the following grid:

<DataGrid 
    x:Name="CandiesDataGrid" 
    ItemsSource="{Binding Candies}" 
    SelectedItem="{Binding SelectedCandy}">

    <i:Interaction.Triggers>
        <i:EventTrigger EventName="SelectionChanged">
            <i:InvokeCommandAction Command="{Binding CandySelectedCommand}"/>
        </i:EventTrigger>
    </i:Interaction.Triggers>

    <DataGrid.Columns>
        <DataGridTextColumn KeyboardNavigation.IsTabStop="False" IsReadOnly="True" Width="100" Header="{l:LocText Candy_Prop1}" Binding="{Binding CandyInfo.Name}"/>
        <DataGridTemplateColumn >
            <DataGridTemplateColumn.CellTemplate>
                <DataTemplate>
                    <CheckBox Name="IsConfirmed" Grid.Column="0"
                        Style="{StaticResource CandyCheckBox}"
                        IsChecked="{Binding IsConfirmed, Mode=TwoWay}"
                        Margin="-75 0 0 0"
                        Command="{Binding IsConfirmedCommand}">

                    </CheckBox>
                </DataTemplate>
            </DataGridTemplateColumn.CellTemplate>
        </DataGridTemplateColumn>

    </DataGrid.Columns>
</DataGrid>

My property uses the OnPropertyChanged. Not only it does not change the value of IsConfirmed but also does not executes the ICommand IsConfirmedCommand.

I searched on the internet and it seems DataGridTemplateColumn loses the ItemSource of the datagrid.

I did try to put RelativeSource in after the mode=TwoWay on my checkbox but it does not work.

Is there any way to have access to the ItemSource in my TemplateColumn?

EDIT:

//Properties
public ObservableCollection<Candy> Candies{ get; } = new ObservableCollection<Candy>();

public Candy SelectedCandy { get { return _selectedCandy; } set { SetProperty(ref _selectedCandy, value); } } //SetProperty acts as OnPropertyChanged

private Candy _selectedCandy;

//Constructor:
        public CandyClass()
        {
            IsConfirmedCommand = new DelegateCommand(IsConfirmedCommand_Execute);
        }

//Method
        private void IsConfirmedCommand_Execute()
        {
            //Doing something
        }

Solution

  • Inside your CellTemplate, the DataContext is the DataGrid row, whatever that may be (Candy in this case). So by default, that Candy instance will be the Source property of any Binding in that DataTemplate. That's where the binding will look for the property named in the Path (IsConfirmed and IsConfirmedCommand, in this case).

    That's what you want: You've got more than one row in the grid, and the row is what you care about in a cell, usually. That or the field: But very often a cell template will want to look at more than one field, so they give you the whole row.

    But in this case, you want to go back up and grab something off the parent viewmodel. Viewmodels have no natural parent/child hierarchy, though you could give them one if you wanted: Candy could have a Parent property that had reference to the viewmodel that owns the Candies collection. If you did, you could bind like this:

    Command="{Binding Parent.IsConfirmed}"
    

    But that's not common practice. I don't know if it's a particularly great idea or not.

    One reason we don't need to do that is we can tell the binding to use a different source instead. UI elements do have a natural parent/child hierarchy, and bindings can navigate it. If you’re doing things right, your parent viewmodel will be the DataContext of something up there somewhere.

    {Binding Path=DataContext.IsConfirmed, 
            RelativeSource={RelativeSource AncestorType=DataGrid}}
    

    "Walk the UI tree upwards until you find a DataGrid. That's your source. Now, once you have a source, find the source object's DataContext property, if any. If it's got a DataContext, take the value of DataContext and look on that object for some property called IsConfirmed."

    DataGrid has a DataContext property. Since your binding to Candies worked, we know that DataContext must be your class that has a Candies property. You assure me that class has IsConfirmed as well.

    Hence:

    <DataTemplate>
        <CheckBox 
            Style="{StaticResource CandyCheckBox}"
            IsChecked="{Binding DataContext.IsConfirmed, 
                RelativeSource={RelativeSource AncestorType=DataGrid}}"
            Margin="-75 0 0 0"
            Command="{Binding DataContext.IsConfirmedCommand,
                RelativeSource={RelativeSource AncestorType=DataGrid}}"
            />
    </DataTemplate>