【问题标题】:In WPF can you filter a CollectionViewSource without code behind?在 WPF 中,您可以在没有代码的情况下过滤 CollectionViewSource 吗?
【发布时间】:2011-09-21 15:42:00
【问题描述】:

主题真的说明了一切。

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

并不是我不能有代码。它只是对我唠叨。

【问题讨论】:

    标签: c# wpf xaml filter collectionviewsource


    【解决方案1】:

    如果你“足够努力”,你几乎可以在 XAML 中做任何事情,up to writing whole programs in it

    您将永远无法绕过代码背后的问题(好吧,如果您使用库,则不必编写任何库,但应用程序当然仍然依赖它),下面是一个示例,说明您在这种特定情况下可以做什么:

    <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-element-notation 设置该处理程序)

    【讨论】:

    • 你真的测试过吗?上次我尝试时,无法在事件上使用标记扩展......但也许它在 4.0 中发生了变化
    • 这很好,我不知道这个新功能!
    • @H.B.我得到一个例外:过滤器不支持 Type PropertyFilter 的值。
    • @Vishal:它编译了吗?
    • @H.B.不,它不编译。
    【解决方案2】:

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

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

    (注意ICollectionView.Filter 不是CollectionViewSource.Filter 之类的事件,它是Predicate&lt;object&gt; 类型的属性)

    【讨论】:

    • 虽然这可能是正确且有价值的信息,但严格来说,这并不能回答我认为的问题。
    • @H.B.,标题说“没有代码后面”;对我来说,它通常意味着“在 ViewModel 中”,但现在我意识到 OP 专门要求 XAML 解决方案......
    • @Jerry Nixon,这是不是代码隐藏;这是 ViewModel 的代码。当然,除非您将任何 C# 代码视为代码隐藏...
    • +1 因为您的答案不是代码隐藏,而是在 ViewModel 中。
    • @XAMlMAX,这不是 UI 意义上的视图; CollectionView 更像是数据库意义上的视图...
    【解决方案3】:

    WPF 自动创建一个CollectionView——或其派生类型之一,例如ListCollectionViewBindingListCollectionView——每当您将任何IEnumerable 派生源数据绑定到@987654329 时@ 财产。您获得的CollectionView 类型取决于运行时在您提供的数据源上检测到的功能。

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

    自动提供的CollectionView 实例由系统基于每个集合创建和维护(注意:不是 per- UI 控件或per- 绑定目标)。换句话说,您绑定到的每个 s̲o̲u̲r̲c̲e̲ 集合都将有一个全球共享的“默认”视图,并且可以随时检索(或按需创建)这个唯一的 CollectionView 实例再次将相同的“原始”IEnumerable 实例传回静态方法CollectionViewSource.​GetDefaultView()

    CollectionView 是一个 shim,它能够跟踪排序和/或过滤状态而无需实际更改源。因此,如果相同的源数据被多个不同的Binding 使用引用,每个使用不同的CollectionView,它们不会相互干扰。 “默认”视图旨在优化不需要或不需要过滤和排序的非常常见且简单得多的情况。

    简而言之,每个带有数据绑定 ItemsSource 属性的 ItemsControl 都将始终具有排序和过滤功能,这要归功于一些流行的 CollectionView。通过从ItemsControl.Items 属性中获取和操作“默认”CollectionView,您可以轻松地对任何给定的IEnumerable 执行过滤/排序,但请注意,UI 中最终使用该视图的所有数据绑定目标 - - 要么是因为您明确绑定到 CollectionViewSource.GetDefaultView(),要么因为您的源根本不是 CollectionView - 都将共享相同的排序/过滤效果。

    除了将源集合绑定到ItemsControlItemsSource 属性(作为绑定target),您还可以“同时”访问应用过滤器/排序结果的有效集合--公开为CollectionView-派生的实例System.Windows.Controls.ItemCollection--通过绑定来自控件的Items属性(作为绑定)。

    这支持许多简化的 XAML 方案:

    1. 如果对给定的IEnumerable 源拥有一个全局共享的过滤/排序功能就足以满足您的应用程序的需求,那么只需直接绑定到ItemsSource。仍然仅在 XAML 中,然后您可以通过将同一控件上的 Items 属性视为 ItemCollection 来过滤/排序项目绑定来源。它有许多有用的可绑定属性来控制过滤器/排序。如前所述,过滤/排序将在以这种方式绑定到同一源IEnumerable 的所有 UI 元素之间共享。 --或者--

    2. 自己创建并应用一个或多个不同的(非“默认”)CollectionView 实例。这允许每个数据绑定目标具有独立的过滤器/排序设置。这也可以在 XAML 中完成,和/或您可以创建自己的 (List)CollectionView 派生类。这种方法在其他地方得到了很好的介绍,但我想在这里指出的是,在许多情况下,XAML 可以通过使用与ItemsControl.Items 属性数据绑定相同的技术来简化。 /strong>(作为绑定)以便访问有效的CollectionView


    总结:

    仅使用 XAML,您就可以将数据绑定到一个集合,该集合表示任何有效结果当前 CollectionView 对 WPF ItemsControl 进行过滤/排序,方法是将其 Items 属性视为只读绑定。这将是一个System.Windows.Controls.ItemCollection,它公开可绑定/可变属性以控制活动过滤器和排序标准。


    [编辑] - 进一步的想法:

    请注意,在将 IEnumerable 直接绑定到 ItemsSource 的简单情况下,ItemCollection 可以绑定到ItemsControl.Items 将是原始集合CollectionViewSource.GetDefaultView() 的包装。如上所述,在使用 XAML 的情况下,绑定到这个 UI 包装器(通过ItemsControl.Items)是不费吹灰之力的,而不是绑定到它包装的底层视图(通过CollectionViewSource.GetDefaultView ),因为前一种方法为您省去了(在 XAML 中,尴尬的)必须明确提及任何 CollectionView 的麻烦。

    但更进一步,因为 ItemCollection 包装默认 CollectionView,在我看来,即使在 代码隐藏 (选择不那么明显)绑定到 UI 发布的视图可能更实用,因为这最适合数据源 de-facto 运行时功能和它的 UI 控制目标。

    【讨论】:

    • 我非常感谢以清晰的方式讨论概念。谢谢。
    • -1 来自我,因为@Glenn Slayden 应该提供一些实际的实现,至少是相关的细节,而不是太多讨论 - 毕竟,这就是这个网站的全部内容......
    猜你喜欢
    • 2021-01-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-07-16
    • 2020-12-06
    • 2020-10-15
    • 2017-04-01
    相关资源
    最近更新 更多