WPF之集合绑定深入

news2025/7/20 12:30:52

文章目录

    • 引言
    • 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为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:

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

// 示例:根据多个条件排序
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岁";
        else
            return "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;
        
        // 设置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代码:

<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.coloradmin.cn/o/2375854.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

(C语言)超市管理系统(测试2版)(指针)(数据结构)(清屏操作)

目录 前言&#xff1a; 源代码&#xff1a; product.h product.c fileio.h fileio.c main.c 代码解析&#xff1a; 一、程序结构概述 二、product.c 函数详解 1. 初始化商品列表 Init_products 2. 添加商品 add_product 3. 显示商品 display_products 4. 修改商品 mo…

编译openssl源码

openssl版本 1.1.1c windows 安装环境 perl 先安装perl&#xff0c;生成makefile需要 https://strawberryperl.com/releases.html nasm nasm 也是生成makefile需要 https://www.nasm.us/ 安装完perl输入一下nasm&#xff0c;看看能不能找到&#xff0c;找不到的话需要配…

OpenCV实现数字水印的相关函数和示例代码

OpenCV计算机视觉开发实践&#xff1a;基于Qt C - 商品搜索 - 京东 实现数字水印的相关函数 用OpenCV来实现数字水印功能&#xff0c;需要使用一些位操作函数&#xff0c;我们需要先了解一下这些函数。 1. bitwise_and函数 bitwise_and函数是OpenCV中的位运算函数之一&…

坐席业绩数据分析

豆包提示词&#xff1a; 使用papaparse.js&#xff0c;chart.js&#xff0c;tailwindcss和font-awesome&#xff0c;生成一个可以交互的简洁且可以运行的HTML代码&#xff0c;不要输出无关内容。 具体要求如下&#xff1a; 1、按坐席姓名输出业绩折线图。 2、系统导航区域&…

怎样将MM模块常用报表设置为ALV默认格式(MB52、MB5B、ME2M、ME1M等)

【SAP系统研究】 对SAP系统中的报表,最方便的格式就是ALV了,可排序、可导出,非常友好。 但有些常见报表却不是默认ALV界面的,譬如MB52: 是不是有点别扭?但其实是可以后台配置进行调整的。 现将一些常用报表修改为默认ALV的方法进行总结,便于大家使用。 一、MB52、MB5…

Arduino使用红外收发模块

目录 Arduino UNO连接红外发射模块&#xff1a; Arduino D1连接红外接收模块&#xff1a; 有一个Arduini UNO板子和一个Arduino D1板子&#xff0c;我想通过红外发射模块和红外接收模块让他们进行通信。 先看结果&#xff1a; Arduino UNO连接红外发射模块&#xff1a; 发射模…

机器学习 Day16 聚类算法 ,数据降维

聚类算法 1.简介 1.1 聚类概念 无监督学习&#xff1a;聚类是一种无监督学习算法&#xff0c;不需要预先标记的训练数据 相似性分组&#xff1a;根据样本之间的相似性自动将样本归到不同类别 相似度度量&#xff1a;常用欧式距离作为相似度计算方法 1.2 聚类vs分类 聚类&…

软件测试——面试八股文(入门篇)

今天给大家分享软件测试面试题入门篇&#xff0c;看看大家能答对几题 一、 请你说一说测试用例的边界 参考回答&#xff1a; 边界值分析法就是对输入或输出的边界值进行测试的一种黑盒测试方法。通常边界值分析法是作为对等价类划分法的补充&#xff0c;这种情况下&#xff…

Yolov8的详解与实战-深度学习目标检测

Yolov8的详解与实战- 文章目录 摘要 模型详解 C2F模块 Loss head部分 模型实战 训练COCO数据集 下载数据集 COCO转yolo格式数据集&#xff08;适用V4&#xff0c;V5&#xff0c;V6&#xff0c;V7&#xff0c;V8&#xff09; 配置yolov8环境 训练 测试 训练自定义数据集 Labelme…

Python(1) 做一个随机数的游戏

有关变量的&#xff0c;其实就是 可以直接打印对应变量。 并且最后倒数第二行就是可以让两个数进行交换。 Py快捷键“ALTP 就是显示上一句的代码。 —————————————————————————————— 字符串 用 双引号或者单引号 。 然后 保证成双出现即可 要是…

【Bootstrap V4系列】学习入门教程之 组件-导航条(Navbar)

Bootstrap V4系列 学习入门教程之 组件-导航条&#xff08;Navbar&#xff09; 导航条&#xff08;Navbar&#xff09;一、How it works二、Supported content 支持的内容2.1 Brand 品牌2.2 Nav 导航2.3 Forms 表格 三、Color schemes 配色方案四、Containers 容器五、Placemen…

[Java实战]Spring Security 添加验证码(二十三)

[Java实战]Spring Security 添加验证码&#xff08;二十三&#xff09; 在现代的 Web 应用中&#xff0c;验证码是防止恶意攻击&#xff08;如暴力破解、自动注册等&#xff09;的重要手段之一。Spring Security 是一个功能强大的安全框架&#xff0c;提供了用户认证、授权等功…

万文c++继承

1、继承的概念与定义 1.1继承的概念 继承&#xff1a;是c代码复用的手段&#xff0c;允许在原有的基础上扩展&#xff0c;在此之前都是函数层次的复用&#xff0c;继承是类设计层次的复用。 下面有两个类Student和Teacher都有姓名/地址/电话/年龄等成员变量。都有identity身…

Linux grep -r 查找依赖包是否存在依赖类 Class

方法一&#xff1a;通过 Linux &#xff0c;grep -r ClassPath 命令 grep -f org.apache.kafka.connect.source.SourceRecord在 jar 包所在 lib 或者 lib/plugins 目录下执行&#xff0c;grep -r&#xff0c; flink-sql-connector-sqlserver-cdc-3.3.0.jar 中此 kafka Source…

41:像素坐标与实际坐标转化

采用上面的算子 将像素坐标点转换为实际坐标 image_points_to_world_plane(CamParam, Worldpose, Row, Column, m, X, Y) 第一个参数&#xff1a;标定得到的内参--根据标定助手得到的 第二个参数&#xff1a;标定得到的外参--根据标定助手得到的 第三个参数&#xff1a;计算…

大某麦演唱会门票如何自动抢

引言 仅供学习研究&#xff0c;欢迎交流 抢票难&#xff0c;难于上青天&#xff01;无论是演唱会、话剧还是体育赛事&#xff0c;大麦网的票总是秒光。大麦网是国内知名的票务平台&#xff0c;热门演出票往往一票难求。手动抢票不仅耗时&#xff0c;还容易错过机会。作为一名…

LVS负载均衡群集和keepalive

目录 一. 集群概述 1.1 集群的定义 1.2 集群的分类 1. 高可用集群 HA 2. 高性能运输群集 HPC 3.负载均衡群集 LB 4. 分布式存储集群 二. LVS概述 2.1 LVS的定义 2.2 LVS的工作原理 2.3 LVS 的三种工作模式 2.4 LVS 三种工作模式的对比 2.5 LVS 调度算法 1. 静态…

Apache Pulsar 消息、流、存储的融合

Apache Pulsar 消息、流、存储的融合 消息队列在大层面有两种不同类型的应用&#xff0c;一种是在线系统的message queue&#xff0c;一种是流计算&#xff0c;data pipeline的streaming高throughout&#xff0c;一致性较低&#xff0c;延迟较差的过程。 存算分离 扩容和缩容快…

最优化方法Python计算:有约束优化应用——线性可分问题支持向量机

设问题的数据样本点 ( x i , y i ) (\boldsymbol{x}_i,y_i) (xi​,yi​)&#xff0c; x i ∈ R n \boldsymbol{x}_i\in\text{R}^n xi​∈Rn&#xff0c; y i 1 y_i\pm1 yi​1&#xff0c; i 1 , 2 , ⋯ , m i1,2,\cdots,m i1,2,⋯,m。由于标签数据 y i ∈ { − 1 , 1 } y_i\…

SpringBoot学习(上) , SpringBoot项目的创建(IDEA2024版本)

目录 1. SpringBoot介绍 SpringBoot特点 2. SpringBoot入门 2.1 创建SpringBoot项目 Spring Initialize 第一步: 选择创建项目 第二步: 选择起步依赖 第三步: 查看启动类 2.2 springboot父项目 2.3 测试案例 2.3.1 数据库 2.3.2 生成代码 1. SpringBoot介绍 Spring B…