WPF过滤基于ListView项目的组合框项目

时间:2009-12-22 17:18:26

标签: wpf listview mvvm combobox filter

我正在使用由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>

3 个答案:

答案 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中使用此逻辑的情况。

我质疑“非常慢的大型名单”评论的有效性......通常,人类的“大”和计算机的“大”是两个非常不同的东西。如果你对计算机和人类都处于“大”的范围,我也会质疑在屏幕上显示这么多数据。重点是,它可能不足以让您注意到这些查询的成本。