先决条件:.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
答案 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.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)
将CollectionViewSource
创建为Resource
。
<CollectionViewSource x:Key="CVSKey" Source="{DynamicResource MyArray}"/>
。
将此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;
}
为单独的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();
}