c# - 添加项目后,CollectionViewGroup.Items 没有提高 PropertyChanged?
问题描述
我正在尝试将带有小计的分组添加到 DataGrid 中。看了几篇文章:解决办法是有一个ObservableCollection
带有数据的,把它包装进去CollectionViewSource
,然后又会ItemsSource
为DataGrid。使用转换器计算小计,该转换器接收Items
作为CollectionViewGroup
输入并计算总和。
仅在 的初始人口ObservableCollection
或添加项目创建新组时,所有工作正常。但是,如果将一个项目添加到任何现有组中,则根本不会调用转换器进行重新计算 - 显然CollectionViewGroup.Items
不是引发PropertyChanged
事件?我在CollectionViewGroup 源代码中浏览了一下- Items
are ReadOnlyObservableCollection<object>
,应该PropertyChanged
在添加项目后触发,不是吗?
然后我注意到,CollectionViewGroup.ItemCount
添加新项目后显示正确,所以我尝试了一个技巧MultiBinding
- 添加了一个IMultiValueConverter
转换器,它同时接受Items
和ItemCount
作为参数,期望ItemCount
触发重新计算。它起作用了,但又没有完全成功——不知何故,当创建新组时,转换器只能获得一次正确的输入。如果将项目添加到现有组中,ItemCount
则正确,但Items
不正确!Items
收藏缺少新添加的项目!例如,当ItemCount
=2 时,Items
只有 1 个“旧”项目(Items.Count
=1)。当ItemCount
=3 时,Items
只有 2 个“旧”项目(Items.Count
=2),等等。所以转换器再次无法计算正确的小计,因为输入不完整......
看起来唯一可行的解决方案是调用Refresh()
整体CollectionViewSource
,但这会扩展所有组,导致闪烁,破坏 MVVM 概念,所以它很难看......
所以我的问题是:
是否还有任何更改可以正确
CollectionViewGroup.Items
加注PropertyChanged
?CollectionViewGroup
Multi Converter接收Items.Count
=ItemCount
- 1不是一个错误吗?
任何建议将不胜感激!
完整的示例代码在GitHub 上
一些代码摘录如下 - XAML:
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="Margin" Value="0,0,0,5"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" BorderThickness="1,1,1,5">
<Expander.Header>
<DockPanel>
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}" Margin="5,0,0,0" Width="100"/>
<TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount}"/>
<TextBlock FontWeight="Bold" Text="Sum 1: " Margin="5,0,0,0"/>
<TextBlock FontWeight="Bold" >
<TextBlock.Text>
<Binding Path="Items" Converter="{StaticResource sumConverter}" ConverterParameter="AmountValue" StringFormat="{}{0:N2}"/>
</TextBlock.Text>
</TextBlock>
<TextBlock FontWeight="Bold" Text="Sum 2: " Margin="5,0,0,0"/>
<TextBlock FontWeight="Bold" >
<TextBlock.Text>
<MultiBinding Converter="{StaticResource sumMulConverter}" ConverterParameter="AmountValue" StringFormat="{}{0:N2}">
<Binding Path="Items"/>
<Binding Path="ItemCount"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
</DockPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
转换器:
public class SumConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == DependencyProperty.UnsetValue) return DependencyProperty.UnsetValue;
if (null == parameter) return null;
string propertyName = (string)parameter;
if (!(value is ReadOnlyObservableCollection<object>)) return null;
ReadOnlyObservableCollection<object> collection = (ReadOnlyObservableCollection<object>)value;
decimal sum = 0;
foreach (object o in collection)
{
sum += (decimal)o.GetType().GetProperty(propertyName).GetValue(o);
}
return sum;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
public class SumMulConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (null == parameter) return null;
if (!(parameter is string)) return null;
string propertyName = (string)parameter;
if (values == DependencyProperty.UnsetValue) return DependencyProperty.UnsetValue;
if (values == null) return null;
if (values.Length < 2) return null;
if (!(values[0] is ReadOnlyObservableCollection<object>)) return null;
ReadOnlyObservableCollection<object> collection = (ReadOnlyObservableCollection<object>)values[0];
if (!(values[1] is int)) return null;
Debug.Print($"ItemCount={(int)values[1]}; Collection Count = {collection.Count}");
decimal sum = 0;
foreach (object o in collection)
{
sum += (decimal)o.GetType().GetProperty(propertyName).GetValue(o);
}
return sum; //.ToString("N2", CultureInfo.CurrentCulture);
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
解决方案
如果您想总结视图中的值,一个简单的解决方案是创建一个附加行为。
Enumerable.Cast<T>
还可以使用 LINQ:您可以使用or将集合从对象转换为显式类型,而不是使用反射Enumerable.OfType<T>
。要根据项目的属性计算集合的总和,请使用Enumerable.Sum
:
GroupItemSumBehavior.cs
public class GroupItemSumBehavior : DependencyObject
{
#region IsEnabled attached property
public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached(
"IsEnabled", typeof(bool), typeof(GroupItemSumBehavior), new PropertyMetadata(default(bool), OnIsEnabledChanged));
public static void SetIsEnabled(DependencyObject attachingElement, bool value) => attachingElement.SetValue(GroupItemSumBehavior.IsEnabledProperty, value);
public static bool GetIsEnabled(DependencyObject attachingElement) => (bool) attachingElement.GetValue(GroupItemSumBehavior.IsEnabledProperty);
#endregion
#region Sum attached property
public static readonly DependencyProperty SumProperty = DependencyProperty.RegisterAttached(
"Sum", typeof(decimal), typeof(GroupItemSumBehavior), new PropertyMetadata(default(decimal)));
public static void SetSum(DependencyObject attachingElement, decimal value) => attachingElement.SetValue(GroupItemSumBehavior.SumProperty, value);
public static decimal GetSum(DependencyObject attachingElement) => (decimal) attachingElement.GetValue(GroupItemSumBehavior.SumProperty);
#endregion
private static Dictionary<IEnumerable, GroupItem> CollectionToGroupItemMap { get; set; }
static GroupItemSumBehavior() => GroupItemSumBehavior.CollectionToGroupItemMap =
new Dictionary<IEnumerable, GroupItem>();
private static void OnIsEnabledChanged(DependencyObject attachingElement, DependencyPropertyChangedEventArgs e)
{
if (!(attachingElement is GroupItem groupItem))
{
return;
}
var collectionViewGroup = groupItem.DataContext as CollectionViewGroup;
bool isEnabled = (bool) e.NewValue;
if (isEnabled)
{
CollectionToGroupItemMap.Add(collectionViewGroup.Items, groupItem);
(collectionViewGroup.Items as INotifyCollectionChanged).CollectionChanged += CalculateSumOnCollectionChanged;
CalculateSum(collectionViewGroup.Items);
}
else
{
CollectionToGroupItemMap.Remove(collectionViewGroup.Items);
(collectionViewGroup.Items as INotifyCollectionChanged).CollectionChanged -= CalculateSumOnCollectionChanged;
}
}
private static void CalculateSum(IEnumerable collection)
{
if (GroupItemSumBehavior.CollectionToGroupItemMap.TryGetValue(collection, out GroupItem groupItem))
{
decimal sum = collection
.OfType<LineItem>()
.Sum(lineItem => lineItem.AmountValue);
SetSum(groupItem, sum);
}
}
private static void CalculateSumOnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
=> CalculateSum(sender as IEnumerable);
}
数据网格组样式
<DataGrid.GroupStyle>
<GroupStyle>
<GroupStyle.ContainerStyle>
<Style TargetType="{x:Type GroupItem}">
<Setter Property="GroupItemSumBehavior.IsEnabled" Value="True" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type GroupItem}">
<Expander IsExpanded="True" BorderThickness="1,1,1,5">
<Expander.Header>
<DockPanel>
<TextBlock FontWeight="Bold" Text="{Binding Path=Name}" Margin="5,0,0,0" Width="100" />
<TextBlock FontWeight="Bold" Text="{Binding Path=ItemCount}" />
<TextBlock FontWeight="Bold" Text="Sum: " Margin="5,0,0,0" />
<TextBlock FontWeight="Bold"
Text="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=(GroupItemSumBehavior.Sum)}" />
</DockPanel>
</Expander.Header>
<Expander.Content>
<ItemsPresenter />
</Expander.Content>
</Expander>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</GroupStyle.ContainerStyle>
</GroupStyle>
</DataGrid.GroupStyle>
推荐阅读
- javascript - 在重新加载完整图表之前,highcharts 是否有回调函数(这不应该包括图例禁用)?
- java - 有没有办法让 Launch4j 3.12 使用捆绑的 OpenJDK 而不是 Oracle JRE?
- vb.net - 从文本框中删除特殊符号
- python - 未发现 BitVecVal 作为 z3 的属性
- javascript - 如何让 Nuxt.js 同时接受 `/` 和 `/index.html` URL?
- python - dash 应用程序在启动 jupyter notebook 容器时拒绝连接
- typescript - 如何键入映射到对象的字符串数组,其中键及其值是数组值
- ffmpeg - 使用 FFMPEG 剪切 mp4 时,有时我会在没有 stderr 的情况下得到空输出
- ios - 以编程方式快速单击按钮时导航到 viewController
- json - Flutter 反序列化列表列表