WPF快速指导13:数据绑定之集合绑定之视图(排序、过滤、分组)

简介: WPF快速指导13:数据绑定之集合绑定之视图(排序、过滤、分组) 本文摘要:1:为什么需要视图; 2:变更集合; 3:排序; 4:过滤筛选; 5:分组; 1:为什么需要视图      一旦 ItemsControl 绑定到数据集合,您可能希望对数据进行排序、筛选或分组。

WPF快速指导13:数据绑定之集合绑定之视图(排序、过滤、分组)

本文摘要:
1:为什么需要视图;

2:变更集合;

3:排序;

4:过滤筛选;

5:分组;

1:为什么需要视图

     一旦 ItemsControl 绑定到数据集合,您可能希望对数据进行排序、筛选或分组。若要执行此操作,可以使用集合视图,这是实现 ICollectionView 接口的类。

     集合视图是位于绑定源集合顶部的一层,您可以通过它使用排序、筛选和分组查询来导航和显示源集合,而无需更改基础源集合本身。集合视图还维护着一个指向集合中的当前项的指针。如果源集合实现了 INotifyCollectionChanged 接口,则 CollectionChanged 事件引发的更改将传播到视图。

     一个最简单的应用视图的例子如下,我们要借助视图完成将ListBox中的当前项移到下一个及上一个(注意:单纯的绑定集合,甚至连这个简单的功能也难实现)。

     前台:

    <Window.Resources>
        <local:People x:Key="Family">
            <local:Person Name="Tom" Age="11"></local:Person>
            <local:Person Name="John" Age="12"></local:Person>
            <local:Person Name="Melissa" Age="38"></local:Person>
        </local:People>
    </Window.Resources>
    <Grid DataContext="{StaticResource Family}">
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition Height="Auto"></RowDefinition>
            <RowDefinition></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="60"></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        
        <ListBox x:Name="listBoxPeople" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" Grid.Row="0" Grid.Column="1" Height="100" >
            <ListBox.ItemTemplate>
                <DataTemplate>
                    <TextBlock>
                        <TextBlock Text="{Binding Path=Name}" Width="150"></TextBlock>
                        <TextBlock Text="{Binding Path=Age}" Width="150"></TextBlock>
                        <Button x:Name="buttonShow" Click="buttonShow_Click">show</Button>
                    </TextBlock>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
        <TextBlock Grid.Row="1" Grid.Column="0">Name:</TextBlock>
        <TextBox x:Name="txtName" Text="{Binding Path=Name}" Grid.Row="1" Grid.Column="1"></TextBox>
        <TextBlock Grid.Row="2" Grid.Column="0">Age:</TextBlock>
        <TextBox x:Name="txtAge" Text="{Binding Path=Age}" Grid.Row="2" Grid.Column="1"></TextBox>
        <StackPanel Grid.Row="3" Grid.Column="1" Orientation="Horizontal">
            <Button x:Name="buttonShowSelected" Click="buttonShowSelected_Click">显示选中项</Button>
            <Button x:Name="buttonAdd" Click="buttonAdd_Click">新加</Button>
            <Button x:Name="buttonPrevious" Click="buttonPrevious_Click">上一个</Button>
            <Button x:Name="buttonNext" Click="buttonNext_Click">下一个</Button>
        </StackPanel>
    </Grid>
   后台:
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            
        }

        private void buttonShowSelected_Click(object sender, RoutedEventArgs e)
        {
            if (listBoxPeople.SelectedItem == null)
            {
                return;
            }
            Person person = listBoxPeople.SelectedItem as Person;
            MessageBox.Show(string.Format("person:{0},Age {1}", person.Name, person.Age));
        }

        private void buttonPrevious_Click(object sender, RoutedEventArgs e)
        {
            ICollectionView view = GetFamilyView();
            view.MoveCurrentToPrevious();
        }

        private void buttonNext_Click(object sender, RoutedEventArgs e)
        {
            ICollectionView view = GetFamilyView();
            view.MoveCurrentToNext();
        }

        private void buttonShow_Click(object sender, RoutedEventArgs e)
        {
            Button button = sender as Button;
            Person person = button.DataContext as Person;
            MessageBox.Show(string.Format("person:{0},Age {1}", person.Name, person.Age));
        }

        ICollectionView GetFamilyView()
        {
            People people = this.FindResource("Family") as People;
            return CollectionViewSource.GetDefaultView(people);
        }

        private void buttonAdd_Click(object sender, RoutedEventArgs e)
        {
            string name = txtName.Text.Trim();
            int age = Int16.Parse(txtAge.Text.Trim());
            Person person = new Person() { Name=name, Age=age};
            People people = this.FindResource("Family") as People;
            people.Add(person);
        }
    }

    class Person
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }

    class People: ObservableCollection<Person>
    {

    }


2:变更集合

      在上文代码中,我们已经完成了添加一个记录到集合中。删除也是同理。


3:排序

     在上文代码中,我们加入一个排序按钮,同时为排序按钮增加点击事件如下:

        private void buttonSort_Click(object sender, RoutedEventArgs e)
        {
            ICollectionView view = GetFamilyView();
            view.SortDescriptions.Add(new SortDescription("Age", ListSortDirection.Ascending));
        }

     则,列表中的条目就会按照年龄升序排列。以上这种做法在大多数情况下已经够用。因为基本上排序的内容都是基本型别,而基本型别都提供了默认的比较器(有关比较器概念,参看http://www.cnblogs.com/luminji/archive/2010/09/30/1839038.html)。但是,难免我们会用到自定义排序的时候(如对IP地址排序),这个时候,我们就要编码实现。现在,为了简单起见,我们仍旧选择按年龄进行排序,但是排序要被处理成自定义排序。首先需要撰写一个自定义排序类:

    class PersonAgeSort: IComparer
    {

        #region IComparer 成员

        public int Compare(object x, object y)
        {
            Person personx = x as Person;
            Person persony = y as Person;
            if (personx.Age > persony.Age)
            {
                return 1;
            }
            else if (personx.Age == persony.Age)
            {
                return 0;
            }
            else
            {
                return -1;
            }
            //return personx.Age.CompareTo(persony.Age);
        }

        #endregion
    }

     然后,按钮事件改为:

        private void buttonSort_Click(object sender, RoutedEventArgs e)
        {
            ListCollectionView view = GetFamilyView() as ListCollectionView;
            view.CustomSort = new PersonAgeSort();                        
        }

     注意按钮事件中,我们将GetFamilyView方法返回值转型为了ListCollectionView。因为他在WPF时用来包装IList接口并提供视图功能的类。在WPF中还有一些ICollectionView接口的实现,但有些不提供自定义排序,所以在使用起来要注意。尤其觉得遗憾的是,没有提供泛型实现的视图类。综上所述,上文代码起码有两点遗憾:

     1:虽然列表数据使用的是class People: ObservableCollection<Person> 型别,但是视图却使用ListCollectionView ;

     2:没有提供泛型实现的视图类,导致排序的时候的拆箱和装箱操作过多,影响效率。

     最后提供默认的数据集视图供参考:

集合数据          默认视图                 注释

IEnumerable    CollectionView        无法对项进行分组。

IList               ListCollectionView    最快。

IBindingList    BindingListCollectionView


4:过滤筛选

     为上文代码增加一个排序按钮,同时为该排序按钮提供点击事件如下:

        private void buttonFilter_Click(object sender, RoutedEventArgs e)
        {
            ICollectionView view = GetFamilyView();
            view.Filter = delegate(object target)
                {
                    return ((Person)target).Age > 25;
                };
            //view.Filter = new Predicate<object>(delegate(object target)
            //    {
            //        return ((Person)target).Age > 25;
            //    });
        }

      过滤比较简单,不在赘述。在这里只要理解Predicate<object>型别这个委托变量Filter就行了。


5:分组

     我们来考虑一种最简单的分组。假设对列表中的数据按照Age进行分组,这个意思就是,假设列表中有11,22,22,28四个年龄的人员,则分组后,将有11,22,28三个组。要是实现这个功能,为上文代码添加一个分组按钮,并为该按钮提供一个点击事件:

        private void buttonGroup_Click(object sender, RoutedEventArgs e)
        {
            ICollectionView view = GetFamilyView();
            view.GroupDescriptions.Add(new PropertyGroupDescription("Age"));
        }

     以上代码运行后,发现点击分组按钮,什么也没有发生,那是因为我们没有提供为ListBox提供组样式。记住,所有派生自ItemsControl的类都能进行分组显示,但是我们必须为它提供组样式。组样式,事实是一个GroupStyle的实例,如果没有特殊要求,我们可以使用GroupStyle为我们提供的Default静态属性来实现一个默认样式。上文的例子,我们只要在前台代码中加入如下代码,即可运行:

        <ListBox x:Name="listBoxPeople" ItemsSource="{Binding}" IsSynchronizedWithCurrentItem="True" Grid.Row="0" Grid.Column="1" Height="100" >
            <ListBox.GroupStyle>
                <x:Static Member="GroupStyle.Default"></x:Static>
            </ListBox.GroupStyle>
            <ListBox.ItemTemplate>
                <DataTemplate>
                    ……
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>

    这种默认的GroupStyle样式太古板,还可以自定义GroupStyle。如下:

            <ListBox.GroupStyle>
                <GroupStyle>
                    <GroupStyle.HeaderTemplate>
                        <DataTemplate>
                            <TextBlock>
                                <TextBlock>年龄:</TextBlock>
                                <TextBlock Text="{Binding Path=Name}"></TextBlock>
                                <TextBlock>数量:</TextBlock>
                                <TextBlock Text="{Binding Path=ItemCount}"></TextBlock>
                            </TextBlock>
                        </DataTemplate>
                    </GroupStyle.HeaderTemplate>
                </GroupStyle>
            </ListBox.GroupStyle>

    注意:在GroupStyle中绑定的Name不是指Person中的Name,而是view.GroupDescriptions.Add(new PropertyGroupDescription("Age"));中指定的这个Age。

   上文也说了,这是一种最简单的分组,在实际应用中,分组往往要复杂的多。比如以年龄分组来说,就不可能像上文中一样,以一岁做为一组。现在,我们来实现如下的分组要求:

   0-30:年轻;

   30-60:中年;

   >60:老年;

   所有的自定义分组器都需要实现接口IValueConverter,如下:

    class AgeToRangeConver: IValueConverter
    {

        #region IValueConverter 成员

        public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            int iValue = (int)value;
            if (0 < iValue && iValue <= 30)
            {
                return "年轻";
            }
            else if (30 < iValue && iValue <= 60)
            {
                return "中年";
            }
            else
            {
                return "老年";
            }
        }

        public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
        {
            throw new NotImplementedException();
        }

        #endregion
    }

   然后,点击事件修改为:

        private void buttonGroup_Click(object sender, RoutedEventArgs e)
        {
            ICollectionView view = GetFamilyView();
            view.GroupDescriptions.Add(new PropertyGroupDescription("Age", new AgeToRangeConver()));
        }

   可以看到,分组已经按我们的要求实现了。

Creative Commons License本文基于 Creative Commons Attribution 2.5 China Mainland License发布,欢迎转载,演绎或用于商业目的,但是必须保留本文的署名 http://www.cnblogs.com/luminji(包含链接)。如您有任何疑问或者授权方面的协商,请给我留言。
目录
相关文章
WPF—多重绑定和跨层级绑定
WPF—多重绑定和跨层级绑定
|
1月前
|
C#
通过Demo学WPF—数据绑定(一)
通过Demo学WPF—数据绑定(一)
29 1
|
1月前
|
开发框架 缓存 前端开发
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(11) -- 下拉列表的数据绑定以及自定义系统字典列表控件
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(11) -- 下拉列表的数据绑定以及自定义系统字典列表控件
|
20天前
|
安全 C# 数据安全/隐私保护
WPF安全加固全攻略:从数据绑定到网络通信,多维度防范让你的应用固若金汤,抵御各类攻击
【8月更文挑战第31天】安全性是WPF应用程序开发中不可或缺的一部分。本文从技术角度探讨了WPF应用面临的多种安全威胁及防护措施。通过严格验证绑定数据、限制资源加载来源、实施基于角色的权限管理和使用加密技术保障网络通信安全,可有效提升应用安全性,增强用户信任。例如,使用HTML编码防止XSS攻击、检查资源签名确保其可信度、定义安全策略限制文件访问权限,以及采用HTTPS和加密算法保护数据传输。这些措施有助于全面保障WPF应用的安全性。
30 0
|
20天前
|
数据处理 开发者 C#
WPF数据绑定实战:从零开始,带你玩转数据与界面同步,让你的应用程序更上一层楼!
【8月更文挑战第31天】在WPF应用开发中,数据绑定是核心技能之一,它能实现界面元素与数据源的同步更新。本文详细介绍了WPF数据绑定的概念与实现方法,包括属性绑定、元素绑定及路径绑定等技术,并通过示例代码展示了如何创建数据绑定。通过数据绑定,开发者不仅能简化代码、提高可维护性,还能提升用户体验。无论初学者还是有经验的开发者,都能从中受益,更好地掌握WPF数据绑定技巧。
26 0
|
1月前
|
存储 开发框架 .NET
通过Demo学WPF—数据绑定(二)
通过Demo学WPF—数据绑定(二)
34 1
|
1月前
|
开发框架 前端开发 JavaScript
在WPF应用中实现DataGrid的分组显示,以及嵌套明细展示效果
在WPF应用中实现DataGrid的分组显示,以及嵌套明细展示效果
在WPF应用中实现DataGrid的分组显示,以及嵌套明细展示效果
|
1月前
|
C#
WPF/C#:数据绑定到方法
WPF/C#:数据绑定到方法
29 0
|
1月前
|
开发框架 .NET C#
WPF/C#:显示分组数据的两种方式
WPF/C#:显示分组数据的两种方式
38 0
|
1月前
|
XML C# 数据格式
WPF/C#:如何将数据分组显示
WPF/C#:如何将数据分组显示
32 0