c# - - - WPF:筛选ItemCollection的ComboBox也会筛选绑定到同一ComboBoxes的其他ItemsSource

给定一个"customer"实体:


public class CustomerEntity: EntityBase


{


 public Dictionary<int, string> ClientsOfCustomer = new Dictionary<int, string>();



 public CustomerEntity() 


 {


 // Load ClientsOfCustomer...


 }


}



以及两个或多个WPF ComboBox,它们的ItemsSourceProperty绑定到同一源(例如,上述"Customer"实体的属性):


var comboBox1 = new ComboBox();


var comboBox2 = new ComboBox();


comboBox1.SetBinding(ItemsControl.ItemsSourceProperty,


 new Binding(itemsSourceProperty) { ElementName ="ClientsOfCustomer" });


comboBox2.SetBinding(ItemsControl.ItemsSourceProperty,


 new Binding(itemsSourceProperty) { ElementName ="ClientsOfCustomer" });



上面的操作是在我们实际拥有底层对象的实例之前完成的,


var customer = new CustomerEntity();


parentPageOfComboboxes.DataContext = customer;



如果我们然后过滤ItemCollection中的任意一个ComboBox:


comboBox1.Items.Filter = i => ((KeyValuePair<int, string>)i).Value.StartsWith("a");



最终也过滤了另一个ComboBox。

CollectionViewSource.GetDefaultCollectionView( _itemsSource, ModelParent, GetSourceItem)

Items = new ListCollectionView(...);除了非常粗糙的循环遍历所有生成的UI元素和更改绑定源之外,还有什么解决方案?

时间:

你是正确的,每个绑定到同一源集合的ItemsControl共享此集合的通用默认ICollectionView,

解决方案是为每个ICollectionView为每个ComboBox创建专用实例,可以使用CollectionViewSource在XAML中定义它们,如下面的示例所示,如果Button触发了过滤器,你可以使用命令绑定这个Button视图。

否则,将ICollectionView定义为视图模型中的属性,并绑定到它们,现在可以直接设置筛选谓词,而无需从视图中使用触发器(命令)。

ViewModel.cs


public ObservableCollection<string> Data source { get; set; }



ICommand ApplyFilterCommand => new RelayCommand(FilterCollectionView);



private void FilterCollectionView(object param)


{ 


 CollectionView collectionView = (param as CollectionViewSource).View;


 collectionView.Filter = item => item.StartsWith("a");


 collectionView.Refresh();


} 



MainWindow.xaml


<Window>


 <Window.DataContext>


 <ViewModel />


 <Window.DataContext>


 <Window.Resources>


 <CollectionViewSource x:Key="FirstCollectionSource" 


 Source="{Binding DataSource}" />


 <CollectionViewSource x:Key="SecondCollectionSource" 


 Source="{Binding DataSource}" />


 <Window.Resources>



 <StackPanel>


 <ComboBox ItemsSource="{Binding Source={StaticResource FirstCollectionSource}}" />


 <ComboBox ItemsSource="{Binding Source={StaticResource SecondCollectionSource}}" />



 <!-- Filter the second ComboBox -->


 <Button Command="{Binding ApplyFilterCommand}"


 CommandParameter="{StaticResource SecondCollectionSource}" />


 </StackPanel>


<Window>



难看的解决方案

我创建了自己的MyComboBox,它从System.Windows.Controls.ComboBox.中继承了MyComboBox i重写了ItemsSourceProperty的元数据,因此只要更改了


public partial class MyComboBox : ComboBox


 {


 static MyComboBox()


 {


 ItemsSourceProperty.OverrideMetadata(typeof(MyComboBox),


 new FrameworkPropertyMetadata((IEnumerable)null, new PropertyChangedCallback(OnItemsSourceChanged)));


 }



 private static void OnItemsSourceChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)


 {


 ItemsControl ic = (ItemsControl)d;


 IEnumerable oldValue = (IEnumerable)e.OldValue;


 IEnumerable newValue = (IEnumerable)e.NewValue;



 // We get into infinite recursion land without the following condition:


 if (newValue.GetType() == typeof(Dictionary<int, string>))


 {


 var cvs = new CollectionViewSource() { Source = ((Dictionary<int, string>)newValue) };


 ic.ItemsSource = cvs.View;


 }


 }


 ...



请注意if (newValue.GetType() == typeof(Dictionary<int, string>))条件是必需的,否则将进入无限递归循环( MyComboBox集合ItemsControl.ItemsSource从而更改ItemsSourceProperty,然后触发ic.ONIMSOURCE已更改( oldValue,newValue )返回到MyComboBox.OnTimesSourceChanged等等。

另一个警告:简单地ic.ItemsSource = new ListCollectionView(((Dictionary<int, string>)newValue).ToList());除非对基础ObservableCollection的更改不会更新绑定combobox,否则有效,有必要创建新的CollectionViewSource,设置源,并绑定到视图,如上所示,这也是在CollectionView的官方文档

不应在代码中创建此类的对象,若要为仅实现IEnumerable的集合创建集合视图,请创建一个CollectionViewSource对象,将集合添加到Source属性,并从集合视图获取属性。

上面的程序可运行,但是很丑陋。

...