当前位置: 首页 > news >正文

WPF之集合绑定深入

文章目录

    • 引言
    • ObservableCollection<T>基础
      • 什么是ObservableCollection
      • ObservableCollection的工作原理
      • 基本用法示例
      • ObservableCollection与MVVM模式
      • ObservableCollection的局限性
    • INotifyCollectionChanged接口深入
      • 接口定义与作用
      • NotifyCollectionChangedEventArgs详解
      • 自定义INotifyCollectionChanged实现
    • 扩展ObservableCollection功能
      • 实现批量操作
      • 处理集合元素属性变化
      • 线程安全的ObservableCollection
    • CollectionView与ICollectionView接口
      • CollectionView概述
      • ICollectionView接口
      • 获取和使用CollectionView
        • XAML中使用CollectionViewSource
        • 代码中获取和操作CollectionView
    • 集合的排序、过滤与分组
      • 排序功能
      • 自定义排序
      • 过滤功能
      • 分组功能
      • 自定义分组
    • 当前项管理
      • 当前项基本操作
      • IsSynchronizedWithCurrentItem属性
      • CurrentItem与SelectedItem的区别
    • 实际应用案例
      • 使用集合绑定实现可编辑数据表格
    • 总结与最佳实践
      • 集合绑定的最佳实践
      • 常见问题解决
    • 学习资源
      • 官方文档
      • 社区资源
      • 书籍推荐

可以根据Github拉取示例程序运行
GitHub程序演示地址(点击直达)
也可以在本文资源中下载
在这里插入图片描述

引言

在WPF应用程序开发中,数据绑定是连接UI和数据的桥梁,而集合绑定则是处理列表、表格等多项数据显示的核心机制。通过集合绑定,我们可以轻松地将数据源中的集合对象与ListBox、ListView、DataGrid等ItemsControl控件关联起来,实现数据的自动呈现与交互。

本文将深入探讨WPF集合绑定的高级特性和技术,帮助开发者更好地理解和应用这一强大机制,构建出更加灵活、高效的数据驱动界面。

集合绑定的核心元素包括:

集合绑定核心元素
可观察集合
集合视图
项目容器控件
数据模板
ObservableCollection
INotifyCollectionChanged
CollectionView
ICollectionView
DataTemplate

ObservableCollection<T>基础

什么是ObservableCollection

ObservableCollection<T>是WPF提供的一个特殊集合类,它继承自Collection<T>并实现了INotifyCollectionChanged接口。这使得它能够在集合内容发生变化时自动通知UI,触发界面更新。

ObservableCollection相比普通集合(如List<T>、Array等)的最大优势在于它能够实时反馈集合变化,无需手动刷新界面。

ObservableCollection的工作原理

用户界面 ObservableCollection 业务逻辑 添加/删除/修改元素 执行操作 触发CollectionChanged事件 更新显示内容 用户界面 ObservableCollection 业务逻辑

基本用法示例

下面是一个简单的ObservableCollection使用示例:

using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;namespace CollectionBindingDemo
{public partial class MainWindow : Window{// 创建一个Person类型的ObservableCollection作为数据源private ObservableCollection<Person> _people;public MainWindow(){InitializeComponent();// 初始化集合并添加一些示例数据_people = new ObservableCollection<Person>{new Person { Name = "张三", Age = 28 },new Person { Name = "李四", Age = 32 },new Person { Name = "王五", Age = 25 }};// 设置ListBox的ItemsSource为ObservableCollectionpeopleListBox.ItemsSource = _people;}private void AddButton_Click(object sender, RoutedEventArgs e){// 添加新人员到集合 - 界面会自动更新_people.Add(new Person { Name = "新人员", Age = 30 });}private void RemoveButton_Click(object sender, RoutedEventArgs e){// 如果有选中项,则从集合中移除if (peopleListBox.SelectedItem != null){_people.Remove(peopleListBox.SelectedItem as Person);}}}// 定义Person类public class Person{public string Name { get; set; }public int Age { get; set; }// 重写ToString方法以便在ListBox中显示public override string ToString(){return $"{Name}, {Age}岁";}}
}

对应的XAML:

<Window x:Class="CollectionBindingDemo.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="集合绑定示例" Height="350" Width="500"><Grid><Grid.RowDefinitions><RowDefinition Height="*" /><RowDefinition Height="Auto" /></Grid.RowDefinitions><!-- 使用ListBox显示集合内容 --><ListBox x:Name="peopleListBox" Margin="10" /><!-- 添加和删除按钮 --><StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,10"><Button Content="添加人员" Click="AddButton_Click" Width="100" Margin="5" /><Button Content="删除所选" Click="RemoveButton_Click" Width="100" Margin="5" /></StackPanel></Grid>
</Window>

ObservableCollection与MVVM模式

在MVVM(Model-View-ViewModel)设计模式中,ObservableCollection通常在ViewModel中定义,作为View层和Model层之间的数据桥梁。

// ViewModel类
public class PeopleViewModel : INotifyPropertyChanged
{private ObservableCollection<Person> _people;// 公开的集合属性,供View绑定public ObservableCollection<Person> People{get => _people;set{_people = value;OnPropertyChanged(nameof(People));}}// 构造函数public PeopleViewModel(){// 初始化集合People = new ObservableCollection<Person>();LoadPeople(); // 加载数据}// 加载数据的方法private void LoadPeople(){// 实际应用中可能从数据库或服务加载People.Add(new Person { Name = "张三", Age = 28 });People.Add(new Person { Name = "李四", Age = 32 });People.Add(new Person { Name = "王五", Age = 25 });}// INotifyPropertyChanged实现public event PropertyChangedEventHandler PropertyChanged;protected virtual void OnPropertyChanged(string propertyName){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}
}

XAML绑定示例:

<Window x:Class="CollectionBindingDemo.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:vm="clr-namespace:CollectionBindingDemo.ViewModels"><Window.DataContext><vm:PeopleViewModel /></Window.DataContext><Grid><!-- 使用ListBox显示集合内容,ItemsSource绑定到ViewModel的People属性 --><ListBox ItemsSource="{Binding People}" Margin="10"><ListBox.ItemTemplate><DataTemplate><StackPanel Orientation="Horizontal"><TextBlock Text="{Binding Name}" FontWeight="Bold" /><TextBlock Text=", " /><TextBlock Text="{Binding Age}" /><TextBlock Text="" /></StackPanel></DataTemplate></ListBox.ItemTemplate></ListBox></Grid>
</Window>

ObservableCollection的局限性

虽然ObservableCollection非常实用,但它也有一些局限性:

  1. 对集合元素的属性变化不敏感 - 只有添加/删除/替换整个元素才会触发通知
  2. 线程安全问题 - 只能在创建集合的线程上修改集合
  3. 批量操作效率低 - 每次操作都会触发通知,大量操作会导致性能问题

这些局限性的解决方案将在后续章节中讨论。

INotifyCollectionChanged接口深入

接口定义与作用

INotifyCollectionChanged是WPF集合绑定的核心接口,它定义了集合内容变化时的通知机制。ObservableCollection正是通过实现这个接口,使得UI能够自动响应集合的变化。

// INotifyCollectionChanged接口定义
public interface INotifyCollectionChanged
{// 当集合变化时触发的事件event NotifyCollectionChangedEventHandler CollectionChanged;
}// 事件处理委托
public delegate void NotifyCollectionChangedEventHandler(object sender, NotifyCollectionChangedEventArgs e);

NotifyCollectionChangedEventArgs详解

当集合发生变化时,CollectionChanged事件会传递一个NotifyCollectionChangedEventArgs对象,包含了变化的详细信息:

  • Action:变化的类型(添加、移除、替换、移动、重置)
  • NewItems:新添加或替换的项目集合
  • OldItems:被移除或替换的旧项目集合
  • NewStartingIndex:变化开始的新索引
  • OldStartingIndex:变化开始的旧索引
// 变化类型枚举
public enum NotifyCollectionChangedAction
{Add,        // 添加项目Remove,     // 删除项目Replace,    // 替换项目Move,       // 移动项目Reset       // 重置集合
}

自定义INotifyCollectionChanged实现

可以创建自己的集合类并实现INotifyCollectionChanged接口,以获得更灵活的集合变化通知能力:

using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.Specialized;namespace CustomCollections
{// 自定义集合实现public class CustomObservableCollection<T> : IList<T>, INotifyCollectionChanged{// 内部存储列表private List<T> _items = new List<T>();// 实现INotifyCollectionChanged接口public event NotifyCollectionChangedEventHandler CollectionChanged;// 触发CollectionChanged事件的辅助方法protected virtual void OnCollectionChanged(NotifyCollectionChangedEventArgs e){CollectionChanged?.Invoke(this, e);}// 添加元素public void Add(T item){_items.Add(item);// 触发添加通知OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, item, _items.Count - 1));}// 移除元素public bool Remove(T item){int index = _items.IndexOf(item);if (index >= 0){_items.RemoveAt(index);// 触发移除通知OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,item,index));return true;}return false;}// 添加一组元素(批量操作,但只发送一次通知)public void AddRange(IEnumerable<T> collection){if (collection == null)throw new ArgumentNullException(nameof(collection));// 转换为列表便于处理List<T> itemsToAdd = new List<T>(collection);if (itemsToAdd.Count == 0)return;// 记住起始索引int startIndex = _items.Count;// 添加所有元素_items.AddRange(itemsToAdd);// 只触发一次通知OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,itemsToAdd,startIndex));}// 清空集合public void Clear(){_items.Clear();// 触发重置通知OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));}// 其他接口方法的实现略...// (为了简洁,此处省略IList<T>接口的其他成员实现)// 基本的IList<T>实现public T this[int index] { get => _items[index]; set {T oldItem = _items[index];_items[index] = value;// 触发替换通知OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Replace,value,oldItem,index));} }public int Count => _items.Count;public bool IsReadOnly => false;public int IndexOf(T item) => _items.IndexOf(item);public void Insert(int index, T item){_items.Insert(index, item);OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add,item,index));}public void RemoveAt(int index){T oldItem = _items[index];_items.RemoveAt(index);OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Remove,oldItem,index));}public bool Contains(T item) => _items.Contains(item);public void CopyTo(T[] array, int arrayIndex) => _items.CopyTo(array, arrayIndex);public IEnumerator<T> GetEnumerator() => _items.GetEnumerator();IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator();}
}

扩展ObservableCollection功能

实现批量操作

ObservableCollection一个主要的限制是没有高效的批量操作支持,每次添加或删除都会触发通知。下面是一个扩展实现,支持批量操作:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;namespace EnhancedCollections
{/// <summary>/// 支持批量操作的ObservableCollection扩展实现/// </summary>public class BulkObservableCollection<T> : ObservableCollection<T>{// 标记是否正在进行批量操作private bool _suppressNotification = false;/// <summary>/// 执行批量操作而不触发多次通知/// </summary>/// <param name="action">要执行的批量操作</param>public void ExecuteBulkOperation(Action action){// 暂停通知_suppressNotification = true;try{// 执行批量操作action();}finally{// 恢复通知_suppressNotification = false;// 操作完成后发送一次重置通知OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));}}/// <summary>/// 批量添加项目/// </summary>public void AddRange(IEnumerable<T> items){ExecuteBulkOperation(() =>{foreach (var item in items){Add(item);}});}/// <summary>/// 批量移除项目/// </summary>public void RemoveRange(IEnumerable<T> items){ExecuteBulkOperation(() =>{foreach (var item in items){Remove(item);}});}/// <summary>/// 重写基类的OnCollectionChanged方法,以支持通知抑制/// </summary>protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs e){if (!_suppressNotification){base.OnCollectionChanged(e);}}/// <summary>/// 重写基类的OnPropertyChanged方法,以支持通知抑制/// </summary>protected override void OnPropertyChanged(PropertyChangedEventArgs e){if (!_suppressNotification){base.OnPropertyChanged(e);}}}
}

使用示例:

// 创建支持批量操作的集合
var people = new BulkObservableCollection<Person>();// 添加多个项目(只会触发一次通知)
people.AddRange(new List<Person>
{new Person { Name = "张三", Age = 28 },new Person { Name = "李四", Age = 32 },new Person { Name = "王五", Age = 25 },new Person { Name = "赵六", Age = 41 }
});// 执行自定义批量操作
people.ExecuteBulkOperation(() =>
{// 移除年龄大于30的人var toRemove = people.Where(p => p.Age > 30).ToList();foreach (var person in toRemove){people.Remove(person);}// 添加新人员people.Add(new Person { Name = "小明", Age = 18 });
});

处理集合元素属性变化

ObservableCollection另一个限制是它只能监听集合项目的添加、删除等操作,而无法感知集合中的对象本身属性的变化。以下是一个可以监听集合元素属性变化的实现:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;namespace EnhancedCollections
{/// <summary>/// 可以监听集合元素属性变化的ObservableCollection扩展实现/// </summary>public class ItemPropertyObservableCollection<T> : ObservableCollection<T>where T : INotifyPropertyChanged{/// <summary>/// 元素属性变化事件/// </summary>public event EventHandler<ItemPropertyChangedEventArgs<T>> ItemPropertyChanged;public ItemPropertyObservableCollection() : base() { }public ItemPropertyObservableCollection(IEnumerable<T> collection) : base(collection){// 为所有初始项目添加事件处理程序AttachPropertyChangedHandlers(collection);}/// <summary>/// 重写InsertItem方法,为新项目添加属性变化处理程序/// </summary>protected override void InsertItem(int index, T item){base.InsertItem(index, item);// 添加属性变化监听AttachPropertyChangedHandler(item);}/// <summary>/// 重写RemoveItem方法,移除项目的属性变化处理程序/// </summary>protected override void RemoveItem(int index){// 移除属性变化监听DetachPropertyChangedHandler(this[index]);base.RemoveItem(index);}/// <summary>/// 重写ClearItems方法,移除所有项目的属性变化处理程序/// </summary>protected override void ClearItems(){foreach (var item in this){DetachPropertyChangedHandler(item);}base.ClearItems();}/// <summary>/// 重写SetItem方法,为替换项目更新属性变化处理程序/// </summary>protected override void SetItem(int index, T item){var oldItem = this[index];// 移除旧项目的属性变化监听DetachPropertyChangedHandler(oldItem);base.SetItem(index, item);// 添加新项目的属性变化监听AttachPropertyChangedHandler(item);}/// <summary>/// 为集合中的所有项目添加属性变化处理程序/// </summary>private void AttachPropertyChangedHandlers(IEnumerable<T> items){foreach (var item in items){AttachPropertyChangedHandler(item);}}/// <summary>/// 为单个项目添加属性变化处理程序/// </summary>private void AttachPropertyChangedHandler(T item){if (item != null){item.PropertyChanged += Item_PropertyChanged;}}/// <summary>/// 移除单个项目的属性变化处理程序/// </summary>private void DetachPropertyChangedHandler(T item){if (item != null){item.PropertyChanged -= Item_PropertyChanged;}}/// <summary>/// 项目属性变化处理方法/// </summary>private void Item_PropertyChanged(object sender, PropertyChangedEventArgs e){// 转发属性变化事件ItemPropertyChanged?.Invoke(this, new ItemPropertyChangedEventArgs<T>{ChangedItem = (T)sender,PropertyName = e.PropertyName});// 重新触发CollectionChanged事件,以便UI更新// 使用Reset操作来确保所有绑定都更新OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));}}/// <summary>/// 集合元素属性变化事件参数/// </summary>public class ItemPropertyChangedEventArgs<T> : EventArgs{public T ChangedItem { get; set; }public string PropertyName { get; set; }}
}

使用示例:

// 创建可以监听元素属性变化的集合
var people = new ItemPropertyObservableCollection<Person>();// 添加元素
people.Add(new Person { Name = "张三", Age = 28 });// 监听元素属性变化
people.ItemPropertyChanged += (sender, e) =>
{Console.WriteLine($"人员 {e.ChangedItem.Name}{e.PropertyName} 属性已变化");
};// 改变集合中元素的属性 - 会触发ItemPropertyChanged事件
people[0].Age = 29;// 注意:Person类必须实现INotifyPropertyChanged接口
public class Person : INotifyPropertyChanged
{private string _name;private int _age;public event PropertyChangedEventHandler PropertyChanged;protected void OnPropertyChanged(string propertyName){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}public string Name{get => _name;set{if (_name != value){_name = value;OnPropertyChanged(nameof(Name));}}}public int Age{get => _age;set{if (_age != value){_age = value;OnPropertyChanged(nameof(Age));}}}
}

线程安全的ObservableCollection

ObservableCollection的另一个限制是它不是线程安全的,只能在创建它的线程上修改。以下是一个线程安全的实现:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Threading;
using System.Windows.Threading;namespace EnhancedCollections
{/// <summary>/// 线程安全的ObservableCollection实现/// </summary>public class ThreadSafeObservableCollection<T> : ObservableCollection<T>{private readonly Dispatcher _dispatcher;private readonly ReaderWriterLockSlim _lock = new ReaderWriterLockSlim();/// <summary>/// 构造函数/// </summary>public ThreadSafeObservableCollection() : base(){// 存储创建集合的线程的Dispatcher_dispatcher = Dispatcher.CurrentDispatcher;}/// <summary>/// 使用现有集合创建新集合/// </summary>public ThreadSafeObservableCollection(IEnumerable<T> collection) : base(collection){_dispatcher = Dispatcher.CurrentDispatcher;}/// <summary>/// 在UI线程上安全地执行操作/// </summary>private void ExecuteOnUIThread(Action action){// 如果已经在UI线程上,直接执行if (_dispatcher.CheckAccess()){action();}else{// 否则调度到UI线程执行_dispatcher.Invoke(action);}}/// <summary>/// 添加项目/// </summary>public new void Add(T item){_lock.EnterWriteLock();try{ExecuteOnUIThread(() => base.Add(item));}finally{_lock.ExitWriteLock();}}/// <summary>/// 移除项目/// </summary>public new bool Remove(T item){_lock.EnterWriteLock();try{bool result = false;ExecuteOnUIThread(() => result = base.Remove(item));return result;}finally{_lock.ExitWriteLock();}}/// <summary>/// 插入项目/// </summary>public new void Insert(int index, T item){_lock.EnterWriteLock();try{ExecuteOnUIThread(() => base.Insert(index, item));}finally{_lock.ExitWriteLock();}}/// <summary>/// 移除指定位置的项目/// </summary>public new void RemoveAt(int index){_lock.EnterWriteLock();try{ExecuteOnUIThread(() => base.RemoveAt(index));}finally{_lock.ExitWriteLock();}}/// <summary>/// 清空集合/// </summary>public new void Clear(){_lock.EnterWriteLock();try{ExecuteOnUIThread(() => base.Clear());}finally{_lock.ExitWriteLock();}}/// <summary>/// 获取项目的索引/// </summary>public new int IndexOf(T item){_lock.EnterReadLock();try{int result = -1;ExecuteOnUIThread(() => result = base.IndexOf(item));return result;}finally{_lock.ExitReadLock();}}/// <summary>/// 访问集合中的元素/// </summary>public new T this[int index]{get{_lock.EnterReadLock();try{T result = default(T);ExecuteOnUIThread(() => result = base[index]);return result;}finally{_lock.ExitReadLock();}}set{_lock.EnterWriteLock();try{ExecuteOnUIThread(() => base[index] = value);}finally{_lock.ExitWriteLock();}}}/// <summary>/// 批量添加项目(线程安全)/// </summary>public void AddRange(IEnumerable<T> items){if (items == null)throw new ArgumentNullException(nameof(items));_lock.EnterWriteLock();try{ExecuteOnUIThread(() =>{var notificationBackup = BlockReentrancy();try{foreach (var item in items){base.InsertItem(Count, item);}}finally{notificationBackup.Dispose();}OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));});}finally{_lock.ExitWriteLock();}}}
}

使用示例:

// 创建线程安全的ObservableCollection
var safeCollection = new ThreadSafeObservableCollection<Person>();// 添加一些测试数据
safeCollection.Add(new Person { Name = "张三", Age = 28 });// 在后台线程上操作集合
Task.Run(() =>
{// 这是安全的,即使在后台线程上safeCollection.Add(new Person { Name = "后台添加的人员", Age = 35 });safeCollection[0].Age = 29; // 修改项目属性(假设Person实现了INotifyPropertyChanged)
});

CollectionView与ICollectionView接口

CollectionView概述

CollectionView是WPF提供的一个强大功能,它在数据源集合与UI控件之间添加了一个视图层,使我们能够对显示的数据进行排序、筛选和分组,而无需修改原始数据源。

数据源集合
CollectionView
ItemsControl
排序
筛选
分组
当前项管理

当将集合绑定到ItemsControl时,WPF不会直接使用集合本身,而是通过CollectionView进行包装。这一设计允许多个控件绑定到同一个集合,但每个控件可以有不同的视图行为。

ICollectionView接口

ICollectionView是WPF集合视图的核心接口,定义了集合视图的基本功能:

// ICollectionView接口定义的主要成员
public interface ICollectionView : IEnumerable, INotifyCollectionChanged
{// 是否可以筛选bool CanFilter { get; }// 筛选谓词Predicate<object> Filter { get; set; }// 集合分组描述ObservableCollection<GroupDescription> GroupDescriptions { get; }// 排序描述SortDescriptionCollection SortDescriptions { get; }// 当前项相关object CurrentItem { get; }int CurrentPosition { get; }bool IsCurrentAfterLast { get; }bool IsCurrentBeforeFirst { get; }// 导航方法bool MoveCurrentToFirst();bool MoveCurrentToLast();bool MoveCurrentToNext();bool MoveCurrentToPrevious();bool MoveCurrentTo(object item);bool MoveCurrentToPosition(int position);// 刷新视图void Refresh();// 延迟刷新IDisposable DeferRefresh();// 事件event EventHandler CurrentChanged;event CurrentChangingEventHandler CurrentChanging;
}

获取和使用CollectionView

有两种主要方式获取CollectionView:

  1. 通过CollectionViewSource类(XAML中常用)
  2. 直接通过CollectionViewSource.GetDefaultView方法(代码中常用)
XAML中使用CollectionViewSource
<Window x:Class="CollectionViewDemo.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="CollectionView示例" Height="450" Width="800"><Window.Resources><!-- 定义CollectionViewSource --><CollectionViewSource x:Key="PeopleViewSource" Source="{Binding People}"><CollectionViewSource.SortDescriptions><!-- 按年龄升序排序 --><scm:SortDescription PropertyName="Age" Direction="Ascending" xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"/></CollectionViewSource.SortDescriptions></CollectionViewSource></Window.Resources><Grid><!-- 使用CollectionViewSource作为ItemsSource --><ListBox ItemsSource="{Binding Source={StaticResource PeopleViewSource}}" Margin="10"><ListBox.ItemTemplate><DataTemplate><StackPanel Orientation="Horizontal"><TextBlock Text="{Binding Name}" FontWeight="Bold" /><TextBlock Text=", " /><TextBlock Text="{Binding Age}" /><TextBlock Text="" /></StackPanel></DataTemplate></ListBox.ItemTemplate></ListBox></Grid>
</Window>
代码中获取和操作CollectionView
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;namespace CollectionViewDemo
{public partial class MainWindow : Window{// 定义数据源集合private ObservableCollection<Person> _people;// 集合视图对象private ICollectionView _peopleView;public MainWindow(){InitializeComponent();// 初始化数据_people = new ObservableCollection<Person>{new Person { Name = "张三", Age = 28, Gender = "男" },new Person { Name = "李四", Age = 32, Gender = "男" },new Person { Name = "王五", Age = 25, Gender = "男" },new Person { Name = "赵六", Age = 41, Gender = "男" },new Person { Name = "小红", Age = 22, Gender = "女" },new Person { Name = "小芳", Age = 35, Gender = "女" }};// 获取集合的默认视图_peopleView = CollectionViewSource.GetDefaultView(_people);// 设置DataContextDataContext = this;// 将ListView的ItemsSource设置为集合的视图peopleListView.ItemsSource = _peopleView;}// 公开数据集合属性,供绑定使用public ObservableCollection<Person> People => _people;// 获取或创建排序后的视图private ICollectionView GetSortedView(){var view = CollectionViewSource.GetDefaultView(_people);// 清除现有排序view.SortDescriptions.Clear();// 添加排序描述(按年龄排序)view.SortDescriptions.Add(new SortDescription("Age", ListSortDirection.Ascending));return view;}}
}

集合的排序、过滤与分组

排序功能

CollectionView提供了强大的排序功能,可以根据一个或多个属性对集合进行排序:

// 示例:根据多个条件排序
private void ApplySorting(ICollectionView view)
{// 清除现有排序view.SortDescriptions.Clear();// 首先按性别排序(升序)view.SortDescriptions.Add(new SortDescription("Gender", ListSortDirection.Ascending));// 然后按年龄排序(降序)view.SortDescriptions.Add(new SortDescription("Age", ListSortDirection.Descending));// 最后按姓名排序(升序)view.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
}

自定义排序

对于复杂的排序逻辑,可以使用IComparer实现自定义排序:

using System.Collections;
using System.ComponentModel;
using System.Windows.Data;// 自定义比较器
public class PersonNameComparer : IComparer
{// 实现Compare方法public int Compare(object x, object y){// 转换为Person对象var person1 = x as Person;var person2 = y as Person;if (person1 == null || person2 == null)return 0;// 自定义排序逻辑 - 按姓氏拼音排序string surname1 = GetSurname(person1.Name);string surname2 = GetSurname(person2.Name);return string.Compare(surname1, surname2);}// 获取中文姓氏private string GetSurname(string fullName){// 简单处理:假设第一个字是姓氏if (!string.IsNullOrEmpty(fullName) && fullName.Length > 0)return fullName.Substring(0, 1);return string.Empty;}
}// 应用自定义排序
private void ApplyCustomSorting()
{// 获取集合视图ICollectionView view = CollectionViewSource.GetDefaultView(_people);// 创建自定义排序描述view.SortDescriptions.Clear();// 使用自定义比较器进行排序ListCollectionView listView = view as ListCollectionView;if (listView != null){listView.CustomSort = new PersonNameComparer();}
}

过滤功能

CollectionView的过滤功能允许只显示符合特定条件的项目:

// 应用过滤
private void ApplyFilter(ICollectionView view, string genderFilter)
{// 设置过滤器view.Filter = item =>{// 转换为Person对象Person person = item as Person;if (person == null)return false;// 如果过滤条件为空,显示所有项if (string.IsNullOrEmpty(genderFilter))return true;// 按性别过滤return person.Gender == genderFilter;};
}// 动态更新过滤器
private void FilterComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{ComboBox comboBox = sender as ComboBox;string filterValue = comboBox.SelectedItem as string;// 获取集合视图ICollectionView view = CollectionViewSource.GetDefaultView(_people);// 应用过滤器ApplyFilter(view, filterValue);
}// 清除过滤器
private void ClearFilterButton_Click(object sender, RoutedEventArgs e)
{// 获取集合视图ICollectionView view = CollectionViewSource.GetDefaultView(_people);// 清除过滤器view.Filter = null;
}

分组功能

CollectionView的分组功能允许将集合中的项目按特定规则分组显示:

// 应用分组
private void ApplyGrouping(ICollectionView view)
{// 清除现有分组view.GroupDescriptions.Clear();// 按性别分组view.GroupDescriptions.Add(new PropertyGroupDescription("Gender"));
}

在XAML中使用分组:

<Window x:Class="CollectionViewDemo.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="分组示例" Height="450" Width="800"><Window.Resources><CollectionViewSource x:Key="PeopleViewSource" Source="{Binding People}"><CollectionViewSource.GroupDescriptions><!-- 按性别分组 --><PropertyGroupDescription PropertyName="Gender" /></CollectionViewSource.GroupDescriptions></CollectionViewSource><!-- 分组项目模板 --><DataTemplate x:Key="GroupHeaderTemplate"><TextBlock Text="{Binding Name}" FontWeight="Bold" FontSize="14" Margin="0,10,0,5" /></DataTemplate></Window.Resources><Grid><ListView ItemsSource="{Binding Source={StaticResource PeopleViewSource}}" Margin="10"><!-- 启用分组 --><ListView.GroupStyle><GroupStyle HeaderTemplate="{StaticResource GroupHeaderTemplate}" /></ListView.GroupStyle><ListView.View><GridView><GridViewColumn Header="姓名" DisplayMemberBinding="{Binding Name}" Width="100" /><GridViewColumn Header="年龄" DisplayMemberBinding="{Binding Age}" Width="50" /><GridViewColumn Header="性别" DisplayMemberBinding="{Binding Gender}" Width="50" /></GridView></ListView.View></ListView></Grid>
</Window>

自定义分组

对于复杂的分组规则,可以实现自定义GroupDescription:

// 自定义分组描述 - 按年龄段分组
public class AgeRangeGroupDescription : GroupDescription
{// 重写分组方法public override object GroupNameFromItem(object item, int level, CultureInfo culture){// 转换为Person对象Person person = item as Person;if (person == null)return null;// 根据年龄范围分组if (person.Age < 20)return "20岁以下";else if (person.Age < 30)return "20-29岁";else if (person.Age < 40)return "30-39岁";elsereturn "40岁及以上";}
}// 应用自定义分组
private void ApplyCustomGrouping(ICollectionView view)
{// 清除现有分组view.GroupDescriptions.Clear();// 添加自定义分组view.GroupDescriptions.Add(new AgeRangeGroupDescription());
}

当前项管理

CollectionView提供了强大的当前项(CurrentItem)管理功能,对于实现主从视图(Master-Detail)界面特别有用。

当前项基本操作

// 当前项管理示例代码
public partial class CurrentItemDemo : Window
{private ObservableCollection<Person> _people;private ICollectionView _peopleView;public CurrentItemDemo(){InitializeComponent();// 初始化数据_people = new ObservableCollection<Person>{new Person { Name = "张三", Age = 28, Gender = "男" },new Person { Name = "李四", Age = 32, Gender = "男" },new Person { Name = "王五", Age = 25, Gender = "男" }};// 获取集合视图_peopleView = CollectionViewSource.GetDefaultView(_people);// 设置当前项变更事件处理_peopleView.CurrentChanged += PeopleView_CurrentChanged;// 将ListView绑定到集合视图peopleListView.ItemsSource = _peopleView;// 设置DataContextDataContext = this;}// 当前项变更事件处理private void PeopleView_CurrentChanged(object sender, EventArgs e){// 获取当前项Person currentPerson = _peopleView.CurrentItem as Person;// 更新详情视图if (currentPerson != null){detailsPanel.DataContext = currentPerson;}}// 导航按钮处理private void FirstButton_Click(object sender, RoutedEventArgs e){_peopleView.MoveCurrentToFirst();}private void PreviousButton_Click(object sender, RoutedEventArgs e){_peopleView.MoveCurrentToPrevious();}private void NextButton_Click(object sender, RoutedEventArgs e){_peopleView.MoveCurrentToNext();}private void LastButton_Click(object sender, RoutedEventArgs e){_peopleView.MoveCurrentToLast();}
}

对应的XAML代码:

<Window x:Class="CollectionBindingDemo.CurrentItemDemo"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="主从视图示例" Height="450" Width="800"><Grid><Grid.ColumnDefinitions><ColumnDefinition Width="*" /><ColumnDefinition Width="*" /></Grid.ColumnDefinitions><Grid.RowDefinitions><RowDefinition Height="*" /><RowDefinition Height="Auto" /></Grid.RowDefinitions><!-- 主视图:列表 --><ListView x:Name="peopleListView" Grid.Column="0" Margin="10"IsSynchronizedWithCurrentItem="True"><ListView.View><GridView><GridViewColumn Header="姓名" DisplayMemberBinding="{Binding Name}" Width="100" /><GridViewColumn Header="年龄" DisplayMemberBinding="{Binding Age}" Width="50" /></GridView></ListView.View></ListView><!-- 从视图:详情面板 --><StackPanel x:Name="detailsPanel" Grid.Column="1" Margin="20"><TextBlock Text="详细信息" FontWeight="Bold" FontSize="16" Margin="0,0,0,10" /><Grid><Grid.ColumnDefinitions><ColumnDefinition Width="Auto" /><ColumnDefinition Width="*" /></Grid.ColumnDefinitions><Grid.RowDefinitions><RowDefinition Height="Auto" /><RowDefinition Height="Auto" /><RowDefinition Height="Auto" /></Grid.RowDefinitions><TextBlock Text="姓名:" Grid.Row="0" Grid.Column="0" Margin="0,5" /><TextBlock Text="{Binding Name}" Grid.Row="0" Grid.Column="1" Margin="0,5" /><TextBlock Text="年龄:" Grid.Row="1" Grid.Column="0" Margin="0,5" /><TextBlock Text="{Binding Age}" Grid.Row="1" Grid.Column="1" Margin="0,5" /><TextBlock Text="性别:" Grid.Row="2" Grid.Column="0" Margin="0,5" /><TextBlock Text="{Binding Gender}" Grid.Row="2" Grid.Column="1" Margin="0,5" /></Grid></StackPanel><!-- 导航按钮 --><StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,10"><Button Content="第一条" Click="FirstButton_Click" Width="80" Margin="5" /><Button Content="上一条" Click="PreviousButton_Click" Width="80" Margin="5" /><Button Content="下一条" Click="NextButton_Click" Width="80" Margin="5" /><Button Content="最后一条" Click="LastButton_Click" Width="80" Margin="5" /></StackPanel></Grid>
</Window>

IsSynchronizedWithCurrentItem属性

在XAML中,IsSynchronizedWithCurrentItem="True"属性是实现列表控件与当前项同步的关键:

<ListBox ItemsSource="{Binding Source={StaticResource PeopleViewSource}}"IsSynchronizedWithCurrentItem="True" />

当此属性设置为True时,ItemsControl会自动跟踪和显示CollectionView的当前项,并在UI上高亮显示。

CurrentItem与SelectedItem的区别

CollectionView的CurrentItem与ItemsControl的SelectedItem是两个不同但相关的概念:

  • SelectedItem:是控件自身的属性,仅影响该控件本身
  • CurrentItem:是CollectionView的属性,影响所有绑定到该视图的控件

IsSynchronizedWithCurrentItem="True"时,这两个属性会保持同步。

实际应用案例

使用集合绑定实现可编辑数据表格

下面是一个使用ObservableCollection和DataGrid实现可编辑数据表格的示例:

using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;namespace DataGridDemo
{public partial class EditableDataGridWindow : Window{// ViewModel类public class ProductsViewModel : INotifyPropertyChanged{private ObservableCollection<Product> _products;private ICollectionView _productsView;public ProductsViewModel(){// 初始化产品集合Products = new ObservableCollection<Product>{new Product { ID = 1, Name = "笔记本电脑", Category = "电子产品", Price = 5999.00m, InStock = true },new Product { ID = 2, Name = "无线鼠标", Category = "电子产品", Price = 129.00m, InStock = true },new Product { ID = 3, Name = "办公椅", Category = "办公家具", Price = 899.00m, InStock = false },new Product { ID = 4, Name = "显示器", Category = "电子产品", Price = 1299.00m, InStock = true },new Product { ID = 5, Name = "文件柜", Category = "办公家具", Price = 499.00m, InStock = true }};// 获取和配置集合视图ProductsView = CollectionViewSource.GetDefaultView(Products);// 设置默认排序ProductsView.SortDescriptions.Add(new SortDescription("Category", ListSortDirection.Ascending));ProductsView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));// 设置默认分组ProductsView.GroupDescriptions.Add(new PropertyGroupDescription("Category"));}// 产品集合属性public ObservableCollection<Product> Products{get => _products;set{_products = value;OnPropertyChanged(nameof(Products));}}// 产品视图属性public ICollectionView ProductsView{get => _productsView;set{_productsView = value;OnPropertyChanged(nameof(ProductsView));}}// 添加新产品public void AddProduct(Product product){// 设置新ID(简单实现)product.ID = Products.Count > 0 ? Products.Max(p => p.ID) + 1 : 1;// 添加到集合Products.Add(product);// 使新添加的产品成为当前项ProductsView.MoveCurrentTo(product);}// 删除产品public void RemoveProduct(Product product){if (product != null && Products.Contains(product)){Products.Remove(product);}}// INotifyPropertyChanged实现public event PropertyChangedEventHandler PropertyChanged;protected virtual void OnPropertyChanged(string propertyName){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}}// 产品数据模型public class Product : INotifyPropertyChanged{private int _id;private string _name;private string _category;private decimal _price;private bool _inStock;public int ID{get => _id;set{if (_id != value){_id = value;OnPropertyChanged(nameof(ID));}}}public string Name{get => _name;set{if (_name != value){_name = value;OnPropertyChanged(nameof(Name));}}}public string Category{get => _category;set{if (_category != value){_category = value;OnPropertyChanged(nameof(Category));}}}public decimal Price{get => _price;set{if (_price != value){_price = value;OnPropertyChanged(nameof(Price));}}}public bool InStock{get => _inStock;set{if (_inStock != value){_inStock = value;OnPropertyChanged(nameof(InStock));}}}// INotifyPropertyChanged实现public event PropertyChangedEventHandler PropertyChanged;protected virtual void OnPropertyChanged(string propertyName){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}}// ViewModel实例private ProductsViewModel _viewModel;public EditableDataGridWindow(){InitializeComponent();// 初始化ViewModel并设置为DataContext_viewModel = new ProductsViewModel();DataContext = _viewModel;// 绑定数据源productsDataGrid.ItemsSource = _viewModel.ProductsView;}// 添加新产品按钮事件处理private void AddButton_Click(object sender, RoutedEventArgs e){// 创建新产品(默认值)var newProduct = new Product{Name = "新产品",Category = "未分类",Price = 0.00m,InStock = true};// 添加到集合_viewModel.AddProduct(newProduct);}// 删除产品按钮事件处理private void DeleteButton_Click(object sender, RoutedEventArgs e){// 获取当前选中产品var selectedProduct = productsDataGrid.SelectedItem as Product;if (selectedProduct != null){// 从集合中删除_viewModel.RemoveProduct(selectedProduct);}}}
}

对应的XAML代码:

<Window x:Class="DataGridDemo.EditableDataGridWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="可编辑数据表格" Height="600" Width="800"><Grid><Grid.RowDefinitions><RowDefinition Height="*" /><RowDefinition Height="Auto" /></Grid.RowDefinitions><!-- 数据表格 --><DataGrid x:Name="productsDataGrid" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False"CanUserReorderColumns="True" CanUserResizeColumns="True"CanUserSortColumns="True" AlternatingRowBackground="AliceBlue"GridLinesVisibility="Horizontal" Margin="10"><!-- 启用分组 --><DataGrid.GroupStyle><GroupStyle><GroupStyle.HeaderTemplate><DataTemplate><StackPanel Orientation="Horizontal"><TextBlock Text="{Binding Name}" FontWeight="Bold" FontSize="14" Foreground="DarkBlue" /><TextBlock Text=" [" Foreground="DarkBlue" /><TextBlock Text="{Binding ItemCount}" Foreground="DarkBlue" /><TextBlock Text=" 个产品]" Foreground="DarkBlue" /></StackPanel></DataTemplate></GroupStyle.HeaderTemplate></GroupStyle></DataGrid.GroupStyle><!-- 列定义 --><DataGrid.Columns><DataGridTextColumn Header="ID" Binding="{Binding ID}" IsReadOnly="True" Width="50" /><DataGridTextColumn Header="产品名称" Binding="{Binding Name}" Width="150" /><DataGridTextColumn Header="类别" Binding="{Binding Category}" Width="100" /><DataGridTextColumn Header="价格" Binding="{Binding Price, StringFormat={}{0:C}}" Width="100" /><DataGridCheckBoxColumn Header="有库存" Binding="{Binding InStock}" Width="70" /></DataGrid.Columns></DataGrid><!-- 按钮面板 --><StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,10"><Button Content="添加产品" Click="AddButton_Click" Width="100" Margin="5" /><Button Content="删除选中" Click="DeleteButton_Click" Width="100" Margin="5" /></StackPanel></Grid>
</Window>

总结与最佳实践

集合绑定的最佳实践

  1. 选择正确的集合类型

    • 使用ObservableCollection作为默认选择
    • 对于特殊需求(批量操作、线程安全等),使用增强版实现
  2. 视图层与数据层分离

    • 不要在视图代码中直接修改集合
    • 使用MVVM模式分离关注点
  3. 性能优化

    • 对于大集合使用UI虚拟化
    • 避免频繁更新UI
    • 使用批量操作减少通知事件
  4. 调试技巧

    • 使用PresentationTraceSources.TraceLevel附加属性调试绑定问题
    • 检查Output窗口的绑定错误

常见问题解决

  1. 集合变化但UI不更新

    • 确保使用ObservableCollection
    • 检查是否在正确的线程修改集合
    • 确认绑定路径正确
  2. 绑定性能问题

    • 启用虚拟化:VirtualizingStackPanel.IsVirtualizing=“True”
    • 禁用缓存回收:VirtualizingStackPanel.VirtualizationMode=“Recycling”
    • 考虑使用延迟加载
  3. 线程访问问题

    • 使用Dispatcher.Invoke在UI线程上修改集合
    • 考虑使用线程安全版ObservableCollection
    • 明确理解线程同步原则

学习资源

官方文档

  • Microsoft Docs - 数据绑定概述
  • Microsoft Docs - ObservableCollection 类
  • Microsoft Docs - CollectionView 类

社区资源

  • CodeProject - WPF中的高级数据绑定
  • GitHub - WPF样例集合

书籍推荐

  • 《Windows Presentation Foundation开发指南》
  • 《Pro WPF 4.5 in C#》–Matthew MacDonald
  • 《WPF深入浅出》-- 刘铁锰
http://www.xdnf.cn/news/372259.html

相关文章:

  • 计算机网络:什么是Mesh组网以及都有哪些设备支持Mesh组网?
  • drf 使用jwt
  • cv_connection (像halcon一样对区域进行打散)
  • .Net Mqtt协议-MQTTNet(一)简介
  • 养生:为健康生活筑牢根基
  • 路由重发布
  • 软件测试——用例篇(3)
  • 嵌入式与物联网:C 语言在边缘计算时代的破局之道
  • OSPF不规则区域划分
  • Win10无法上网:Windows 无法访问指定设备、路径或文件。你可能没有适当的权限访问该项目找不到域 TEST 的域控制器DNS 解析存在问题
  • 大节点是选择自建机房还是托管机房
  • 数据结构与算法分析实验12 实现二叉查找树
  • 深入理解 TCP:重传机制、滑动窗口、流量控制与拥塞控制
  • 考研408《计算机组成原理》复习笔记,第三章数值数据的表示和运算(定点数篇)
  • Ping 不通外网,Ping 得通主机问题解决小记
  • BUUCTF——Cookie is so stable
  • 《C++探幽:模板从初阶到进阶》
  • Docker Desktop安装在其他盘
  • [面试]SoC验证工程师面试常见问题(七)低速接口篇
  • rust-candle学习笔记13-实现多头注意力
  • Skyvern:用 AI+视觉驱动浏览器自动化
  • FreeTex v0.2.0:功能升级/支持Mac
  • Ubuntu 22.04(WSL2)使用 Docker 安装 Zipkin 和 Skywalking
  • 【含文档+PPT+源码】基于微信小程序的社区便民防诈宣传系统设计与实现
  • 基本句子结构
  • 前端取经路——现代API探索:沙僧的通灵法术
  • 每天五分钟机器学习:KTT条件
  • 在 Excel 中有效筛选重复元素
  • Stable Diffusion XL 文生图
  • 【金仓数据库征文】金融行业中的国产化数据库替代应用实践