Передайте нестатический параметр для фильтрации в CollectionViewSource - PullRequest
0 голосов
/ 17 марта 2019

В моем приложении у меня есть структура, похожая на базу данных, где сам объект базы данных содержит несколько ObservableCollection<KeyValuePair<Guid, T>> коллекций.Guid s действуют аналогично первичным ключам в реляционной базе данных, т. Е. Они обеспечивают отображения 1: 1 и 1: n между объектами разных коллекций («таблицами» в базе данных).

Теперьрассмотрите возможность связывания ObservableCollection<KeyValuePair<Guid, T>>, который находится в корне иерархии объектов в ItemsControl.Внутри DataTemplate я хочу связать подмножество другой коллекции с DependencyProperty из UserControl, где Guid s соответствуют значениям, которые каждый из объектов в первомcollection carry.

Как подсказывает множество ответов здесь на SO, CollectionViewSource - это то, что мне нужно, т. е.

<ItemsControl ItemsSource="{Binding RootObjectCollection}>
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <local:CustomUserControl>
        <local:CustomUserControl.SubsetCollection>
          <Binding>
            <Binding.Source>
              <CollectionViewSource Source="{Binding DataContext.Database.SubsetCollection, RelativeSource={RelativeSource AncestorType=UserControl}}"
                                    Filter="someFilter"
                               ???? FilterParameters="{Binding SelectedKeys}" />
            </Binding.Source>
          </Binding>
        </local:CustomUserControl.SubsetCollection>
      </local:CustomUserControl>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

Однако мне нужно динамически передать параметр типаObservableCollection<Guid> к фильтру CollectionViewSource.

Я откровенно потерян, так как в документации ничего по этому поводу нет.Я не могу поверить, что я первый, кому нужен параметризованный динамический фильтр, который не привязывается к текстовому полю ... Любая подсказка очень ценится!

Обновление 2019-03-18

Приведенный выше код теперь должен быть немного понятнее.Помимо этого, еще немного справочной информации, чтобы прояснить вопросы @ erotavlas:

  • Приведенный выше код находится в представлении с его собственной моделью представления в качестве контекста данных.CustomUserControl, который создается в DataTemplate, также имеет свою собственную модель представления.Я пытаюсь передать результат фильтрации (который является подмножеством SubsetCollection на основе индикатора первичного ключа, содержащегося в текущем элементе RootObjectCollection ItemControl) в соответствующее поле CustomUserControl.

  • Все ObservableCollection находятся в модели представления окружающего вида в объекте с именем Database.Этот объект содержит несколько таких ObservableCollections, среди прочих RootObjectCollection и SubsetCollection.

Ответы [ 2 ]

0 голосов
/ 24 марта 2019

Когда я отправил этот вопрос, я спешил, так как не хватало всего нескольких дней до очень важной демонстрации (раунд финансирования продукта).Поскольку я не мог понять, как подойти к проблеме с CollectionViewSource, я решил попробовать решение, используя старый подход MultiValueConverter, все время прекрасно понимая, что это заставит меня создать новый ObservableCollection подмножества.значения коллекции, которые - согласно справочной странице C # для ObservableCollection<T>(IEnumerable<T>) будут работать только односторонне, поскольку «элементы копируются в ObservableCollection<T>».Я подумал, что было бы лучше показать, что представление заполнено из базы данных без отражения изменений в базе данных, чем вообще ничего не показывать.

Вообразите мое удивление, когда выяснилось, что страница руководстваздесь не совсем правильно: копируются только примитивные значения, сложные объекты передаются по ссылке в новый ObservableCollection<T>!Это означает, что следующий фрагмент является полностью верным решением моей проблемы:

<ItemsControl ItemsSource="{Binding RootObjectCollection}>
  <ItemsControl.ItemTemplate>
    <DataTemplate>
      <local:CustomUserControl>
        <local:CustomUserControl.SubsetCollection>
          <MultiBinding Converter="{StaticResource SubsetEntryFromRootObjectIdSelectionConverter}">
            <Binding Path="Value.SubsetIds" />
            <Binding Path="DataContext.Database.SubsetCollection" RelativeSource="{RelativeSource AncestorType=UserControl}" />
        </MultiBinding>
        </local:CustomUserControl.SubsetCollection>
      </local:CustomUserControl>
    </DataTemplate>
  </ItemsControl.ItemTemplate>
</ItemsControl>

Важной частью здесь является сам MultiValueConverter, который определяется как

public class SubsetEntryFromRootObjectIdSelectionConverter: IMultiValueConverter {

    public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) {
      if (values[0] == null) // no reference ids contained
        return new ObservableCollection<SubsetItem>();

      if (!(values[0] is ObservableCollection<Guid>))
        throw new InvalidOperationException("Value must be a collection of Guids.");

      if (!(values[1] is ObservableCollection<KeyValuePair<Guid, SubsetItem>>))
        throw new InvalidOperationException("Value must be a collection of SubsetItems.");

      var selectedKeys = (ObservableCollection<Guid>)values[0];
      var originalCollection = (ObservableCollection<KeyValuePair<Guid, SubsetItem>>)values[1];
      var queryCollection = originalCollection.Where(kvp => selectedKeys.Contains(kvp.Key)).Select(kvp => kvp.Value);

      // it seems that the man page is misleading and that this constructor indeed copies references, not objects
      return new ObservableCollection<SubsetItem>(queryCollection);
    }

    public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) {
      throw new NotImplementedException();
    }
  }

Так какРешение работает безупречно, я не реализовал тот, который предложен @ mm8 выше .Однако, с технической точки зрения, предлагаемое решение является прямым ответом на мой вопрос, тогда как мой, честно говоря, скорее обходной путь.Поэтому я приму ответ @ mm8 вместо моего.

0 голосов
/ 18 марта 2019

Однако мне нужно динамически передать параметр типа ObservableCollection<Guid> в фильтр CollectionViewSource.

CollectionViewSource.Filter - это событие, и вы не можете передатьлюбые пользовательские параметры к нему.Вы получаете FilterEventArgs, который имеет ссылку только для чтения на элемент и свойство Accepted, которое можно установить, чтобы указать, включать ли элемент в отфильтрованный набор, но это все.

Вы можетевозможно, стоит подумать о создании класса, расширяющего CollectionViewSource, и добавить ваше собственное свойство зависимости it.Это должно позволить вам привязать к исходному свойству, например SelectedKeys.Затем вы можете получить значение свойства зависимости, приведя аргумент sender в обработчике события Filter, например:

private void Cvs_Filter(object sender, FilterEventArgs e)
{
    YourCustomCollectionViewSource cvs = sender as YourCustomCollectionViewSource;
    //..
}
...