WPF - CollectionViewSource Filter event in a DataTemplate not working
I'm seeing some really weird behavior where WPF isn't doing what I expect it to do. I've managed to boil the problem down the following bit of code:
XAML:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<TabControl x:Name="tabControl">
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type List}">
<UserControl>
<UserControl.Resources>
<CollectionViewSource x:Key="filteredValues" Source="{Binding}" Filter="CollectionViewSource_Filter" />
</UserControl.Resources>
<ListBox ItemsSource="{Binding Source={StaticResource filteredValues}}" />
开发者_运维问答
</UserControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Window>
Code-behind:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;
namespace WpfApplication3
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.tabControl.ItemsSource = new List<List<string>>()
{
new List<string>() { "a", "b", "c"},
};
}
private void CollectionViewSource_Filter(object sender, FilterEventArgs e)
{
string item = (string)e.Item;
e.Accepted = item.StartsWith("b");
}
}
}
I'd expect that this code would result in a TabControl
with a single tab that has a ListBox
with a single item that says "b." But, instead, I get a ListBox
with all 3 of the strings. Setting a breakpoint inside CollectionViewSource_Filter
shows that the filter never even runs.
What's going on here? Why isn't the filter working?
I was thinking maybe it has something to do with the CollectionViewSource
being a resource in a DataTemplate
. The events on the ListBox
fire properly. If the UserControl
is not part of a DataTemplate
, the Filter event works fine.
EDIT:
For example, the following works as expected, with the List being filtered as expected.
XAML:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<UserControl>
<UserControl.Resources>
<CollectionViewSource x:Key="filteredValues" Source="{Binding}" Filter="CollectionViewSource_Filter" />
</UserControl.Resources>
<ListBox ItemsSource="{Binding Source={StaticResource filteredValues}}" />
</UserControl>
</Window>
Code behind:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;
namespace WpfApplication3
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new List<string>() { "a", "b", "c" };
}
private void CollectionViewSource_Filter(object sender, FilterEventArgs e)
{
string item = (string)e.Item;
e.Accepted = item.StartsWith("b");
}
}
}
Well, I don't know why it doesn't work, but at this point, I'm assuming it's a Microsoft bug. I'll probably be filing a Connect report shortly.
To work around the bug, I did the following. I created a subclass of CollectionViewSource
like this:
using System.Windows.Data;
namespace WpfApplication3
{
internal class CustomFilteredCollectionViewSource : CollectionViewSource
{
public CustomFilteredCollectionViewSource()
: base()
{
this.Filter += CustomFilter;
}
private void CustomFilter(object sender, FilterEventArgs args)
{
string item = (string)args.Item;
args.Accepted = item.StartsWith("b");
}
}
}
I then replaced
<CollectionViewSource x:Key="filteredValues" Source="{Binding}" Filter="CollectionViewSource_Filter" />
with
<local:CustomFilteredCollectionViewSource x:Key="filteredValues" Source="{Binding}" />
and it now works perfectly.
You're using the Filter as if it's a property on the CollectionViewSource which always gets used.
It isn't. It's an event. It says, "When you filter this CollectionViewSource, this event will be called." It will respond to requests to filter, but won't trigger those requests itself.
I don't know a huge amount about CollectionViewSource, but I assume you'd have to bind it to a filtering control in order for this event to be triggered, like a Grid which allowed filtering.
I came across this same issue today and found a much easier workaround.
The issue is that the Filter event is not subscribed properly for some reason. You can get around this by subscribing to Filter in the Loaded event of the control which contains the CollectionViewSource as a resource.
Applying this to the example in the question you get
XAML:
<Window x:Class="WpfApplication3.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<TabControl x:Name="tabControl">
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type List}">
<UserControl Loaded="UserControl_OnLoaded">
<UserControl.Resources>
<CollectionViewSource x:Key="filteredValues" Source="{Binding}"/>
</UserControl.Resources>
<ListBox ItemsSource="{Binding Source={StaticResource filteredValues}}" />
</UserControl>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
Code Behind:
using System.Collections.Generic;
using System.Windows;
using System.Windows.Data;
namespace WpfApplication3
{
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.tabControl.ItemsSource = new List<List<string>>()
{
new List<string>() { "a", "b", "c"},
};
}
private void CollectionViewSource_Filter(object sender, FilterEventArgs e)
{
string item = (string)e.Item;
e.Accepted = item.StartsWith("b");
}
private void UserControl_OnLoaded(object sender, RoutedEventArgs e)
{
var control = (UserControl) sender;
var cvs = (CollectionViewSource) control.Resources["filteredValues"];
cvs.Filter += CollectionViewSource_Filter;
}
}
}
精彩评论