В моем приложении у меня есть структура, подобная базе данных, где сам объект базы данных содержит несколько коллекций 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.
Я откровенно растерялся, так как в документации ничего по этому поводу нет. Не могу поверить, что я первый, кому нужен параметризованный динамический фильтр, который не привязывается к текстовому полю… Любая подсказка приветствуется!
Вышеприведенный код теперь должен быть немного понятнее. Кроме того, еще немного справочной информации, чтобы прояснить вопросы @erotavlas:
Приведенный выше код находится в представлении с собственной моделью представления в качестве контекста данных. CustomUserControl, созданный внутри DataTemplate, также имеет собственную модель представления. То, что я пробовал выше, — это передать результат фильтра (который является подмножеством SubsetCollection на основе индикатора первичного ключа, содержащегося в текущем элементе ItemControlRootObjectCollection) в соответствующее поле CustomUserControl.
Все ObservableCollection находятся в модели представления окружающего представления в объекте с именем Database. Этот объект содержит несколько таких ObservableCollections, в том числе RootObjectCollection и SubsetCollection.
@erotavlas, я обновил свой вопрос, чтобы уточнить, о чем вы спрашиваете. Пожалуйста, посмотрите и спасибо за вашу поддержку!





However, I need to dynamically pass a parameter of type
ObservableCollection<Guid>to the filter of theCollectionViewSource.
CollectionViewSource.Filter — это событие, и вы не можете передавать ему какие-либо пользовательские параметры. Вы получаете FilterEventArgs, который имеет доступную только для чтения ссылку на элемент и свойство Accepted, которое вы можете установить, чтобы указать, следует ли включать элемент в отфильтрованный набор, но это все.
Возможно, вы могли бы создать класс, расширяющий CollectionViewSource, и добавить в него свой собственный свойство зависимости. Это должно позволить вам привязаться к свойству источника, например SelectedKeys. Затем вы можете получить значение свойства зависимости, приведя аргумент sender в обработчике событий Filter, например:
private void Cvs_Filter(object sender, FilterEventArgs e)
{
YourCustomCollectionViewSource cvs = sender as YourCustomCollectionViewSource;
//..
}
Спасибо! Из-за нехватки времени мне пришлось пройти по знакомому пути и найти вполне возможное обходное решение. Я не реализовал ваше предложение, но поскольку оно а) кажется выполнимым и б) является прямым ответом на мой вопрос (в отличие от моего собственного обходного пути), я принимаю ваше как ответ на мой вопрос.
Когда я разместил этот вопрос, я спешил, так как до очень важной демонстрации (финансового раунда продукта) не хватало всего нескольких дней. Поскольку я не мог понять, как подойти к проблеме с помощью 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 вместо своего.
Как настроен ваш пользовательский контроль данных? В свою собственную модель представления или наследует контекст данных родительского элемента управления? И где находится каждая наблюдаемая коллекция?