过滤使用嵌套 xaml 数据模板显示的分层对象

Posted

技术标签:

【中文标题】过滤使用嵌套 xaml 数据模板显示的分层对象【英文标题】:Filtering a hierarchical object displayed with nested xaml data templates 【发布时间】:2011-03-20 06:30:15 【问题描述】:

我在过滤嵌套 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 都是一个选项卡控件,每个 Bar 都包含在它自己的选项卡中。每个 Bar 都包含一个其 Quxes 的列表框。

或:

 ______ ______ ______  
| Foo1 | Foo2 | Foo3 |  
|______ ______       |  
| Bar1 | Bar2 |______|  
| | qux1            ||  
| | qux2            ||  
| | qux3            ||  
---------------------- 

我还有一个文本框,我想用它来过滤这个细分。 当我在文本框中输入内容时,我想过滤 quxes,以便不包含文本的那些不可见。 理想情况下,Bar 选项卡如果没有可见的quxes,也将被隐藏, 和 Foo 选项卡在没有可见 Bars 时隐藏

我考虑了两种方法:

方法一,在适当的 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?

方法二,将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 的数据上下文和适当的 Quxes 成员。我尝试的任何操作都不会导致显示 quxes,并且我在控制台上没有收到任何错误。即使我可以让这种方法发挥作用,我也不确定当搜索框中的文本发生变化时如何重新触发此过滤器。

任何建议或指导将不胜感激。

【问题讨论】:

【参考方案1】:

我认为您应该从您的视图模型中公开ICollectionView,而不是(或除了)ObservableCollection。这会将与过滤/排序相关的所有业务逻辑都带入 VM,这是它的正确位置。

您可以通过创建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; 

【讨论】:

但这如何帮助我处理模板生成的项目? 您可以将带有过滤器的文本框绑定到视图模型中的属性,并相应地修改集合视图的过滤器。也可以修改子item的collection view等。 如何让子集合(Foos/Quxes)的视图能够过滤?您介意发布一个示例或一些伪代码吗? 一个可能的解决方案,但我必须修改 Foo/Bar 的定义,这不太理想。 应该更改定义。视图模型是放置过滤逻辑的正确位置。您可以同时公开集合和集合视图。【参考方案2】:

编辑

终于可以满足您的要求了。

Here is the link to the updated project.


(由卢克编辑)

这是我最终采用的(优秀的)解决方案,所以我将提取重要部分,并在此处将它们作为帖子的一部分:

关键的 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();
    

优秀的解决方案,因为它不需要更改底层对象或层次结构。

【讨论】:

我很欣赏 Eugene 的例子,但我希望主要停留在我所概述的框架中。如果这是不可能的,我总是可以像你建议的那样定义一个自定义列表控件,但我试图避免这种情况。 您不能创建自定义列表控件。由列表控件的Items 属性表示的ItemsCollection 具有Filter 属性。您需要使用指定的谓词对其进行设置,并通过更改 TextBox.Text 属性或通过内部(对用户隐藏)过滤逻辑来更新此谓词。请参阅 MSDN 链接中ItemCollection.Filter 的使用示例:msdn.microsoft.com/en-us/library/ms752348.aspx 听起来不错,但这让我回到了最初的问题:如何在模板 (xaml) 中设置过滤器,或者访问模板实例化以在代码中设置过滤器?跨度> 请检查编辑。我认为这正是您所需要的。 看来您正在将 CVS 的 Source 绑定到自身,这很有趣。我会试一试。仅供参考-您的项目无法按原样编译。【参考方案3】:

我今天在工作中遇到了类似的问题,并提出了以下解决方案:

    直接或通过适配器模式将 Visibility 属性添加到所有元素。

        Visibility Visibility
        
            get  return visibility; 
            set  visibility = value; PropertyChanged("Visibility"); 
        
    

    将控件的 Visibility 属性绑定到步骤 1 中相应的 Visibility 属性。

    通过扩展方法或在它们内部实现对数据的简单过滤。

    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));

【讨论】:

这是一个有趣的想法,但这需要我觉得 XAML/WPF 应该提供的额外机制。 嗯,这是我提出的最快、最简单的解决方案 :)

以上是关于过滤使用嵌套 xaml 数据模板显示的分层对象的主要内容,如果未能解决你的问题,请参考以下文章

R语言使用treemap包中的treemap函数可视化treemap图:treemap将分层数据显示为一组嵌套矩形自定义设置各个分层分组的线条色彩线条的宽度

用于显示分层数据的嵌套 *ngFor 循环的性能问题

Magento 2 Amasty分层导航。删除当前应用的过滤器的显示

如何在 wpf 的分层数据模板中显示树视图项的上下文菜单

python中使用squarify包可视化treemap图:treemap将分层数据显示为一组嵌套矩形,每一组都用一个矩形表示,该矩形的面积与其值成正比

Python使用matplotlib可视化Treemap图treemap将分层数据显示为一组嵌套矩形,每一组都用一个矩形表示,该矩形的面积与其值成正比(Treemap)