Search code examples
wpfdatatemplatecollectionviewsource

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");
        }
    }
}

Solution

  • 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.