В WPF можно фильтровать CollectionViewSource без кода?

На самом деле предмет говорит все.

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

Не то, чтобы у меня не было кода. Это просто придирается ко мне.

Ответ 1

Вы можете сделать что угодно в 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-элементную нотацию)

Ответ 2

На самом деле вам даже не нужен доступ к экземпляру CollectionViewSource, вы можете напрямую фильтровать исходную коллекцию в ViewModel:

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

(обратите внимание, что ICollectionView.Filter не является событием вроде CollectionViewSource.Filter, это свойство типа Predicate<object>)

Ответ 3

WPF автоматически создает производный тип CollectionView --or, например ListCollectionView, BindingListCollectionView и т.д. (Это зависит от возможностей, обнаруженных в вашей исходной коллекции) --for any ItemsSource, если вы не предоставите его при привязке источника IEnumerable -derived непосредственно к свойству ItemsControl.ItemsSource.

Этот автоматически предоставленный экземпляр CollectionView создается и поддерживается системой на основе для коллекции (примечание: не для элемента управления или цели привязки для каждого пользовательского интерфейса). Другими словами, для каждого s̲o̲u̲r̲c̲e̲, к которому вы привязываете, будет ровно один глобально общий вид "Default", и этот уникальный экземпляр CollectionView может быть извлечен (или создан по требованию) в любое время, передав IEnumerable статический метод CollectionViewSource.GetDefaultView().

Иногда, даже если вы пытаетесь явно привязать свой собственный тип CollectionView -derived к ItemsSource, механизм привязки данных WPF может обернуть его (используя внутренний тип CollectionViewProxy).

В любом случае, каждый ItemsControl со свойством ItemsSource с привязкой к данным всегда будет иметь возможность сортировки и фильтрации, благодаря некоторому преобладающему CollectionView. Вы можете легко выполнить фильтрацию/сортировку для любого заданного IEnumerable путем захвата и манипулирования его "По умолчанию" CollectionView, но обратите внимание, что все связанные с данными цели в пользовательском интерфейсе, которые в конечном итоге используют это представление - либо потому, что вы явно привязали на CollectionViewSource.GetDefaultView(), или потому что вы вообще не предоставили никакого представления - все будут иметь те же самые эффекты сортировки/фильтрации.

Что не часто упоминается по этому вопросу, помимо привязки исходной коллекции к свойству ItemsSource объекта ItemsControl (в качестве цели привязки), вы также можете "одновременно" получить доступ к эффективный сбор примененных результатов фильтрации/сортировки --exposed как CollectionView -derived экземпляра System.Windows.Controls.ItemCollection --by привязки из элемента управления Items свойство (в качестве источника привязки).

Это позволяет использовать многочисленные упрощенные сценарии XAML:

  1. Если для вашего приложения достаточно наличия единой, общей для всех возможности фильтрации/сортировки для данного источника IEnumerable, просто подключитесь непосредственно к ItemsSource. Тем не менее, только в XAML вы можете фильтровать/сортировать элементы, обрабатывая свойство Items в том же элементе управления, что и источник привязки ItemCollection. Он имеет много полезных привязываемых свойств для управления фильтром/сортировкой. Как уже отмечалось, фильтрация/сортировка будут совместно использоваться всеми элементами пользовательского интерфейса, которые связаны таким же образом с одним источником IEnumerable. --or -

  2. Создайте и примените один или несколько отдельных (non- "по умолчанию") CollectionView экземпляров самостоятельно. Это позволяет каждой связанной с данными цели иметь независимые настройки фильтра/сортировки. Это также можно сделать в XAML, и/или вы можете создать свои собственные классы (List)CollectionView -derived. Этот тип подхода хорошо освещен в другом месте, но я хотел бы отметить, что во многих случаях XAML можно упростить, используя ту же технику привязки данных к свойству ItemsControl.Items ( в качестве источника привязки) для доступа к эффективному CollectionView.


Сводка:

С помощью XAML вы можете привязать данные к коллекции, представляющей эффективные результаты любой текущей фильтрации/сортировки CollectionView в WPF ItemsControl, обрабатывая ее свойство Items как привязка только для чтения источник. Это будет System.Windows.Controls.ItemCollection, который предоставляет привязываемые/изменяемые свойства для управления активным фильтром и критериями сортировки.



[edit] - дальнейшие размышления:

Обратите внимание, что в простом случае привязки вашего IEnumerable непосредственно к ItemsSource, ItemCollection, к которому вы можете привязаться в ItemsControl.Items, будет быть оберткой в оригинальной коллекции CollectionViewSource.GetDefaultView(). Как обсуждалось выше, в случае использования XAML связывание с этой оболочкой пользовательского интерфейса не представляет никакой сложности (в отличие от ItemsControl.Items), в отличие от связывания с базовым представлением, которое он переносит (через CollectionViewSource.GetDefaultView) поскольку первый подход избавляет вас от (в XAML, неудобной) проблемы с явным упоминанием какого-либо CollectionView вообще.

Но, кроме того, поскольку ItemCollection оборачивает значение по умолчанию CollectionView, мне кажется, что даже в коде позади (где выбор менее очевиден), возможно, также более практично привязывать к представлению обнародован пользовательским интерфейсом, поскольку он лучше всего настроен на фактические возможности времени выполнения как источника данных, так и его целевого элемента управления пользовательским интерфейсом.