In WPF can you filter a CollectionViewSource without code behind? In WPF can you filter a CollectionViewSource without code behind? wpf wpf

In WPF can you filter a CollectionViewSource without code behind?


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)


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>)


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.