代码之家  ›  专栏  ›  技术社区  ›  Brent

基于ListView项的WPF筛选器组合框项

  •  2
  • Brent  · 技术社区  · 15 年前

    我正在使用由ListView和一些组合框组成的MVVM设计模式创建一个WPF应用程序。组合框用于筛选ListView。我试图完成的是用相关ListView列中的项填充组合框。换句话说,如果我的ListView有第1列、第2列和第3列,我希望ComboBox1在第1列中显示所有唯一的项。一旦在ComboBox1中选择了一个项目,我希望根据ComboBox1的选择过滤Combox2和Combox3中的项目,这意味着Combox2和Combox3只能包含有效的选择。如果在ASP.NET中使用Ajax工具包,这将有点类似于级联下拉控件,但用户可以随机选择任何组合框,而不是按顺序。

    我的第一个想法是将组合框绑定到ListView绑定到的同一ListCollectionView,并将DisplayMemberPath设置为相应的列。这对于一起过滤ListView和ComboBox非常有用,但它显示了ComboBox中的所有项,而不仅仅是唯一项(显然)。所以我的下一个想法是使用ValueConverter只返回唯一的项目,但我没有成功。

    仅供参考:我阅读了科林·埃伯哈特的文章,内容是在 CodeProject 但他的方法循环遍历整个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 回复  |  直到 15 年前
        1
  •  3
  •   Aviad P.    15 年前

    看看这个:

    <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>
        {
        }
    }
    
        2
  •  0
  •   Joel Cochran    15 年前

    如果您使用的是MVVM,那么所有绑定的数据对象都在您的viewModel类中,并且您的viewModel类正在实现inotifyPropertyChanged,对吗?

    如果是,则可以维护绑定到ComboBox SelectedItem依赖属性的SelectedItemType1、SelectedItemType2等的状态变量。在SelectedItemType1的setter中,填充List属性(它绑定到ComboBoxType2的itemsSource),并为List属性激发NotifyPropertyChanged。对3型重复这个步骤,你应该在球场上。

    至于“刷新”问题,或者视图如何知道什么时候发生了更改,这一切都归结为绑定模式,并在正确的时刻触发notifypropertychanged事件。

    您可以使用ValueConverter来实现这一点,我喜欢ValueConverter,但我认为在这种情况下,管理您的ViewModel使绑定发生会更为优雅。

        3
  •  0
  •   Anderson Imes    15 年前

    为什么不使用LINQ查询或类似的方法创建另一个只包含列表中不同值的属性呢?

    public IEnumerable<string> ProductNameFilters
    {
         get { return Products.Select(product => product.ProductName).Distinct(); }
    }
    

    ……等

    当产品属性发生更改时,您必须为每个筛选器列表发出属性更改通知,但这并不重要。

    您真的应该将视图模型视为视图的大值转换器。在MVVM中,我唯一一次使用ValueConverter是在需要将数据从不是特定于视图的数据类型更改为 视图特定。示例:对于大于10的值,文本需要为红色,对于小于10的值,文本需要为蓝色…蓝色和红色是视图特定的类型,不应该是从视图模型返回的类型。这实际上是唯一一种情况,这种逻辑不应该出现在视图模型中。

    我怀疑“对于大名单来说很慢”评论的有效性…一般来说,人类的“大”和计算机的“大”是两个非常不同的东西。如果你在计算机和人类的“大”领域,我也会质疑在屏幕上显示这么多数据。关键是,它可能不够大,您无法注意到这些查询的成本。