单个集合上的Multiple ItemsControl一次将筛选器应用于所有视图

本文关键字:筛选 一次 应用于 视图 集合 Multiple ItemsControl 单个 | 更新日期: 2023-09-27 17:58:16

先决条件:。净4.5.1

我有三个TreeView控件,它们显示单个集合实例的三个过滤变体。当我尝试在其中一个控件的Items集合上应用筛选器时,此筛选器会自动传播到其他控件,从而阻止我在不同控件上使用不同的筛选器。

有没有任何方法可以在不必同时维护三个集合实例的情况下实现相同的结果?

下面是一个显示问题的示例。前两个ListView直接绑定到同一个集合实例。第三个实例通过CompositeCollection绑定到该实例。第四是独立收藏。当我按下"设置过滤器"按钮时,如果第一个ListView设置为WTest窗口的IsAllowedItem方法,则ItemsControl.Items.Filter属性。之后,第二个istView.Items.Filter属性以某种方式指向相同的方法,而第三个和第四个ListView返回null。另一个效果是,尽管第三个ListView显示了null过滤器,但它的集合仍然被过滤,正如您在运行示例时看到的那样。这种非常奇怪的效果源于ItemCollection类的行为,即当基于所有者元素的ItemsSource属性时,通过CollectionViewSource.GetDefaultCollectionView方法从一些应用程序范围的存储中获取底层CollectionView。我不知道这个实现的原因,但怀疑它的性能。

测试窗口WTest.xaml:

<Window x:Class="Local.WTest"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:s="clr-namespace:System;assembly=mscorlib"
        xmlns:c="clr-namespace:System.Collections;assembly=mscorlib"
        xmlns:local="clr-namespace:Local"
        Name="_WTest" Title="WTest" Height="300" Width="600">
    <Window.Resources>
        <c:ArrayList x:Key="MyArray">
            <s:String>Letter A</s:String>
            <s:String>Letter B</s:String>
            <s:String>Letter C</s:String>
        </c:ArrayList>
        <CompositeCollection x:Key="MyCollection" >
            <CollectionContainer Collection="{StaticResource ResourceKey=MyArray}"/>
        </CompositeCollection>
        <c:ArrayList x:Key="AnotherArray">
            <s:String>Letter A</s:String>
            <s:String>Letter B</s:String>
            <s:String>Letter C</s:String>
        </c:ArrayList>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="*"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
            <ColumnDefinition Width="*"/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Row="0" Grid.Column="0" Name="FilterLabel1"/>
        <TextBlock Grid.Row="0" Grid.Column="1" Name="FilterLabel2"/>
        <TextBlock Grid.Row="0" Grid.Column="2" Name="FilterLabel3"/>
        <TextBlock Grid.Row="0" Grid.Column="3" Name="FilterLabel4"/>
        <ListView Grid.Row="1" Grid.Column="0" Name="View1" ItemsSource="{StaticResource ResourceKey=MyArray}"/>
        <ListView Grid.Row="1" Grid.Column="1" Name="View2" ItemsSource="{StaticResource ResourceKey=MyArray}"/>
        <ListView Grid.Row="1" Grid.Column="2" Name="View3" ItemsSource="{StaticResource ResourceKey=MyCollection}"/>
        <ListView Grid.Row="1" Grid.Column="3" Name="View4" ItemsSource="{StaticResource ResourceKey=AnotherArray}"/>
        <Button Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="4" Content="Set Filter" Click="OnSetFilterButtonClick"/>
    </Grid>
</Window>

WTest.xaml.cs 背后的代码

namespace Local
{
    using System.Windows;
    public partial class WTest : Window
    {
        public WTest()
        {
            InitializeComponent();
            UpdateFilterLabels();
        }
        private bool IsAllowedItem(object item)
        {
            return "Letter A" == (string)item;
        }
        private void OnSetFilterButtonClick(object sender, RoutedEventArgs e)
        {
            View1.Items.Filter = IsAllowedItem;
            UpdateFilterLabels();
        }
        private void UpdateFilterLabels()
        {
            FilterLabel1.Text = (null == View1.Items.Filter) ? "No Filter" : View1.Items.Filter.Method.Name;
            FilterLabel2.Text = (null == View2.Items.Filter) ? "No Filter" : View2.Items.Filter.Method.Name;
            FilterLabel3.Text = (null == View3.Items.Filter) ? "No Filter" : View3.Items.Filter.Method.Name;
            FilterLabel4.Text = (null == View4.Items.Filter) ? "No Filter" : View4.Items.Filter.Method.Name;
        }
    }
}

点击"设置过滤器"按钮后的结果:示例:点击"设置过滤器"按钮

单个集合上的Multiple ItemsControl一次将筛选器应用于所有视图

的结果
  1. CollectionViewSource创建为Resource

    <CollectionViewSource x:Key="CVSKey" Source="{DynamicResource MyArray}"/>
    
  2. 使用此CollectionViewSource作为您的ItemsSource。将View1替换为:

    <!--<ListView Grid.Row="1" Grid.Column="0" Name="View1" ItemsSource="{DynamicResource ResourceKey=MyArray}"/>-->
    <ListView Grid.Row="1" Grid.Column="0" Name="View1" ItemsSource="{Binding Source={StaticResource ResourceKey=CVSKey}}"/>
    

就是这样,现在一切都会按照你的意愿进行。

此外,现在您可以将筛选应用于此CollectionViewSource,而不是View1:

((CollectionViewSource)this.Resources["CVSKey"]).Filter += List_Filter;
void List_Filter(object sender, FilterEventArgs e)
{
    e.Accepted = (e.Item.ToString() == "Letter A") ? true : false;
}

为独立的ListBoxes创建单独的CollectionViewSource,以从同一基础集合创建单独的视图。

在谷歌上搜索CollectionViewSource

OnSetFilterButtonClick方法更改为

private void OnSetFilterButtonClick(object sender, RoutedEventArgs e)
{ 
    //Create a new listview by the ItemsSource,Apply Filter to the new listview
    ListCollectionView listView = new ListCollectionView(View1.ItemsSource as IList); 
    listView.Filter = IsAllowedItem;
    View1.ItemsSource = listView;
    UpdateFilterLabels();
}

我找到了一个简单的解决方案,它不需要在XAML中创建CollectionViewSource资源,也不需要在代码中为每个需要自己过滤器的集合创建ListCollectionView

我的解决方案是使用ValueConverter将ItemsSource源转换为CollectionViewSource.View

项目源转换器:

[ValueConversion(typeof(IEnumerable), typeof(IEnumerable))]
public class ItemsSourceConverter : IValueConverter
{
    public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
    {
        if (value is IEnumerable itemsSource && itemsSource != null)
        {
            return new CollectionViewSource() { Source = itemsSource }.View;
        }
        else
        {
            throw new Exception($"Value must be an {nameof(IEnumerable)}");
        }
    }
    public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
    {
        return DependencyProperty.UnsetValue;
    }
}

XAML:

<Window ...>
    <Window.Resources>
        <local:ItemsSourceConverter x:Key="ItemsSourceConverter"/>
    </Window.Resources>
...
    <ItemsControl Name="View1", 
                  ItemsSource="{Binding Collection1, Converter={StaticResource ItemsSourceConverter}, Mode=OneWay}" />
    <ItemsControl Name="View2", 
                  ItemsSource="{Binding Collection3, Converter={StaticResource ItemsSourceConverter}, Mode=OneWay}" />
    <ItemsControl Name="View3", 
                  ItemsSource="{Binding Collection3, Converter={StaticResource ItemsSourceConverter}, Mode=OneWay}" />
</Window>

代码隐藏:

public partial class MainWindow : Window
{
    ObservableCollection<DataClass> Collection1 { get; private set; }
    ObservableCollection<DataClass> Collection2 { get; private set; }
    ObservableCollection<DataClass> Collection3 { get; private set; }
    public MainWindow()
    {
        InitializeComponent();
    }
    ...
    private void SetFilters()
    {
        View1.Filter = (item) =>
        {
            // Filter logic
        };
        View2.Filter = (item) =>
        {
            // Filter logic
        };
        View2.Filter = (item) =>
        {
            // Filter logic
        };
    }
    ...
}

具有筛选器绑定的MVVM ItemsControl

如果我们想将上述解决方案与MVVM一起使用,我们可以创建一个附加的属性,将ItemsControl.Filter绑定到ViewModel中定义的过滤器。

过滤器附加属性:

public static class CollectionViewExtensions
{
    public static readonly DependencyProperty FilterProperty = DependencyProperty.RegisterAttached(
        "Filter",
        typeof(Predicate<object>),
        typeof(CollectionViewExtensions),
        new PropertyMetadata(default(Predicate<object>), OnFilterChanged));
    public static void SetFilter(ItemsControl element, Predicate<object> value)
    {
        element.SetValue(FilterProperty, value);
    }
    public static Predicate<object> GetFilter(ItemsControl element)
    {
        return (Predicate<object>)element.GetValue(FilterProperty);
    }
    private static void OnFilterChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        if (d is ItemsControl itemsControl && itemsControl.Items.CanFilter)
        {
            if (e.OldValue is Predicate<object> oldPredicate)
            {
                itemsControl.Items.Filter -= oldPredicate;
            }
            if (e.NewValue is Predicate<object> newPredicate)
            {
                itemsControl.Items.Filter += newPredicate;
            }
        }
    }
}

来源:https://stackoverflow.com/a/39438710/10927863

XAML:

<Window ...>
    <Window.Resources>
        <local:ItemsSourceConverter x:Key="ItemsSourceConverter"/>
    </Window.Resources>
...
    <ItemsControl ItemsSource="{Binding Collection1, Converter={StaticResource ItemsSourceConverter}, Mode=OneWay}" 
                  local:CollectionViewExtensions.Filter="{Binding Filter1}"/>
    <ItemsControl ItemsSource="{Binding Collection3, Converter={StaticResource ItemsSourceConverter}, Mode=OneWay}" 
                  local:CollectionViewExtensions.Filter="{Binding Filter2}"/>
    <ItemsControl ItemsSource="{Binding Collection3, Converter={StaticResource ItemsSourceConverter}, Mode=OneWay}" 
                  local:CollectionViewExtensions.Filter="{Binding Filter3}"/>
</Window>

视图模型:

public class ViewModel
{
    ObservableCollection<DataClass> Collection1 { get; private set; }
    ObservableCollection<DataClass> Collection2 { get; private set; }
    ObservableCollection<DataClass> Collection3 { get; private set; }
    public Predicate<object> Filter1 { get; private set; }
    public Predicate<object> Filter2 { get; private set; }
    public Predicate<object> Filter3 { get; private set; }
    ...
    private void SetFilters()
    {
        Filter1 = (item) =>
        {
            // Filter logic
        };
        Filter2 = (item) =>
        {
            // Filter logic
        };
        Filter3 = (item) =>
        {
            // Filter logic
        };
    }
    ...
}