Celso Lívero
Celso Lívero

Reputation: 726

DataGrid and CheckBox - Changing a value of an external property when there is change in IsChecked (MVVM)

I have a problem, I need the Quantity property value to be added to or subtracted from the Amount property when the selection in the CheckBox is changed, this is done today when the selected row is changed

What am I doing wrong?

MainWindow.xaml

<Window.DataContext>
    <local:MainViewModel />
</Window.DataContext>
<Grid>
    <Grid.RowDefinitions>
        <RowDefinition Height="Auto"/>
        <RowDefinition Height="*"/>
    </Grid.RowDefinitions>

    <StackPanel Orientation="Horizontal" Margin="5">
        <Label Content="Sum of selected items: " />
        <TextBlock Text="{Binding Amount}"  Grid.Row="0" VerticalAlignment="Center" />
    </StackPanel>


    <DataGrid Grid.Row="1" Margin="5"
              ColumnWidth="*" 
              AutoGenerateColumns="False"
              SelectionMode="Single" 
              HorizontalContentAlignment="Center" 
              ItemsSource="{Binding MyDataList}"
              SelectedItem="{Binding MyData, UpdateSourceTrigger=PropertyChanged}"
              ScrollViewer.CanContentScroll="True"
              ScrollViewer.VerticalScrollBarVisibility="Auto"
              ScrollViewer.HorizontalScrollBarVisibility="Auto" >
        <DataGrid.Columns>
            <DataGridTemplateColumn Width="30">
                <DataGridTemplateColumn.Header>
                    <Grid>
                        <CheckBox IsChecked="{Binding DataContext.AllSelected, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}"/>
                    </Grid>
                </DataGridTemplateColumn.Header>
                <DataGridTemplateColumn.CellTemplate>
                    <DataTemplate>
                        <!--<Viewbox Height="25">-->
                        <CheckBox IsChecked="{Binding IsSelected, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True, Mode=TwoWay}"/>
                        <!--</Viewbox>-->
                    </DataTemplate>
                </DataGridTemplateColumn.CellTemplate>
            </DataGridTemplateColumn>
            <DataGridTextColumn Header="Description" Binding="{Binding Description}"  Width="*" IsReadOnly="True"/>
            <DataGridTextColumn Header="Quantity" Binding="{Binding Quantity, StringFormat=N2}" Width="160" IsReadOnly="True"/>

        </DataGrid.Columns>
    </DataGrid>
</Grid>

Classes (ViewModelBase implements INotifyPropertyChanged)

public class MyDataClass: ViewModelBase
{
    private bool _isSelected;

    public bool IsSelected
    {
        get => _isSelected;
        set
        {
            _isSelected = value;
            OnPropertyChanged();
        }
    }
    public string Description { get; set; }
    public double Quantity { get; set; }
}

MainViewModel

public class MainViewModel : ViewModelBase
{
    private ObservableCollection<MyDataClass> _myDataList;
    private MyDataClass _myData;

    public MainViewModel()
    {
        var list = new List<MyDataClass>
        {
            new MyDataClass { Description = "Item 01", Quantity = 20, IsSelected = false },
            new MyDataClass { Description = "Item 02", Quantity = 50, IsSelected = false },
            new MyDataClass { Description = "Item 03", Quantity = 60, IsSelected = false }
        };
        MyDataList = new ObservableCollection<MyDataClass>(list);
    }

    public ObservableCollection<MyDataClass> MyDataList
    {
        get => _myDataList;
        set
        {
            _myDataList = value;
            OnPropertyChanged();
            OnPropertyChanged(nameof(Amount));
        }
    }

    public MyDataClass MyData
    {
        get => _myData;
        set
        {
            _myData = value;
            OnPropertyChanged();
            OnPropertyChanged(nameof(Amount));
        }
    }

    public double Amount => MyDataList.Where(x=>x.IsSelected).Sum(x => x.Quantity);

}

Upvotes: 1

Views: 98

Answers (3)

cuongtd
cuongtd

Reputation: 3182

Another way is add a command to all checkboxes so whenever you click a checkbox it will fire the command

<CheckBox Command="{Binding RelativeSource={RelativeSource Mode=FindAncestor,
                                                     AncestorType={x:Type DataGrid}},
                                                     Path=DataContext.CheckCommand}" IsChecked="{Binding DataContext.AllSelected, RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, UpdateSourceTrigger=PropertyChanged, NotifyOnSourceUpdated=True}"/>

MainViewModel

    private ObservableCollection<MyDataClass> _myDataList;
    private MyDataClass _myData;

    public ICommand CheckCommand { get; set; }

    public MainViewModel()
    {
        var list = new List<MyDataClass>
            {
                new MyDataClass { Description = "Item 01", Quantity = 20, IsSelected = false },
                new MyDataClass { Description = "Item 02", Quantity = 50, IsSelected = false },
                new MyDataClass { Description = "Item 03", Quantity = 60, IsSelected = false }
            };
        MyDataList = new ObservableCollection<MyDataClass>(list);
        CheckCommand = new RelayCommand(()=> { Amount = MyDataList.Where(x => x.IsSelected).Sum(x => x.Quantity); });
    }

    public ObservableCollection<MyDataClass> MyDataList
    {
        get => _myDataList;
        set
        {
            _myDataList = value;
            OnPropertyChanged();
        }
    }

My RelayCommand class

 public class RelayCommand : ICommand
{

    private Action mAction;
    public event EventHandler CanExecuteChanged = (sender, e) => { };
    public RelayCommand(Action action)
    {
        mAction = action;
    }
    public bool CanExecute(object parameter)
    {
        return true;
    }
    public void Execute(object parameter)
    {
        mAction();
    }
}

Upvotes: 2

grek40
grek40

Reputation: 13438

You need to listen to MyDataClass.PropertyChanged for each item and if the IsSelected property changes, call OnPropertyChanged(nameof(Amount)).

public MainViewModel()
{
    var list = new List<MyDataClass>
    {
        new MyDataClass { Description = "Item 01", Quantity = 20, IsSelected = false },
        new MyDataClass { Description = "Item 02", Quantity = 50, IsSelected = false },
        new MyDataClass { Description = "Item 03", Quantity = 60, IsSelected = false }
    };

    foreach (var item in list)
    {
        item.PropertyChanged += OnItemPropertyChanged;
    }

    MyDataList = new ObservableCollection<MyDataClass>(list);
}

void OnItemPropertyChanged(object sender, PropertyChangedEventArgs e)
{
    if (e.PropertyName == "IsSelected") OnPropertyChanged(nameof(Amount));
}

Note the code might contain minor typos, I just wrote it in a plain text editor.

If you add/remove items dynamically, you need to handle the change notification subscriptions for those items (like @Marisa answered).

Upvotes: 1

Marisa
Marisa

Reputation: 792

You need to hook into the CollectionChanged event handler for the collection of data, then hook into the PropertyChanged for each item.

Note that you also need to change how you initialize the observable collection; initializing it like you did in your original sample means that the collection won't get the PropertyChanged event handlers, and so won't notify that the members of the collection have had a change applied to their properties.

public class MainViewModel : ViewModelBase
{
   private ObservableCollection<MyDataClass> _myDataList;

   public MainViewModel()
   {
      var list = new List<MyDataClass>
      {
         new MyDataClass {Description = "Item 01", Quantity = 20, IsSelected = false},
         new MyDataClass {Description = "Item 02", Quantity = 50, IsSelected = false},
         new MyDataClass {Description = "Item 03", Quantity = 60, IsSelected = false}
      };

      MyDataList = new ObservableCollection<MyDataClass>();

      foreach (var myDataClass in list)
      {
         MyDataList.Add(myDataClass);
      }
   }

   public ObservableCollection<MyDataClass> MyDataList
   {
      get => _myDataList;
      set
      {
         _myDataList = value;
         MyDataList.CollectionChanged += MyDataList_CollectionChanged; // sets up the collection so it will auto-hookup each element
         OnPropertyChanged();
      }
   }

   private void MyDataList_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
   {
      if (e.NewItems != null)
         foreach (MyDataClass item in e.NewItems)
            item.PropertyChanged += MyData_PropertyChanged;

      if (e.OldItems != null)
         foreach (MyDataClass item in e.OldItems)
            item.PropertyChanged -= MyData_PropertyChanged;

      OnPropertyChanged(nameof(MyDataList));
   }

   private void MyData_PropertyChanged(object sender, PropertyChangedEventArgs e)
   {
      switch (e.PropertyName)
      {
         case nameof(MyDataClass.IsSelected):
            OnPropertyChanged(nameof(Amount));
            break;
      }
   }

   public double Amount => MyDataList.Where(x => x.IsSelected).Sum(x => x.Quantity);
}

Upvotes: 1

Related Questions