【问题标题】:Filtering a hierarchical object displayed with nested xaml data templates过滤使用嵌套 xaml 数据模板显示的分层对象
【发布时间】: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,并且我在控制台上没有收到任何错误。即使我可以让这种方法发挥作用,我也不确定当搜索框中的文本发生变化时如何重新触发此过滤器。

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

【问题讨论】:

    标签: c# xaml binding filtering datatemplate


    【解决方案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】:

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

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

          Visibility Visibility
          {
              get { return visibility; }
              set { visibility = value; PropertyChanged("Visibility"); }
          }
      
    2. 将控件的 Visibility 属性绑定到步骤 1 中相应的 Visibility 属性。

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

      void Filter(Func<Foo, bool> filterFunc)
      {
          foreach (var item in foos)
          {
              if (!filterFunc(item))
                  item.Visibility = Visibility.Collapsed;
              else
                  item.Visibility = Visibility.Visible;
          }
      }
      
    4. 在 TextBox 的 TextChanged 事件上添加简单的过滤器调用。

      Filter(n => n.Name.ToLower().Contains(textBox.Text));

    或者更高级的容器控件:

    Filter(c => c.Items.Any(i => i.Visibility == Visibility.Visible));
    

    【讨论】:

    • 这是一个有趣的想法,但这需要我觉得 XAML/WPF 应该提供的额外机制。
    • 嗯,这是我提出的最快、最简单的解决方案 :)
    猜你喜欢
    • 2019-07-24
    • 1970-01-01
    • 2020-03-11
    • 1970-01-01
    • 1970-01-01
    • 2020-06-28
    • 1970-01-01
    • 2013-01-27
    • 1970-01-01
    相关资源
    最近更新 更多