Reputation: 726
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
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
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
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