我正在使用由ListView和一些ComboBox组成的MVVM设计模式创建WPF应用程序。 ComboBox用于过滤ListView。我想要完成的是使用相关ListView列中的项填充组合框。换句话说,如果我的ListView具有Column1,Column2和Column3,我希望ComboBox1显示Column1中的所有UNIQUE项。一旦在ComboBox1中选择了一个项目,我希望根据ComboBox1的选择过滤ComboBox2和ComboBox3中的项目,这意味着ComboBox2和ComboBox3只能包含有效的选择。如果在ASP.NET中使用AJAX工具包,这有点类似于CascadingDropDown控件,除了用户可以随机选择任何ComboBox,而不是按顺序。
我的第一个想法是将ComboBox绑定到ListView绑定的同一ListCollectionView,并将DisplayMemberPath设置为适当的列。就ListView和ComboBox一起过滤而言,这很有效,但是它显示了ComboBox中的所有项目,而不仅仅是唯一的项目(显然)。所以我的下一个想法是使用ValueConverter只返回唯一的唯一项目,但我还没有成功。
仅供参考:我阅读了Colin Eberhardt关于在CodeProject上向ListView添加AutoFilter的帖子,但他的方法遍历整个ListView中的每个项目,并将唯一的项目添加到集合中。虽然这种方法有效,但对于大型列表来说似乎很慢。
关于如何优雅地实现这一目标的任何建议?谢谢!
代码示例:
<ListView ItemsSource="{Binding Products}" SelectedItem="{Binding SelectedProduct}">
<ListView.View>
<GridView>
<GridViewColumn Header="Item" Width="100" DisplayMemberBinding="{Binding ProductName}"/>
<GridViewColumn Header="Type" Width="100" DisplayMemberBinding="{Binding ProductType}"/>
<GridViewColumn Header="Category" Width="100" DisplayMemberBinding="{Binding Category}"/>
</GridView>
</ListView.View>
</ListView>
<StackPanel Grid.Row="1">
<ComboBox ItemsSource="{Binding Products}" DisplayMemberPath="ProductName"/>
<ComboBox ItemsSource="{Binding Products}" DisplayMemberPath="ProductType"/>
<ComboBox ItemsSource="{Binding Products}" DisplayMemberPath="Category"/>
</StackPanel>
答案 0 :(得分:3)
检查出来:
<Window x:Class="DistinctListCollectionView.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DistinctListCollectionView"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<local:PersonCollection x:Key="data">
<local:Person FirstName="aaa" LastName="xxx" Age="1"/>
<local:Person FirstName="aaa" LastName="yyy" Age="2"/>
<local:Person FirstName="aaa" LastName="zzz" Age="1"/>
<local:Person FirstName="bbb" LastName="xxx" Age="2"/>
<local:Person FirstName="bbb" LastName="yyy" Age="1"/>
<local:Person FirstName="bbb" LastName="kkk" Age="2"/>
<local:Person FirstName="ccc" LastName="xxx" Age="1"/>
<local:Person FirstName="ccc" LastName="yyy" Age="2"/>
<local:Person FirstName="ccc" LastName="lll" Age="1"/>
</local:PersonCollection>
<local:PersonAutoFilterCollection x:Key="data2" SourceCollection="{StaticResource data}"/>
<DataTemplate DataType="{x:Type local:Person}">
<WrapPanel>
<TextBlock Text="{Binding FirstName}" Margin="5"/>
<TextBlock Text="{Binding LastName}" Margin="5"/>
<TextBlock Text="{Binding Age}" Margin="5"/>
</WrapPanel>
</DataTemplate>
</Window.Resources>
<DockPanel>
<WrapPanel DockPanel.Dock="Top">
<ComboBox DataContext="{Binding Source={StaticResource data2}, Path=Filters[0]}" ItemsSource="{Binding DistinctValues}" SelectedItem="{Binding Value}" Width="120"/>
<ComboBox DataContext="{Binding Source={StaticResource data2}, Path=Filters[1]}" ItemsSource="{Binding DistinctValues}" SelectedItem="{Binding Value}" Width="120"/>
<ComboBox DataContext="{Binding Source={StaticResource data2}, Path=Filters[2]}" ItemsSource="{Binding DistinctValues}" SelectedItem="{Binding Value}" Width="120"/>
</WrapPanel>
<ListBox ItemsSource="{Binding Source={StaticResource data2}, Path=FilteredCollection}"/>
</DockPanel>
</Window>
视图模型:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.ComponentModel;
namespace DistinctListCollectionView
{
class AutoFilterCollection<T> : INotifyPropertyChanged
{
List<AutoFilterColumn<T>> filters = new List<AutoFilterColumn<T>>();
public List<AutoFilterColumn<T>> Filters { get { return filters; } }
IEnumerable<T> sourceCollection;
public IEnumerable<T> SourceCollection
{
get { return sourceCollection; }
set
{
if (sourceCollection != value)
{
sourceCollection = value;
CalculateFilters();
}
}
}
void CalculateFilters()
{
var propDescriptors = typeof(T).GetProperties(System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.Public);
foreach (var p in propDescriptors)
{
Filters.Add(new AutoFilterColumn<T>()
{
Parent = this,
Name = p.Name,
Value = null
});
}
}
public IEnumerable GetValuesForFilter(string name)
{
IEnumerable<T> result = SourceCollection;
foreach (var flt in Filters)
{
if (flt.Name == name) continue;
if (flt.Value == null || flt.Value.Equals("All")) continue;
var pdd = typeof(T).GetProperty(flt.Name);
{
var pd = pdd;
var fltt = flt;
result = result.Where(x => pd.GetValue(x, null).Equals(fltt.Value));
}
}
var pdx = typeof(T).GetProperty(name);
return result.Select(x => pdx.GetValue(x, null)).Concat(new List<object>() { "All" }).Distinct();
}
public AutoFilterColumn<T> GetFilter(string name)
{
return Filters.SingleOrDefault(x => x.Name == name);
}
public IEnumerable<T> FilteredCollection
{
get
{
IEnumerable<T> result = SourceCollection;
foreach (var flt in Filters)
{
if (flt.Value == null || flt.Value.Equals("All")) continue;
var pd = typeof(T).GetProperty(flt.Name);
{
var pdd = pd;
var fltt = flt;
result = result.Where(x => pdd.GetValue(x, null).Equals(fltt.Value));
}
}
return result;
}
}
internal void NotifyAll()
{
foreach (var flt in Filters)
flt.Notify();
OnPropertyChanged("FilteredCollection");
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
#endregion
}
class AutoFilterColumn<T> : INotifyPropertyChanged
{
public AutoFilterCollection<T> Parent { get; set; }
public string Name { get; set; }
object theValue = null;
public object Value
{
get { return theValue; }
set
{
if (theValue != value)
{
theValue = value;
Parent.NotifyAll();
}
}
}
public IEnumerable DistinctValues
{
get
{
var rc = Parent.GetValuesForFilter(Name);
return rc;
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string prop)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(prop));
}
#endregion
internal void Notify()
{
OnPropertyChanged("DistinctValues");
}
}
}
其他课程:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DistinctListCollectionView
{
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public int Age { get; set; }
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace DistinctListCollectionView
{
class PersonCollection : List<Person>
{
}
class PersonAutoFilterCollection : AutoFilterCollection<Person>
{
}
}
答案 1 :(得分:0)
如果您使用的是MVVM,那么您的所有绑定数据对象都在ViewModel类中,而您的ViewModel类正在实现INotifyPropertyChanged,对吗?
如果是这样,那么您可以维护SelectedItemType1,SelectedItemType2等的状态变量,这些变量绑定到ComboBox的SelectedItem依赖项属性。在Setter for SelectedItemType1中,填充List属性(绑定到ComboBoxType2的ItemsSource)并为List属性触发NotifyPropertyChanged。对Type3重复这个,你应该在球场。
至于“刷新”问题,或者View如何知道什么时候发生了变化,这一切都归结为绑定模式并在正确的时刻触发NotifyPropertyChanged事件。
你可以用ValueConverter做到这一点,我喜欢ValueConverters,但我认为在这种情况下管理你的ViewModel更加优雅,以便Binding发生。
答案 2 :(得分:0)
为什么不使用linq查询或类似的东西创建另一个只包含列表中不同值的属性?
public IEnumerable<string> ProductNameFilters
{
get { return Products.Select(product => product.ProductName).Distinct(); }
}
...等
当您的产品属性发生变化时,您必须为每个过滤器列表引发属性更改通知,但这不是什么大问题。
您应该将ViewModel视为视图的大型ValueConverter。我唯一一次在MVVM中使用ValueConverter是因为我需要将数据从非特定于视图的数据类型更改为 视图特定的数据类型。示例:对于大于10的值,文本需要为红色,对于小于10的值,文本必须为蓝色...蓝色和红色是特定于视图的类型,不应该是从ViewModel返回的内容。这实际上是唯一不应该在ViewModel中使用此逻辑的情况。
我质疑“非常慢的大型名单”评论的有效性......通常,人类的“大”和计算机的“大”是两个非常不同的东西。如果你对计算机和人类都处于“大”的范围,我也会质疑在屏幕上显示这么多数据。重点是,它可能不足以让您注意到这些查询的成本。