WPF之集合绑定深入

文章目录

可以根据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使用示例:

csharp 复制代码
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为ObservableCollection
            peopleListBox.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:

xml 复制代码
<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层之间的数据桥梁。

csharp 复制代码
// 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绑定示例:

xml 复制代码
<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能够自动响应集合的变化。

csharp 复制代码
// INotifyCollectionChanged接口定义
public interface INotifyCollectionChanged
{
    // 当集合变化时触发的事件
    event NotifyCollectionChangedEventHandler CollectionChanged;
}

// 事件处理委托
public delegate void NotifyCollectionChangedEventHandler(object sender, NotifyCollectionChangedEventArgs e);

NotifyCollectionChangedEventArgs详解

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

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

自定义INotifyCollectionChanged实现

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

csharp 复制代码
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一个主要的限制是没有高效的批量操作支持,每次添加或删除都会触发通知。下面是一个扩展实现,支持批量操作:

csharp 复制代码
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);
            }
        }
    }
}

使用示例:

csharp 复制代码
// 创建支持批量操作的集合
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另一个限制是它只能监听集合项目的添加、删除等操作,而无法感知集合中的对象本身属性的变化。以下是一个可以监听集合元素属性变化的实现:

csharp 复制代码
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; }
    }
}

使用示例:

csharp 复制代码
// 创建可以监听元素属性变化的集合
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的另一个限制是它不是线程安全的,只能在创建它的线程上修改。以下是一个线程安全的实现:

csharp 复制代码
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();
            }
        }
    }
}

使用示例:

csharp 复制代码
// 创建线程安全的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集合视图的核心接口,定义了集合视图的基本功能:

csharp 复制代码
// 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
xml 复制代码
<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
csharp 复制代码
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);
            
            // 设置DataContext
            DataContext = 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提供了强大的排序功能,可以根据一个或多个属性对集合进行排序:

csharp 复制代码
// 示例:根据多个条件排序
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实现自定义排序:

csharp 复制代码
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的过滤功能允许只显示符合特定条件的项目:

csharp 复制代码
// 应用过滤
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的分组功能允许将集合中的项目按特定规则分组显示:

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

在XAML中使用分组:

xml 复制代码
<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:

csharp 复制代码
// 自定义分组描述 - 按年龄段分组
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岁";
        else
            return "40岁及以上";
    }
}

// 应用自定义分组
private void ApplyCustomGrouping(ICollectionView view)
{
    // 清除现有分组
    view.GroupDescriptions.Clear();
    
    // 添加自定义分组
    view.GroupDescriptions.Add(new AgeRangeGroupDescription());
}

当前项管理

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

当前项基本操作

csharp 复制代码
// 当前项管理示例代码
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;
        
        // 设置DataContext
        DataContext = 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代码:

xml 复制代码
<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"属性是实现列表控件与当前项同步的关键:

xml 复制代码
<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实现可编辑数据表格的示例:

csharp 复制代码
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代码:

xml 复制代码
<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
    • 明确理解线程同步原则

学习资源

官方文档

社区资源

书籍推荐

  • 《Windows Presentation Foundation开发指南》
  • 《Pro WPF 4.5 in C#》--Matthew MacDonald
  • 《WPF深入浅出》-- 刘铁锰
相关推荐
编程乐趣8 分钟前
SwarmUI:基于.Net开发的开源AI 图像生成 Web 用户界面系统
人工智能·开源·c#·.net
带娃的IT创业者1 小时前
《AI大模型应知应会100篇》第58篇:Semantic Kernel:微软的大模型应用框架
人工智能·microsoft·flask
离歌漠2 小时前
WPF内嵌其他进程的窗口
c#·wpf
极小狐6 小时前
如何使用极狐GitLab 软件包仓库功能托管 maven?
java·运维·数据库·安全·c#·gitlab·maven
嗯.~8 小时前
【无标题】如何在sheel中运行Spark
前端·javascript·c#
Leinwin8 小时前
Microsoft Azure 在印度尼西亚区域正式上线
microsoft·azure
沉到海底去吧Go12 小时前
【身份证识别表格】批量识别身份证扫描件或照片保存为Excel表格,怎么大批量将身份证图片转为excel表格?基于WPF和腾讯OCR的识别方案
ocr·wpf·excel·身份证识别表格·批量扫描件身份证转表格·图片识别表格·图片识别excel表格
csdn_aspnet14 小时前
WPF 性能 UI 虚拟化 软件开发人员的思考
ui·wpf
冰茶_15 小时前
WPF之绑定模式深入
学习·microsoft·微软·c#·wpf·绑定模式
vortex515 小时前
微软输入法常用快捷键介绍以及调教技巧
microsoft