Передайте нестатический параметр для фильтрации в CollectionViewSource

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

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

Как предлагает множество ответов здесь, на 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 на основе индикатора первичного ключа, содержащегося в текущем элементе ItemControlRootObjectCollection) в соответствующее поле CustomUserControl.

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

Как настроен ваш пользовательский контроль данных? В свою собственную модель представления или наследует контекст данных родительского элемента управления? И где находится каждая наблюдаемая коллекция?

erotavlas 17.03.2019 15:33

@erotavlas, я обновил свой вопрос, чтобы уточнить, о чем вы спрашиваете. Пожалуйста, посмотрите и спасибо за вашу поддержку!

Informagic 18.03.2019 08:39
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
2
210
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Ответ принят как подходящий

However, I need to dynamically pass a parameter of type ObservableCollection<Guid> to the filter of the CollectionViewSource.

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

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

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

Спасибо! Из-за нехватки времени мне пришлось пройти по знакомому пути и найти вполне возможное обходное решение. Я не реализовал ваше предложение, но поскольку оно а) кажется выполнимым и б) является прямым ответом на мой вопрос (в отличие от моего собственного обходного пути), я принимаю ваше как ответ на мой вопрос.

Informagic 23.03.2019 23:03

Когда я разместил этот вопрос, я спешил, так как до очень важной демонстрации (финансового раунда продукта) не хватало всего нескольких дней. Поскольку я не мог понять, как подойти к проблеме с помощью CollectionViewSource, я решил попробовать решение, используя старый подход MultiValueConverter, при этом полностью осознавая, что это заставит меня создать новый ObservableCollection значений коллекции подмножеств, которые, согласно к Справочная страница С# для 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 вместо своего.

Другие вопросы по теме