在WPF中,您是否可以在不使用代码后台的情况下过滤CollectionViewSource?

18

主题已经把问题说清楚了。

<CollectionViewSource x:Key="MyData"
    Source="{Binding}" Filter="{ SomethingMagicInXaml? }" />

并不是我不能使用代码后端,只是这让我感到不舒服。

4个回答

25

如果你 "尽力而为",在XAML中几乎可以做任何事情,甚至可以用它编写整个程序

你永远无法绕过代码后台(如果你使用库,你不必编写任何代码,但应用程序当然仍然依赖于它),这里有一个特定情况下可以做的示例:

<CollectionViewSource x:Key="Filtered" Source="{Binding DpData}"
                      xmlns:me="clr-namespace:Test.MarkupExtensions">
    <CollectionViewSource.Filter>
        <me:Filter>
            <me:PropertyFilter PropertyName="Name" Value="Skeet" />
        </me:Filter>
    </CollectionViewSource.Filter>
</CollectionViewSource>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Markup;
using System.Windows.Data;
using System.Collections.ObjectModel;
using System.Windows;
using System.Text.RegularExpressions;

namespace Test.MarkupExtensions
{
    [ContentProperty("Filters")]
    class FilterExtension : MarkupExtension
    {
        private readonly Collection<IFilter> _filters = new Collection<IFilter>();
        public ICollection<IFilter> Filters { get { return _filters; } }

        public override object ProvideValue(IServiceProvider serviceProvider)
        {
            return new FilterEventHandler((s, e) =>
                {
                    foreach (var filter in Filters)
                    {
                        var res = filter.Filter(e.Item);
                        if (!res)
                        {
                            e.Accepted = false;
                            return;
                        }
                    }
                    e.Accepted = true;
                });
        }
    }

    public interface IFilter
    {
        bool Filter(object item);
    }
    // Sketchy Example Filter
    public class PropertyFilter : DependencyObject, IFilter
    {
        public static readonly DependencyProperty PropertyNameProperty =
            DependencyProperty.Register("PropertyName", typeof(string), typeof(PropertyFilter), new UIPropertyMetadata(null));
        public string PropertyName
        {
            get { return (string)GetValue(PropertyNameProperty); }
            set { SetValue(PropertyNameProperty, value); }
        }
        public static readonly DependencyProperty ValueProperty =
            DependencyProperty.Register("Value", typeof(object), typeof(PropertyFilter), new UIPropertyMetadata(null));
        public object Value
        {
            get { return (object)GetValue(ValueProperty); }
            set { SetValue(ValueProperty, value); }
        }
        public static readonly DependencyProperty RegexPatternProperty =
            DependencyProperty.Register("RegexPattern", typeof(string), typeof(PropertyFilter), new UIPropertyMetadata(null));
        public string RegexPattern
        {
            get { return (string)GetValue(RegexPatternProperty); }
            set { SetValue(RegexPatternProperty, value); }
        }

        public bool Filter(object item)
        {
            var type = item.GetType();
            var itemValue = type.GetProperty(PropertyName).GetValue(item, null);
            if (RegexPattern == null)
            {
                return (object.Equals(itemValue, Value));
            }
            else
            {
                if (itemValue is string == false)
                {
                    throw new Exception("Cannot match non-string with regex.");
                }
                else
                {
                    return Regex.Match((string)itemValue, RegexPattern).Success;
                }
            }
        }
    }
}

如果你想在XAML中实现某些功能,标记扩展是你的好帮手。

(你可能需要完整拼写扩展名,比如me:FilterExtension,因为Visual Studio的即时检查可能会无缘无故地发出警告。当然它仍然可以编译和运行,但这些警告可能会让人恼火。
并且不要期望CollectionViewSource.Filter会出现在IntelliSense中,它不希望你通过XML元素符号来设置该处理程序)


你真的测试过吗?上次我尝试时,在事件上使用标记扩展是不可能的...但也许在4.0中已经改变了。 - Thomas Levesque
很好,我不知道这个新功能! - Thomas Levesque
@H.B. 我遇到了一个异常:Filters不支持PropertyFilter类型的值。 - Vishal
1
@H.B. 不,它无法编译。 - Vishal
我认为我可能已经找到了编译失败的罪魁祸首,可能与public ICollection<IFilter> Filters有关,而不是public Collection<IFilter> Filters,请参见我的回答 - Jonathan
显示剩余14条评论

18

实际上,您甚至不需要访问CollectionViewSource实例,您可以直接在ViewModel中直接过滤源集合:

ICollectionView view = CollectionViewSource.GetDefaultView(collection);
view.Filter = predicate;

(请注意,ICollectionView.Filter不像CollectionViewSource.Filter那样是一个事件,它是一种名为Predicate<object>类型的属性)


1
尽管这可能是正确和有价值的信息,但严格来说,我认为这并没有回答所问的问题。 - H.B.
1
@H.B.,标题说“无代码背后(without code behind)”;对我来说,通常意味着“在ViewModel中”,但现在我意识到OP特别要求一个XAML解决方案... - Thomas Levesque
4
@Jerry Nixon,这不是代码后台;这是ViewModel的代码。当然,除非您将任何C#代码视为代码后台... - Thomas Levesque
2
+1 是因为你的答案不是写在 Code-behind 里面,而是在 ViewModel 中。 - Riegardt Steyn
1
@XAMlMAX,它不是 UI 视图;CollectionView 更像是数据库视图... - Thomas Levesque
显示剩余2条评论

8

WPF会在将任何IEnumerable派生的源数据绑定到ItemsControl.ItemsSource属性时自动创建一个CollectionView,或其派生类型之一,例如ListCollectionViewBindingListCollectionView。您获得哪种类型的CollectionView取决于您提供的数据源在运行时检测到的功能。

有时即使您尝试显式地将自己特定的CollectionView派生类型绑定到ItemsSource,WPF数据绑定引擎也可能会使用内部类型CollectionViewProxy对其进行包装。

自动提供的CollectionView实例是在系统上基于每个集合创建和维护的(注意:不是每个UI控件或每个绑定目标)。换句话说,每个你绑定到的源集合将会有一个全局共享的“默认”视图,并且可以通过再次将相同的“原始”IEnumerable实例传递回静态方法CollectionViewSource.GetDefaultView()来检索(或按需创建)这个唯一的CollectionView实例。
CollectionView是一个支持跟踪排序和/或过滤状态而不实际更改源的桥接器。因此,如果多个不同的Binding用法引用相同的源数据,每个用法都有不同的CollectionView,它们不会互相干扰。默认视图旨在优化非要求或预期进行过滤和排序的常见和简单情况。
简而言之,每个具有数据绑定的ItemsSource属性的ItemsControl都将始终拥有排序和过滤功能,这要归功于一些普遍存在的CollectionView。您可以通过获取并操作ItemsControl.Items属性中的“Default”CollectionView轻松执行任何给定IEnumerable的过滤/排序,但请注意,所有在UI中使用该视图的数据绑定目标--无论是因为您明确地绑定到CollectionViewSource.GetDefaultView(),还是因为您的源根本不是CollectionView--都将共享这些相同的排序/过滤效果。
在这个主题上往往没有提到的是,除了将源集合绑定到ItemsControlItemsSource属性(作为绑定目标)之外,您还可以"同时"访问已应用的过滤/排序结果的有效集合--作为System.Windows.Controls.ItemCollection的一个CollectionView派生实例--通过从控件的Items属性(作为绑定源)进行绑定。这使得许多简化的XAML方案成为可能。
如果你的应用只需要针对给定的 IEnumerable 源具有单个、全局共享的筛选/排序功能,则直接绑定到 ItemsSource 即可。仍然只在XAML中,您可以通过将同一控件上的Items属性视为绑定源的 ItemCollection 来筛选/排序项目。它有许多有用的可绑定属性来控制筛选/排序。如前所述,在以这种方式绑定到相同源 IEnumerable 的所有 UI 元素之间共享筛选/排序。--或--自己创建并应用一个或多个不同(非“默认”)的CollectionView实例。这允许每个数据绑定目标具有独立的筛选/排序设置。这也可以在XAML中完成,或者您可以创建自己的(List)CollectionView派生类。这种方法已经得到了广泛的覆盖,但我想在这里指出的是,在许多情况下,可以使用相同的技术来简化 XAML,即通过将ItemsControl.Items属性作为绑定源进行数据绑定,以便访问有效的CollectionView

摘要:

仅使用XAML,您就可以将数据绑定到表示当前WPF ItemsControl上任何CollectionView过滤/排序的有效结果集合,只需将其Items属性视为只读绑定源。这将是一个System.Windows.Controls.ItemCollection,它公开了可绑定/可变属性,用于控制活动过滤和排序条件。


[编辑] - 进一步思考:

请注意,在将您的IEnumerable直接绑定到ItemsSource的简单情况下,您可以在ItemsControl.Items处绑定到的ItemCollection将是原始集合的CollectionViewSource.GetDefaultView()的包装器。正如上面所讨论的那样,在使用XAML时,绑定到这个UI包装器(通过ItemsControl.Items)而不是绑定到它包装的底层视图(通过CollectionViewSource.GetDefaultView)是一个不需要费脑筋的方法,因为前者的方法可以节省您明确提及任何CollectionView的麻烦(在XAML中,这很尴尬)。

但更进一步地,由于ItemCollection 包装了默认的CollectionView,因此在代码后台(选择不太明显的情况下),绑定到UI发布的视图可能更加实用,因为这样可以最好地调整数据源和其UI控件目标的实际运行时能力。

2
我非常欣赏以清晰的方式进行概念讨论。谢谢。 - Alan Wayne
4
我给他一个负分,因为除了许多讨论之外,@Glenn Slayden应该提供一些实际的实现,至少是相关的细节。毕竟,这就是这个网站的全部内容... - Yoda

1

我使用.NET Framework 4.6.1(旧版本,我知道,但不幸的是,这是我目前情况下的限制)遇到了以下问题,即H.B.提供的已接受的解决方案:

严重程度 代码 描述
错误 XDG0012 成员“Filter”未被识别或无法访问。
错误 无法在元素“FilterExtension”上设置内容属性“Filters”。'Filters'具有不正确的访问级别或其程序集不允许访问。第xx行,第yy个位置。

通过更改以下内容轻松解决了此问题:

public ICollection<IFilter> Filters { get { return _filters; } }

public Collection<IFilter> Filters { get { return _filters; } }

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接