Я испытываю затруднения при фильтрации иерархических данных, это отображается во вложенных шаблонах xaml.
У меня есть a ObservableCollection<Foo> Foos
, то, что я отображаюсь в XAML.
Позволяет говорят, что Foo похож:
class Foo
{
public ObservableCollection<Bar> Bars;
}
class Bar
{
public ObservableCollection<Qux> Quxes;
}
Я отображаю Foos со следующим xaml:
<Grid>
<Grid.Resources>
<CollectionViewSource x:Key="MyCVS" Source="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ListView}}, Path=DataContext.UnifiedSymbols}" Filter="MyCVS_Filter" />
<DataTemplate x:Key="NestedTabHeaderTemplate">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
<DataTemplate x:Key="NestedTabContentTemplate">
<ListBox ItemsSource="{Binding Path=Quxes}" DisplayMemberPath="Name"/>
</DataTemplate>
<DataTemplate x:Key="TopLevelTabHeaderTemplate">
<TextBlock Text="{Binding Path=Name}"/>
</DataTemplate>
<DataTemplate x:Key="TopLevelTabContentTemplate">
<TabControl ItemsSource="{Binding Path=Bars}"
ItemTemplate="{StaticResource NestedTabHeaderTemplate}"
ContentTemplate="{StaticResource NestedTabContentTemplate}"
/>
</DataTemplate>
</Grid.Resources>
<TabControl ItemSource="{Binding correct binding for my control's collection of Foos}"
ItemTemplate="{StaticResource TopLevelTabHeaderTemplate}"
ContentTemplate="{StaticResource TopLevelTabContentTemplate}"
x:Name="tabControl"
/>
</Grid>
Для выражения его словами существует управление вкладкой с вкладкой для каждого Foo. Каждый Foo является управлением вкладкой с каждой Панелью, которую оно содержит на своей собственной вкладке. Каждый contain's Панели поле списка его Quxes.
или:
______ ______ ______
| Foo1 | Foo2 | Foo3 |
|______ ______ |
| Bar1 | Bar2 |______|
| | qux1 ||
| | qux2 ||
| | qux3 ||
----------------------
У меня также есть TextBox, который я хотел бы использовать для фильтрации этой разбивки. Когда я ввожу в текстовом поле, я хотел бы отфильтровать quxes, таким образом, те, которые не содержат текст, не будут видимы. Идеально Bar
вкладки также были бы скрыты, если у них нет видимого quxes, и Foo
вкладки, скрытые, когда они имеют не видимый Bar
s
Я рассмотрел два подхода:
На событии TextChanged моего текстового поля я циклично выполняюсь посредством просьбы своего Foo о CollectionViewSource соответствующего (статического) TabControl:
foreach(Foo foo in tabControl.Items)
{
var tabItem = tabControl.ItemContainerGenerator.ContainerFromItem(foo); // This is always of type TabItem
// How do I get the TabControl that will belong to each of Foo's Bar's?
}
Я пытался установить Фильтр через xaml путем изменения этой строки:
<ListBox ItemsSource="{Binding Path=Quxes}" DisplayMemberPath="Name">
к этому,
<CollectionViewSource x:Key="MyCVS" Source="?????" Filter="MyCVS_Filter" />
...
<ListBox ItemsSource="{Binding Source={StaticResource MyCVS}}" DisplayMemberPath="Name">
Я попробовал много вещей, где я имею"?????" но я не могу правильно связать с datacontext ListBox и соответствующим членом Quxes. Ничто я пробую результаты в quxes том, чтобы быть отображенным, и я не получаю ошибок на консоли. Даже если бы я мог бы заставить этот подход работать, я не уверен, как я повторно инициировал бы этот фильтр когда текст в измененном поле поиска.
Любой совет или направление ценились бы.
Edit
Наконец-то все заработало с вашими требованиями.
Вот ссылка на обновленный проект .
(отредактировал luke)
Это (отличное) решение, которое я выбрал, поэтому я собираюсь извлечь важные части и фактически сделать их частью сообщения здесь:
Ключ xaml в конечном итоге выглядит следующим образом:
<CollectionViewSource x:Key="FooCVS" x:Name="_fooCVS" Source="{Binding Foos, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type WpfApplication1:MainWindow}}}" Filter="_fooCVS_Filter"/>
<CollectionViewSource x:Key="BarCVS" x:Name="_barCVS" Source="{Binding Bars, Source={StaticResource FooCVS}}" Filter="_barCVS_Filter"/>
<CollectionViewSource x:Key="QuxCVS" x:Name="_quxCVS" Source="{Binding Quxs, Source={StaticResource BarCVS}}" Filter="_quxCVS_Filter"/>
Я установил соответствующий элемент управления для каждого из этих представлений как элемент управления ItemSource
. Магия заключается в привязке каждой CVS. Каждая CVS получает контекст данных для элемента управления / шаблонного элемента управления, в котором появляется, поэтому вы можете использовать настоящее имя коллекции связанных объектов. Я не уверен, что понимаю, почему привязка источника этой привязки к самому себе (CVS) работает, но это прекрасно.
Код фильтра TextBox
затем становится примерно таким:
private void filterTextBox_TextChanged(object sender, TextChangedEventArgs e)
{
var cvs = TryFindResource("FooCVS") as CollectionViewSource;
if (cvs != null)
{
if (cvs.View != null)
cvs.View.Refresh();
}
cvs = TryFindResource("QuxCVS") as CollectionViewSource;
if (cvs != null)
{
if (cvs.View != null)
cvs.View.Refresh();
}
cvs = TryFindResource("BarCVS") as CollectionViewSource;
if (cvs != null)
{
if (cvs.View != null)
cvs.View.Refresh();
}
}
Отличное решение, поскольку оно не требует изменения базовых объектов или иерархии.
Я думаю, вам следует раскрыть ICollectionView
из вашей модели представления вместо (или в дополнение) ObservableCollection
. Это перенесет всю бизнес-логику, связанную с фильтрацией / сортировкой, в виртуальную машину, которая является подходящим местом для этого.
Вы можете получить ICollectionView
коллекции, создав CollectionViewSource
, установив его свойство Source
для коллекции и получив представление View
] имущество.
(Обновление) Вот пример кода:
class Foo
{
public Foo()
{
_bars = new ObservableCollection<Bar>();
Bars = new CollectionViewSource { Source = _bars }.View;
}
private ObservableCollection<Bar> _bars;
public ICollectionView Bars { get; private set; }
public void Filter(string quxName)
{
Bars.Filter = o => ((Bar)o).Quxes.Any(q => q.Name == quxName);
foreach (Bar bar in Bars)
{
bar.Filter(quxName);
}
}
}
class Bar
{
private ObservableCollection<Qux> _quxes;
public ICollectionView Quxes { get; private set; }
public void Filter(string quxName)
{
Quexs.Filter = o => ((Qux)o).Name == quxName;
}
}
class Qux
{
public string Name { get; set; }
}
У меня сегодня была аналогичная проблема на работе, и я предложил следующее решение:
Добавьте свойство Visibility ко всем вашим элементам напрямую или через шаблон адаптера.
Видимость Видимость
{
получить {вернуть видимость; }
установить {видимость = значение; PropertyChanged («Видимость»); }
}
Привязать свойство Visibility элементов управления к соответствующим свойствам Visibility из шага 1.
Реализуйте простую фильтрацию ваших данных с помощью методов расширения или внутри них.
void Filter (Func filterFunc)
{
foreach (элемент var в foos)
{
если (! filterFunc (элемент))
item.Visibility = Видимость.Collapsed;
еще
item.Visibility = Видимость.Видимый;
}
}
Добавьте вызовы простого фильтра к событию TextChanged вашего TextBox.
Filter (n => n.Name.ToLower (). Contains (textBox.Text));
или более продвинутые элементы управления контейнером:
Filter(c => c.Items.Any(i => i.Visibility == Visibility.Visible));