在 WPF 中,您可以在没有代码隐藏的情况下过滤 CollectionViewSource 吗?

发布于 2024-11-16 16:50:19 字数 177 浏览 1 评论 0 原文

确实,主题说明了一切。

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

这并不是说我不能有代码。它只是让我烦恼。

Really the subject says it all.

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

It's not that I can't have code behind. It just nags at me.

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

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

发布评论

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

评论(4

孤君无依 2024-11-23 16:50:19

如果您“足够努力”,您可以在 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 元素表示法设置该处理程序)

You can do pretty much anything in XAML if you "try hard enough", up to writing whole programs in it.

You will never get around code behind (well, if you use libraries you don't have to write any but the application still relies on it of course), here's an example of what you can do in this specific case:

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

Markup extensions are your friend if you want to do something in XAML.

(You might want to spell out the name of the extension, i.e. me:FilterExtension as the on-the-fly checking in Visual Studio may complain without reason, it still compiles and runs of course but the warnings might be annoying.
Also do not expect the CollectionViewSource.Filter to show up in the IntelliSense, it does not expect you to set that handler via XML-element-notation)

仅此而已 2024-11-23 16:50:19

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

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

请注意,ICollectionView.Filter 不是像 ICollectionView.Filter 这样的事件code>CollectionViewSource.Filter,它是 Predicate类型的属性)

Actually you don't even need access to the CollectionViewSource instance, you can filter the source collection directly in the ViewModel:

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

(note that ICollectionView.Filter is not an event like CollectionViewSource.Filter, it's a property of type Predicate<object>)

拥抱没勇气 2024-11-23 16:50:19

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

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

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

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

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

除了将源集合绑定到 ItemsControl 的 ItemsSource 属性之外,此主题中不常提及的内容(作为绑定目标),您还可以“同时”访问已应用的过滤/排序结果的有效集合--暴露为 CollectionView 派生实例System.Windows.Controls.ItemCollection——通过绑定来自控件的Items属性(如绑定来源)。

这可以实现许多简化的 XAML 场景:

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


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


摘要:

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


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

请注意,在将 IEnumerable 直接绑定到 ItemsSource 的简单情况下,< a href="https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.itemcollection" rel="nofollow noreferrer">ItemCollection 您可以绑定到ItemsControl.Items 将是原始集合的 CollectionViewSource.GetDefaultView() 的包装器。如上所述,在使用 XAML 的情况下,绑定到此 UI 包装器(通过 ItemsControl.Items)是理所当然的事情,而不是绑定到底层视图它包装(通过CollectionViewSource.GetDefaultView),因为前一种方法为您省去了(在XAML中,尴尬的)必须显式提及任何CollectionView 根本没有。

但更进一步,因为 ItemCollection 包装了默认的 CollectionView,在我看来,即使在代码隐藏< /strong>(其中选择不太明显)绑定到 UI 发布的视图可能也更实用,因为这样最适合 事实上 的运行时功能两者都是数据源它的 UI 控制目标。

WPF automatically creates a CollectionView—or one of its derived types such as ListCollectionView, or BindingListCollectionView—whenever you bind any IEnumerable-derived source data to an ItemsControl.ItemsSource property. Which type of CollectionView you get depends on the capabilities detected at runtime on the data source you provide.

Sometimes even if you try to explicitly bind your own specific CollectionView-derived type to an ItemsSource, the WPF data binding engine may wrap it (using the internal type CollectionViewProxy).

The automatically-supplied CollectionView instance is created and maintained by the system on a per collection basis (note: not per- UI control or per- bound target). In other words, there will be exactly one globally-shared "Default" view for each s̲o̲u̲r̲c̲e̲ collection that you bind to, and this unique CollectionView instance can be retrieved (or created on demand) at any time by passing the same "original" IEnumerable instance back to the static method CollectionViewSource.​GetDefaultView() again.

CollectionView is a shim that is able to keep track of the sorting and/or filtering state without actually altering the source. Therefore, if the same source data is referenced by several different Binding usages each with a different CollectionView, they won't interfere with each other. The "Default" view is intended to optimize the very common--and much simpler--situations where filtering and sorting are not required or expected.

In short, every ItemsControl with a data-bound ItemsSource property will always end up with sorting and filtering capabilities, courtesy of some prevailing CollectionView. You can easily perform filtering/sorting for any given IEnumerable by grabbing and manipulating the "Default" CollectionView from the ItemsControl.Items property, but note that all the data-bound targets in the UI that end up using that view--either because you explicitly bound to CollectionViewSource.GetDefaultView(), or because your source wasn't a CollectionView at all--will all share those same sorting/filtering effects.

What's not often mentioned on this subject is, in addition to binding the source collection to the ItemsSource property of an ItemsControl (as a binding target), you can also "simultaneously" access the effective collection of applied filter/sort results--exposed as a CollectionView-derived instance of System.Windows.Controls.ItemCollection--by binding from the Control's Items property (as a binding source).

This enables numerous simplified XAML scenarios:

  1. If having a single, globally-shared filter/sort capability for the given IEnumerable source is sufficient for your app, then just bind directly to ItemsSource. Still in XAML only, you can then filter/sort the items by treating the Items property on the same Control as an ItemCollection binding source. It has many useful bindable properties for controlling the filter/sort. As noted, filtering/sorting will be shared amongst all UI elements which are bound to the same source IEnumerable in this way.   --or--

  2. Create and apply one or more distinct (non-"Default") CollectionView instances yourself. This allows each data-bound target to have independent filter/sort settings. This can also be done in XAML, and/or you can create your own (List)CollectionView-derived classes. This type of approach is well-covered elsewhere, but what I wanted to point out here is that in many cases the XAML can be simplified by using the same technique of data-binding to the ItemsControl.Items property (as a binding source) in order to access the effective CollectionView.


Summary:

With XAML alone, you can data-bind to a collection representing the effective results of any current CollectionView filtering/sorting on a WPF ItemsControl by treating its Items property as a read-only binding source. This will be a System.Windows.Controls.ItemCollection which exposes bindable/mutable properties for controlling the active filter and sort criteria.


[edit] - further thoughts:

Note that in the simple case of binding your IEnumerable directly to ItemsSource, the ItemCollection you can bind to at ItemsControl.Items will be a wrapper on the original collection's CollectionViewSource.GetDefaultView(). As discussed above, in the case of XAML usage it's a no-brainer to bind to this UI wrapper (via ItemsControl.Items), as opposed to binding to the underlying view it wraps (via CollectionViewSource.GetDefaultView), since the former approach saves you the (in XAML, awkward) trouble of having to explicitly mention any CollectionView at all.

But further, because that ItemCollection wraps the default CollectionView, it seems to me that, even in code-behind (where the choice is less obvious) it's perhaps also more utilitarian to bind to the view promulgated by the UI, since such is best attuned to the de-facto runtime capabilities of both the data source and its UI control target.

嗫嚅 2024-11-23 16:50:19

我在使用 接受的解决方案时遇到了以下问题:

严重性 代码 描述
错误 XDG0012 546730/hb">HB 使用 .NET FrameWork 4.6.1(旧的 成员“过滤器”无法识别或不可访问。
错误 无法在元素“FilterExtension”上设置内容属性“过滤器”。 “过滤器”的访问级别不正确或其程序集不允许访问。第 xx 行 yy 位置。

可以轻松解决此问题

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

通过更改为

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

I had the following issues with the accepted solution provided by H.B. using .NET FrameWork 4.6.1 (old, I know, but unfortunately a limitation for my current situation):

Severity Code Description
Error XDG0012 The member "Filter" is not recognized or is not accessible.
Error Cannot set content property 'Filters' on element 'FilterExtension'. 'Filters' has incorrect access level or its assembly does not allow access. Line xx Position yy.

This was easily resolved by changing

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

to

public Collection<IFilter> Filters { get { return _filters; } }
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文