【问题标题】:Multiple ItemsControl on single collection applies filter to all views at once单个集合上的多个 ItemsControl 一次将过滤器应用于所有视图
【发布时间】:2020-06-04 20:15:39
【问题描述】:

先决条件:.NET 4.5.1

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

有没有什么方法可以实现相同的结果而不必同时维护三个集合实例?

下面是一个显示问题的示例。前两个 ListView 直接绑定到同一个集合实例。第三个通过CompositeCollection 绑定到该实例。第四种必然是独立收藏。当我按下“设置过滤器”按钮 ItemsControl.Items.Filter 属性时,如果第一个 ListView 设置为 WTest 窗口的 IsAllowedItem 方法。在这第二个istView.Items.Filter 属性以某种方式指向相同的方法之后,而第三个和第四个 ListView 返回 null。另一个效果是,尽管第三个 ListView 显示空过滤器,但它的集合仍然被过滤,如果您运行该示例,您可以看到。这种非常奇怪的效果源于 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;
        }
    }
}

单击“设置过滤器”按钮后的结果: Example: result of clicking "Set Filter" button

【问题讨论】:

    标签: c# wpf xaml listview


    【解决方案1】:
    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

    【讨论】:

    • 很好的答案。读者可以查看this 了解更多信息。关键是,在 WPF 应用程序中,所有集合都有一个关联的默认集合视图。
    【解决方案2】:

    更改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();
    }
    

    【讨论】:

    • 一个collection可以创建多个collectionview,一个listbox绑定一个collectionview。
    【解决方案3】:

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

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

    ItemsSourceConverter:

    [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
            };
        }
    
        ...
    }
    

    【讨论】:

      猜你喜欢
      • 2014-03-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-16
      • 2020-09-17
      • 1970-01-01
      • 2022-09-23
      • 2011-09-05
      相关资源
      最近更新 更多