过滤使用嵌套xaml数据模板显示的分层对象
我无法过滤嵌套xaml模板中显示的分层数据。
我有一个ObservableCollection<Foo> Foos
,我在XAML中显示。
可以说Foo看起来像:
class Foo
{
public ObservableCollection<Bar> Bars;
}
class Bar
{
public ObservableCollection<Qux> Quxes;
}
我使用以下xaml显示Foos:
<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都是一个选项卡控件,每个Foo包含在它自己的选项卡中。 每个Bar都包含Quxes的列表框。
要么:
______ ______ ______
| Foo1 | Foo2 | Foo3 |
|______ ______ |
| Bar1 | Bar2 |______|
| | qux1 ||
| | qux2 ||
| | qux3 ||
----------------------
我也有一个文本框,我想用它来过滤这个故障。 当我输入文本框时,我想过滤quxes,这样不包含文本的文件就不可见。 理想情况如果Bar
选项卡没有可见的qux,它们也会被隐藏,并且当它们没有可见的Bar
时隐藏Foo
选项卡
我考虑过两种方法:
方法1,重置相应CollectionViewSources上的Filter属性
在我的文本框的TextChanged事件中,循环访问我的Foo的相应(静态)TabControl的CollectionViewSource:
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?
}
方法2,将ListView的ItemSource声明为CollectionViewSource
我尝试通过更改以下行来设置通过xaml的过滤器:
<ListBox ItemsSource="{Binding Path=Quxes}" DisplayMemberPath="Name">
对此,
<CollectionViewSource x:Key="MyCVS" Source="?????" Filter="MyCVS_Filter" />
...
<ListBox ItemsSource="{Binding Source={StaticResource MyCVS}}" DisplayMemberPath="Name">
我已经尝试了很多我拥有“?????”的东西 但我无法正确绑定到ListBox的datacontext和适当的Quxes成员。 没有任何我尝试显示quxes的结果,并且在控制台上没有出现错误。 即使如果我可以使这种方法起作用,但我不确定在搜索框中的文本更改时如何重新触发此过滤器。
任何意见或方向将不胜感激。
编辑
最后,我已经与你的要求合作了。
这是更新项目的链接。
(由卢克编辑)
这是我最终选择的(优秀)解决方案,因此我将提取重要的部分,并将其作为后续部分:
关键的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();
}
}
优秀的解决方案,因为它不需要更改底层对象或层次结构。
我认为你应该从你的View-Model中暴露一个ICollectionView
,而不是(或者除了)一个ObservableCollection
。 这将把涉及过滤/排序的所有业务逻辑带入虚拟机,这是它的正确位置。
您可以通过创建CollectionViewSource
,将Source
属性设置为集合并检索View
属性来获取集合的ICollectionView
。
(更新)以下是一些示例代码:
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属性直接或通过适配器模式添加到所有元素。
Visibility Visibility
{
get { return visibility; }
set { visibility = value; PropertyChanged("Visibility"); }
}
将控件的可见性属性绑定到来自step1的相应可见性属性。
通过扩展方法或在其内部实现对数据的简单过滤。
void Filter(Func<Foo, bool> filterFunc)
{
foreach (var item in foos)
{
if (!filterFunc(item))
item.Visibility = Visibility.Collapsed;
else
item.Visibility = Visibility.Visible;
}
}
在TextBox的TextChanged事件上添加简单的过滤器调用。
Filter(n => n.Name.ToLower()。Contains(textBox.Text));
或者更适合你的容器控件:
Filter(c => c.Items.Any(i => i.Visibility == Visibility.Visible));
链接地址: http://www.djcxy.com/p/47061.html
上一篇: Filtering a hierarchical object displayed with nested xaml data templates
下一篇: How should I handle blocking operations when using scala actors?