CoderForHire
CoderForHire

Reputation: 439

WPF DataGrid with DataGrid in RowDetailsTemplate

My previous post about detecting property changes in the VM wasn't in depth enough, so I'm posting this

I have a grid of Jobs. Each job can have one or more employees.

The DataGrid's RowDetailsTemplate contains another grid to show the employees. So to parent grid is bound to a list of Jobs. The inner grid is bound to a list of Employees that is on the Job model.

The Job Model:

public class Job : _Base
{
    private string _JobName = string.Empty;
    public string JobName
    {
        get { return _JobName; }
        set 
        {
            if (_JobName != value)
            {
                _JobName = value;
                RaisePropertyChanged("JobName");
            }
        }
    }

    private string _JobNumber = string.Empty;
    public string JobNumber
    {
        get { return _JobNumber; }
        set
        {
            if (_JobNumber != value)
            {
                _JobNumber = value;
                RaisePropertyChanged("JobNumber");
            }
        }
    }

    private ObservableCollection<Employee> _Employees;
    public ObservableCollection<Employee> Employees
    {
        get { return _Employees; }
        set
        {
            if (_Employees != value)
            {
                if (_Employees != value)
                {
                    _Employees = value;
                    RaisePropertyChanged("Employees");
                }
            }
        }
    }

    private Employee _SelectedEmployee;
    public Employee SelectedEmployee
    {
        get { return _SelectedEmployee; }
        set
        {
            if (_SelectedEmployee != value)
            {
                if (_SelectedEmployee != value)
                {
                    _SelectedEmployee = value;
                    RaisePropertyChanged("SelectedEmployee");
                }
            }
        }
    }

    public Job()
    {
        Employees = new ObservableCollection<Employee>();
    }
}

The Employee model

public class Employee : _Base
{
    private string _EmployeeName = string.Empty;
    public string EmployeeName
    {
        get { return _EmployeeName; }
        set
        {
            if (_EmployeeName != value)
            {
                _EmployeeName = value;
                RaisePropertyChanged("EmployeeName");
            }
        }
    }

    private bool _IsChecked = false;
    public bool IsChecked
    {
        get { return _IsChecked; }
        set
        {
            if (_IsChecked != value)
            {
                _IsChecked = value;
                RaisePropertyChanged("IsChecked");
            }
        }
    }
}        

The XAML

<DataGrid ItemsSource="{Binding Jobs}"
            SelectedItem="{Binding SelectedJob}"
            AutoGenerateColumns="False">

    <DataGrid.Columns>
        <DataGridTextColumn Header="Job Name" Binding="{Binding JobName}" />
        <DataGridTextColumn Header="Job Number" Binding="{Binding JobNumber}" />
    </DataGrid.Columns>

    <DataGrid.RowDetailsTemplate>
        <DataTemplate>

            <StackPanel Orientation="Vertical">

                <DataGrid ItemsSource="{Binding Employees}"
                            SelectedItem="{Binding SelectedEmployee}"
                            AutoGenerateColumns="False">

                    <DataGrid.Columns>
                        <DataGridCheckBoxColumn Binding="{Binding IsChecked}"/>
                        <DataGridTextColumn Binding="{Binding EmployeeName}"/>
                    </DataGrid.Columns>

                </DataGrid>

                <Button Margin="5"
                        Height="23"
                        Width="75"
                        HorizontalAlignment="Left"
                        Content="Remove"/>

            </StackPanel>


        </DataTemplate>
    </DataGrid.RowDetailsTemplate>

</DataGrid>

The MainWindowViewModel

public class MainWindowViewModel : _Base
{
    private ObservableCollection<Job> _Jobs;
    public ObservableCollection<Job> Jobs
    {
        get { return _Jobs; }
        set 
        {
            if (_Jobs != value)
            {
                if (_Jobs != value)
                {
                    _Jobs = value;
                    RaisePropertyChanged("Jobs");
                }
            }
        }
    }

    private Job _SelectedJob;
    public Job SelectedJob
    {
        get { return _SelectedJob; }
        set
        {
            if (_SelectedJob != value)
            {
                if (_SelectedJob != value)
                {
                    _SelectedJob = value;
                    RaisePropertyChanged("SelectedJob");
                }
            }
        }
    }

    public MainWindowViewModel()
    {
        this.PropertyChanged += new PropertyChangedEventHandler(MainWindowViewModel_PropertyChanged);
    }

    void MainWindowViewModel_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e)
    {
        if (e.PropertyName.Trim().ToLower() == "ischecked")
        {
            int x = 1;
        }
    }
}

I have a couple of questions:

1) The SelectedEmployee property on the Job model does not fire when I click an employee in the inner grid.

2) The MainWindowViewModel_PropertyChanged does not fire when an employee is selected.

3) Notice the button below the inner grid. How do I bind its command to MainWindowVM?

Upvotes: 1

Views: 3946

Answers (1)

Nitin Purohit
Nitin Purohit

Reputation: 18580

  1. As you have DataGridinside DataGrid's row, so the above DataGrid is somehow eating up the selectionchange for the inner DataGrid. To solve this, you will need to forcefully update the binding source for the child DataGrid. For doing so capture the SelectionChanged event for the inner DataGrid and in the handler do the following.

     private void DataGrid_SelectionChanged(object sender, SelectionChangedEventArgs e)
     {
          DataGrid grid = e.OriginalSource as DataGrid;
          var expression = grid.GetBindingExpression(DataGrid.SelectedItemProperty);
          expression.UpdateSource();
    
      }
    
  2. MainwindowVM does not have any ischecked property thats why its propertychanged for that property is not firing.

  3. To solve this you need to do couple of things. The problem is DataGrid cell and rows does not inherit the DataContext as they don't come under its visual tree. So to solve this you will have to use the BindingProxy to take the windows DataContext to your button in rowdetails of DataGrid

Define Binding Proxy class as below:

    public class MyBindingProxy : Freezable
    {
        public static readonly DependencyProperty BindingDataProperty =
            DependencyProperty.Register("BindingData", typeof(object),
            typeof(MyBindingProxy), new UIPropertyMetadata(null));

        protected override Freezable CreateInstanceCore()
        {
            return new MyBindingProxy();
        }

        public object BindingData
        {
            get { return (object)GetValue(BindingDataProperty); }
            set { SetValue(BindingDataProperty, value); }
        }

    }

Now in the resources of your window (where DataGrid is) create the instance of proxy and set the BindingData to the DataContext of the Window i.e MainWindowViewModel as:

<Window.Resources>
    <local:MyBindingProxy x:Key="myproxy" BindingData="{Binding}" />
</Window.Resources>

Now just set command as below on the button:

<Button Margin="5"
   Height="23"
   Width="75"
   HorizontalAlignment="Left"
   Content="Remove"
   Command="{Binding BindingData.MyCommand, Source={StaticResource myproxy}}"/>

Upvotes: 1

Related Questions