代码之家  ›  专栏  ›  技术社区  ›  luke

筛选使用嵌套的XAML数据模板显示的分层对象

  •  7
  • luke  · 技术社区  · 14 年前

    我在筛选嵌套的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都是一个选项卡控件,其中每个栏都包含在自己的选项卡中。每一个酒吧都有一个其曲的列表框。

    或:

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

    我还有一个文本框,我想用它来过滤这个细目。 当我在文本框中键入时,我想过滤quxes,以便不包含文本的不可见。 理想的 Bar 如果标签没有可见的quxes,它们也将被隐藏,并且 Foo 选项卡不可见时隐藏 酒吧 S

    我考虑了两种方法:

    方法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的项源声明为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">
    

    我在哪里试过很多东西??????”但我无法正确绑定到列表框的DataContext和适当的Quxes成员。我尝试的任何操作都不会导致显示quxes,并且在控制台上也不会出现任何错误。即使我可以使用这种方法,我也不确定当搜索框中的文本发生变化时,我将如何重新触发这个过滤器。

    如有任何建议或指示,将不胜感激。

    3 回复  |  直到 14 年前
        1
  •  3
  •   luke    14 年前

    编辑

    最后,我可以满足你的要求。

    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)是有效的,但是它做得很漂亮。

    过滤器的代码 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();
        }
    }
    

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

        2
  •  2
  •   Eli Arbel    14 年前

    我认为你应该揭露 ICollectionView 从视图模型而不是 ObservableCollection . 这将把与过滤/排序相关的所有业务逻辑引入到VM中,而VM正是适合它的地方。

    你可以得到 集成视图 通过创建 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; }
    }
    
        3
  •  0
  •   Grozz    14 年前

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

    1. 直接或通过适配器模式向所有元素添加可见性属性。

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

    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. 添加对文本框的TextChanged事件的简单筛选调用。

      筛选器(n=>n.name.tolower()。包含(textbox.text));

    或者更高级的容器控件:

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