单个集合上的多个ItemsControl一次对所有视图应用过滤器

时间:2016-07-23 14:19:41

标签: c# wpf listview

先决条件:.NET 4.5.1

我有3个TreeView控件,显示单个集合实例的树过滤变体。当我尝试对其中一个控件的Items集合应用过滤器时,此过滤器会自动传播到其他控件,这会阻止我在不同的控件上使用不同的过滤器。 有没有办法在不必同时维护集合的树实例的情况下实现相同的结果?

显示问题的示例如下。前两个ListView直接绑定到同一个集合实例。第三个通过CompositeCollection绑定到该实例。第四个必然是独立收藏。当我按下#34;设置过滤器"如果第一个ListView设置为WTest窗口的IsAllowedItem方法,则按钮ItemsControl.Items.Filter属性。在第二个ListView.Items.Filter属性以某种方式指向相同的方法,而第三个和第四个ListView返回null。另一个影响是,虽然第三个ListView显示空过滤器,但仍会过滤其集合,因为您可以看到是否运行该示例。这种非常奇怪的效果源于ItemCollection类的行为,当基于Owner元素的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;
        }
    }
}

&#34; Set Filter&#34;单击按钮: Example: result of clicking "Set Filter" button

3 个答案:

答案 0 :(得分:2)

我找到了一个简单的解决方案,不需要为每个需要自己过滤器的集合在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>

ViewModel:

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

    ...
}

答案 1 :(得分: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}}"/>
    
  3. 多数民众赞成,现在一切都会按照您的意愿运作。

    此外,现在您可以对此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;
        }
    

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

    在Google上搜索CollectionViewSource

答案 2 :(得分:0)

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