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

На самом деле в теме все сказано.

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

Дело не в том, что у меня не может быть кода. Это просто грызет меня.


person Jerry Nixon    schedule 23.06.2011    source источник


Ответы (3)


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

person H.B.    schedule 24.06.2011
comment
Вы на самом деле тестировали это? В прошлый раз, когда я пытался, было невозможно использовать расширение разметки для события... но, возможно, оно изменилось в версии 4.0. - person Thomas Levesque; 24.06.2011
comment
Это здорово, я не знал об этой новой функции! - person Thomas Levesque; 24.06.2011
comment
@Х.Б. Я получаю исключение: фильтры не поддерживают значения Type PropertyFilter. - person Vishal; 26.11.2014
comment
@Vishal: компилируется? - person H.B.; 26.11.2014
comment
@Х.Б. Нет, не компилируется. - person Vishal; 26.11.2014
comment
@Vishal: Вы правильно установили атрибут ContentProperty? - person H.B.; 26.11.2014
comment
@Х.Б. Я просто скопировал ваш код и xaml. Также внесены изменения в имена в соответствии с моим проектом. - person Vishal; 26.11.2014
comment
@Х.Б. Я также получаю сообщение об ошибке: Тип me:Filter не найден. Подтвердите это......... - person Vishal; 26.11.2014
comment
@Х.Б. Ошибка о PropertyFilter исчезла автоматически. Теперь я получил новую ошибку: в разметке можно использовать только общедоступные или внутренние классы. Тип FilterExtension не является общедоступным или внутренним. - person Vishal; 26.11.2014
comment
@Vishal: Тогда опубликуй это? (Хотя по умолчанию он должен быть внутренним, что, конечно, не поможет вам, если вы попытаетесь использовать его из другой сборки) - person H.B.; 26.11.2014
comment
@Х.Б. Я сделал класс общедоступным. В дополнение к этому я изменил свойство Filters на Collection‹IFilter› вместо ICollection‹IFilter›, добавил к этому свойству сеттер и удалил readonly из _filters. Теперь он работает нормально. Спасибо за помощь мне. - person Vishal; 26.11.2014
comment
@Vishal: создание устанавливаемого свойства не обязательно, если это тип, в котором система может добавлять элементы в текущий экземпляр. Что ж, если это поможет в вашем случае, это лучше, чем ничего. - person H.B.; 26.11.2014
comment
@Х.Б. В моем случае, если я устанавливаю значение свойства Value PropertyFilter в статическую строку, все работает нормально. И когда я меняю его на привязку к моему свойству ViewModel строки типа, он перестает сортировать. Короче говоря, у меня есть textBox и DataGrid. Когда я набираю что-то в текстовом поле, я хочу фильтровать DataGrid. - person Vishal; 26.11.2014
comment
@Vishal: Вероятно, контекста привязки нет, знаете ли вы, как отлаживать привязки? Если вы считаете, что сделали все, чтобы исследовать проблему, но все еще застряли, задайте отдельный вопрос. Я не буду объяснять систему привязки в этом разделе комментариев. - person H.B.; 26.11.2014
comment
@Х.Б. Хорошо спасибо. Я каждый раз смотрю на ошибки привязки в окне вывода. Если есть лучший способ, то можете ли вы сказать мне? - person Vishal; 26.11.2014
comment
@Vishal: это самая важная часть, вы можете уточнить детали только с помощью TraceLevel, что обычно не требуется. Что ж, вам также нужно понять, что означают различные ошибки и каковы общие причины. - person H.B.; 26.11.2014
comment
@Х.Б. Хорошо, я проверю это. Спасибо за ваше драгоценное время и помощь. - person Vishal; 26.11.2014
comment
@Х.Б. Я разместил здесь новый вопрос: stackoverflow.com/questions/27157160/ . Если вы станете свободными, пожалуйста, взгляните на это. - person Vishal; 26.11.2014

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

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

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

person Thomas Levesque    schedule 24.06.2011
comment
Хотя это может быть правильной и ценной информацией и все такое, это, строго говоря, не отвечает на вопрос, который был задан, я думаю. - person H.B.; 24.06.2011
comment
@HB, в заголовке указано без кода; для меня это обычно означает в ViewModel, но теперь я понимаю, что OP специально запросил решение XAML... - person Thomas Levesque; 24.06.2011
comment
@ Джерри Никсон, это не программный код; это код ViewModel. Если, конечно, вы не считаете любой код C# кодом программной части... - person Thomas Levesque; 25.06.2011
comment
+1, потому что ваш ответ НЕ является кодом программной части, он находится в ViewModel. - person Riegardt Steyn; 22.05.2013
comment
Если позволите, ViewModel, предоставляющий представление с представлением, звучит как Controller. - person XAMlMAX; 21.07.2014
comment
@XAMlMAX, это не представление в смысле пользовательского интерфейса; CollectionView больше похож на представление в смысле БД... - person Thomas Levesque; 21.07.2014
comment
Я оставлю свой предыдущий комментарий для других людей, которые могут задать аналогичный вопрос. спасибо @ThomasLevesque. - person XAMlMAX; 21.07.2014

WPF автоматически создает CollectionView — или один из его производных типов, таких как ListCollectionView или BindingListCollectionView, — всякий раз, когда вы привязываете любые исходные данные, производные от IEnumerable, к свойству ItemsControl.ItemsSource. Какой тип CollectionView вы получите, зависит от возможностей, обнаруженных во время выполнения в предоставленном вами источнике данных.

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

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

CollectionView — это прокладка, способная отслеживать состояние сортировки и/или фильтрации без фактического изменения источника. Следовательно, если на одни и те же исходные данные ссылаются несколько разных Binding использований, каждое из которых использует разные CollectionView, они не будут мешать друг другу. Представление по умолчанию предназначено для оптимизации очень распространенных и гораздо более простых ситуаций, когда фильтрация и сортировка не требуются или не ожидаются.

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

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

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

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

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


Вывод.

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


[edit] — дополнительные мысли:

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

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

person Glenn Slayden    schedule 05.06.2018
comment
Я очень ценю обсуждение концепции в ясной форме. Спасибо. - person Alan Wayne; 15.07.2018
comment
-1 от меня, поскольку вместо долгого обсуждения @Glenn Slayden должен был предоставить некоторую реальную реализацию, по крайней мере, соответствующие детали - в конце концов, это то, о чем этот сайт... - person Yoda; 28.05.2020