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

发布于 2024-09-10 20:11:30 字数 3293 浏览 4 评论 0原文

我在过滤嵌套 xaml 模板中显示的分层数据时遇到问题。

我有一个 ObservableCollection; 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 都包含其 Qux 的列表框。

或者:

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

我还有一个文本框,我想用它来过滤此细分。 当我在文本框中键入内容时,我想过滤 quxes,以便那些不包含文本的内容不可见。 理想情况下,Bar 选项卡也会被隐藏,如果它们没有可见的 quxes,并且当它们没有可见的 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 的数据上下文和适当的 Quxes 成员。我尝试的任何结果都不会显示 quxes,并且控制台上没有出现任何错误。即使我能让这种方法发挥作用,我也不确定当搜索框中的文本发生变化时如何重新触发此过滤器。

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

I'm having trouble filtering hierarchical data that's being displayed in nested xaml templates.

I've got a ObservableCollection<Foo> Foos, that I'm displaying in XAML.

Lets say Foo looks like:

class Foo
{
    public ObservableCollection<Bar> Bars;
}

class Bar
{
    public ObservableCollection<Qux> Quxes;
}

I'm displaying Foos with the following xaml:

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

To put it into words, there's a tab control, with a tab for each Foo. Each Foo is a tab control, with each Bar it contains in it's own tab. Each Bar contain's a listbox of its Quxes.

or:

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

I also have a TextBox that I'd like to use to filter this breakdown. When I type in the text box, I'd like to filter the quxes so those not containing the text wouldn't be visible. Ideally Bar tabs would also be hidden if they have no visible quxes, and Foo tabs hidden when they have no visible Bars

I have considered two approaches:

Approach 1, reset the Filter property on the appropriate CollectionViewSources

On my text box's TextChanged event, I loop through my Foo's asking for the corresponding (static) TabControl's 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?
}

Approach 2, declare the ListView's ItemSource to a CollectionViewSource

I tried setting the Filter via xaml, by changing this line:

<ListBox ItemsSource="{Binding Path=Quxes}" DisplayMemberPath="Name">

to this,

<CollectionViewSource x:Key="MyCVS" Source="?????" Filter="MyCVS_Filter" />
...
<ListBox ItemsSource="{Binding Source={StaticResource MyCVS}}" DisplayMemberPath="Name">

I've tried a number of things where I have "?????" but I cannot correctly bind to the ListBox's datacontext and appropriate Quxes member. Nothing I try results in the quxes being displayed, and I get no errors on the console. Even If I could get this approach to work, I'm not sure how I would re-trigger this filter when the text in the search box changed.

Any advice or direction would be appreciated.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

ら栖息 2024-09-17 20:11:30

编辑

最后我让它满足您的要求。

这是更新项目的链接


(由卢克编辑)

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

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

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

Edit

Finally I've got it working with your requirements.

Here is the link to the updated project.


(edit by luke)

This is the (excellent) solution I ended up going with, so I'm going to extract the important parts, and actually make them part of the post here:

The key xaml portion ends up looking like this:

<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"/>

I set the respective control to each one of these views as the control's ItemSource. The magic is in the binding of each CVS. Each CVS gets the data context for the control/templated control in which appears, so you can use the the real name of the bound object's collection. I'm not sure I understand why binding the source of that source binding to itself (the CVS) works, but it does so beautifully.

The code for the filter TextBox then becomes something like:

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

Excellent solution as it requires no changing of the underlying objects or hierarchy.

伤感在游骋 2024-09-17 20:11:30

我认为您应该从视图模型中公开一个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; }
}

I think you should expose an ICollectionView from your View-Model instead of (or in addition to) an ObservableCollection. This would bring all the business logic involved with filtering/sorting into the VM, which is the right place for it.

You can get the ICollectionView of a collection by creating a CollectionViewSource, setting its Source property to the collection and retrieving the View property.

(Update) Here's some sample code:

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; }
}
浅浅淡淡 2024-09-17 20:11:30

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

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

     可见性 可见性
        {
            获取{返回可见性; }
            设置{可见性=值; PropertyChanged("可见性"); }
        }
    
  2. 将控件的 Visibility 属性绑定到步骤 1 中相应的 Visibility 属性。

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

    void Filter(FuncfilterFunc)
    {
        foreach(foos 中的 var 项目)
        {
            if (!filterFunc(item))
                item.Visibility = Visibility.Collapsed;
            别的
                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));

I've had the similar problem at the work today and came out with the following solution:

  1. Add Visibility property to all your elements directly or through adapter pattern.

        Visibility Visibility
        {
            get { return visibility; }
            set { visibility = value; PropertyChanged("Visibility"); }
        }
    
  2. Bind Visibility property of controls to corresponding Visibility properties from step1.

  3. Implement simple filtering to your data through extension methods or inside them.

    void Filter(Func<Foo, bool> filterFunc)
    {
        foreach (var item in foos)
        {
            if (!filterFunc(item))
                item.Visibility = Visibility.Collapsed;
            else
                item.Visibility = Visibility.Visible;
        }
    }
    
  4. Add simple filter calls on TextChanged event of your TextBox.

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

or a bit more advanced for you container controls:

Filter(c => c.Items.Any(i => i.Visibility == Visibility.Visible));
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文