I have a datagrid with grouped items created via CollectionViewGroup class. For each group of items I calculate the sum of the items of this group and then the total sum of all groups. The user can change value of a sum of one of groups. The total sum has to be changed as well, but it doesn't. I suppose that the problem is that the qualifier of the event OnPropertyChanged is protected, but I'm not sure.
My code is:
public MainWindow()
{
InitializeComponent();
var lv = new ObservableCollection<LPosition>();
lv.positions.Add(new LPosition() { GB = 5, OZ1 = "1", OZ2 = "1" });
lv.positions.Add(new LPosition() { GB = 5, OZ1 = "1", OZ2 = "2" });
lv.positions.Add(new LPosition() { GB = 5, OZ1 = "1", OZ2 = "2" });
lv.positions.Add(new LPosition() { GB = 5, OZ1 = "2", OZ2 = "1" });
lv.positions.Add(new LPosition() { GB = 5, OZ1 = "2", OZ2 = "1" });
lv.positions.Add(new LPosition() { GB = 5, OZ1 = "2", OZ2 = "2" });
myGrid.DataContext = lv;
in XAML:
<Window.Resources>
<local:GroupSumConverter x:Key="groupSumConverter"/>
<CollectionViewSource x:Key="groupedItems" Source="{Binding ElementName=myGrid, Path=DataContext.positions}">
<CollectionViewSource.GroupDescriptions>
<PropertyGroupDescription PropertyName="OZ1"/>
<PropertyGroupDescription PropertyName="OZ2"/>
</CollectionViewSource.GroupDescriptions>
</CollectionViewSource>
<Style x:Key="FirstLevelGroupStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Border BorderThickness="2" BorderBrush="DarkGray" Margin="0,1">
<StackPanel>
<Expander>
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock FontWeight="Bold" x:Name="TextBlock_SecondLvlSum">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} - {1}">
<Binding Path="Name"/>
<Binding Path="myGrid.DataContext"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</Expander.Header>
<ItemsPresenter/>
</Expander>
<TextBlock TextAlignment="Right" FontWeight="Bold">
<TextBlock.Text>
<MultiBinding x:Name="totalSumConverter" Converter="{StaticResource groupSumConverter}" StringFormat="Summe: {0:N2}" ConverterCulture='de-DE'>
<Binding Path="Items"/>
<Binding />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="SecondLevelGroupStyle" TargetType="{x:Type GroupItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<StackPanel>
<ItemsPresenter>
<ItemsPresenter.Style>
<Style TargetType="ItemsPresenter">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Name}" Value="">
<Setter Property="Visibility" Value="Visible"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ItemsPresenter.Style>
</ItemsPresenter>
<Border BorderThickness="2" BorderBrush="DarkGray" Margin="0,1" Background="Aquamarine">
<Border.Style>
<Style TargetType="Border">
<Style.Triggers>
<DataTrigger Binding="{Binding Name}" Value="">
<Setter Property="Visibility" Value="Collapsed"></Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel>
<Expander>
<Expander.Header>
<StackPanel Orientation="Horizontal">
<TextBlock FontWeight="Bold">
<TextBlock.Text>
<MultiBinding StringFormat="{}{0} - {1}">
<Binding Path="Name"/>
<Binding Path="Items[0].kurztext"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</Expander.Header>
<ItemsPresenter/>
</Expander>
<TextBlock TextAlignment="Right" FontWeight="Bold">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource groupSumConverter}" StringFormat="Summe: {0:N2}" ConverterCulture='de-DE'>
<Binding Path="Items"/>
<Binding Path="Items[0].GB"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</StackPanel>
</Border>
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<DataGrid AutoGenerateColumns="False"
x:Name="myGrid"
ItemsSource="{Binding Source={StaticResource groupedItems}}">
<DataGrid.Columns>
<DataGridTextColumn Binding="{Binding OZ1}" Header="OZ1"/>
<DataGridTextColumn Binding="{Binding OZ2}" Header="OZ2"/>
<DataGridTextColumn Binding="{Binding GB}" Header="GB"/>
</DataGrid.Columns>
<DataGrid.GroupStyle>
<GroupStyle ContainerStyle="{StaticResource FirstLevelGroupStyle}">
<GroupStyle.Panel>
<ItemsPanelTemplate>
<DataGridRowsPresenter/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
<GroupStyle ContainerStyle="{StaticResource SecondLevelGroupStyle}">
<GroupStyle.Panel>
<ItemsPanelTemplate>
<DataGridRowsPresenter/>
</ItemsPanelTemplate>
</GroupStyle.Panel>
</GroupStyle>
</DataGrid.GroupStyle>
</DataGrid>
The corresponding converter:
class GroupSumConverter:IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
var lPosContainer = (ReadOnlyObservableCollection<Object>)values[0];
double sum = 0.0;
if (lPosContainer[0] != null && lPosContainer[0] is CollectionViewGroup)
{
foreach (CollectionViewGroup group in lPosContainer)
{
sum += sumPositions((ReadOnlyObservableCollection<Object>)group.Items);
}
}
else
{
sum += sumPositions(lPosContainer);
}
return sum;
}
private double sumPositions(ReadOnlyObservableCollection<Object> items)
{
double sum = 0.0;
foreach (LPosition lPos in items)
{
sum += lPos.GB;
}
return sum;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
And the LPosition class is:
class LPosition:INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
private void OnPropertyChanged(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}
private double _GB;
public double GB
{
get { return _GB; }
set { _GB = value;
OnPropertyChanged("GB");
}
}
public string OZ1 { get; set; }
public string OZ2 { get; set; }
}
Any suggestions how to solve this problem and help would be appreciated!
EDIT 1:
I have tried to solve the problem according to this question. I've used a class TrulyObservableCollection. The problem now is, that collection notices when item in it changes, but the Converter is not called after the item has been changed.
EDIT 2: I have added screenshots so it's clear what the problem is:
The sum is correct
The sum is not correct
EDIT 3: I have tried the solution of @Estacado7706, but there is still one problem remaining.
It works for all rows, except the ones that are the last of a group. The problem seems to be, that the change of the index is handeled in another way in this case. The SelectedIndex is set to -1 because there is no next following item to select and therefore the converter is called (works as expected so far).
The problem is, that the last value of the collection is not passed in this case. @Estacado7706 has mentioned this and posted a workaround. But this workaround does not work for me: The index is changed to 0 and so the converter is called again, but the edited item is still not passed.
@Estacado7706 has mentioned in a workaround to add a selectedItemchanged event to the DataGrid. There is no such event (at least I didn't find one), so I tried SelectionChanged event, as well as SelecedCellsChanged event, but both seem to be called before the commit.
I have also tried to avoid this by commiting the item manually by using
myGrid.CommitEdit();
myGrid.Items.Refresh();
This works and refreshes the sum, but the disadvantage is, that all
expanders are closed (because of the Items.Refresh())
, which is not very
user-friendly.