问题
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.
回答1:
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)
回答2:
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>
)
回答3:
WPF automatically creates a CollectionView--or derived type such as ListCollectionView
, BindingListCollectionView
, etc. (it depends on the capabilities detected on your source collection)--for any ItemsSource
binding, if you don't supply one when you bind your IEnumerable
-derived source directly to an ItemsControl.ItemsSource
property.
This automatically-supplied CollectionView
instance is created and maintained by the system on a per collection basis (note: not per-UI control or binding target). In other words, there will be exactly one globally-shared "Default" view for each s̲o̲u̲r̲c̲e̲ that you bind to, and this unique CollectionView
instance can be retrieved (or created on demand) at any time by passing the IEnumerable
to the static method CollectionViewSource.GetDefaultView()
.
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
).
In any case, 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 its "Default" CollectionView
, 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 you didn't provide any view 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 anItemsControl
(as a binding target), you can also "simultaneously" access the effective collection of applied filter/sort results--exposed as aCollectionView
-derived instance of System.Windows.Controls.ItemCollection--by binding from the Control'sItems
property (as a binding source).
This enables numerous simplified XAML scenarios:
If having a single, globally-shared filter/sort capability for the given
IEnumerable
source is sufficient for your app, then just bind directly toItemsSource
. Still in XAML only, you can then filter/sort the items by treating theItems
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 sourceIEnumerable
in this way. --or--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 theItemsControl.Items
property (as a binding source) in order to access the effectiveCollectionView
.
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.
来源:https://stackoverflow.com/questions/6461826/in-wpf-can-you-filter-a-collectionviewsource-without-code-behind