过滤使用嵌套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?