CollectionViewGroup.Items не вызывает PropertyChanged после добавления элемента? - PullRequest
0 голосов
/ 21 марта 2020

Я пытаюсь добавить группировку с промежуточной суммой в DataGrid. Прочитайте несколько статей: решение состоит в том, чтобы иметь ObservableCollection с данными, обернуть их в CollectionViewSource, что в свою очередь будет ItemsSource для DataGrid. Промежуточный итог рассчитывается с помощью преобразователя, который получает Items из CollectionViewGroup в качестве входных данных и вычисляет сумму.

Все отлично работает только при начальной совокупности ObservableCollection или при добавлении элемента создает новая группа. Но если элемент добавлен в какую-либо существующую группу, конвертер просто не вызывается для пересчета - очевидно, CollectionViewGroup.Items не вызывает событие PropertyChanged? Я немного просмотрел в Источник CollectionViewGroup - Items, равный 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) и т. Д. c. Итак, опять же, преобразователь не может вычислить правильный промежуточный итог, потому что входные данные неполные ...

Похоже, что единственным рабочим решением было бы вызвать 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();
    }
}

1 Ответ

1 голос
/ 25 марта 2020

Если вы хотите суммировать значения в представлении, простым решением было бы создать присоединенное поведение.

Также используйте LINQ: вместо отражения вы можете привести коллекцию из объекта к явный тип, использующий Enumerable.Cast<T> или 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

<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>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...