WPF之集合绑定深入
文章目录
- 引言
- ObservableCollection<T>基础
- 什么是ObservableCollection
- ObservableCollection的工作原理
- 基本用法示例
- ObservableCollection与MVVM模式
- ObservableCollection的局限性
- INotifyCollectionChanged接口深入
- 接口定义与作用
- NotifyCollectionChangedEventArgs详解
- 自定义INotifyCollectionChanged实现
- 扩展ObservableCollection功能
- 实现批量操作
- 处理集合元素属性变化
- 线程安全的ObservableCollection
- CollectionView与ICollectionView接口
- CollectionView概述
- ICollectionView接口
- 获取和使用CollectionView
- XAML中使用CollectionViewSource
- 代码中获取和操作CollectionView
- 集合的排序、过滤与分组
- 排序功能
- 自定义排序
- 过滤功能
- 分组功能
- 自定义分组
- 当前项管理
- 当前项基本操作
- IsSynchronizedWithCurrentItem属性
- CurrentItem与SelectedItem的区别
- 实际应用案例
- 使用集合绑定实现可编辑数据表格
- 总结与最佳实践
- 集合绑定的最佳实践
- 常见问题解决
- 学习资源
- 官方文档
- 社区资源
- 书籍推荐
可以根据Github拉取示例程序运行
GitHub程序演示地址(点击直达)
也可以在本文资源中下载
引言
在WPF应用程序开发中,数据绑定是连接UI和数据的桥梁,而集合绑定则是处理列表、表格等多项数据显示的核心机制。通过集合绑定,我们可以轻松地将数据源中的集合对象与ListBox、ListView、DataGrid等ItemsControl控件关联起来,实现数据的自动呈现与交互。
本文将深入探讨WPF集合绑定的高级特性和技术,帮助开发者更好地理解和应用这一强大机制,构建出更加灵活、高效的数据驱动界面。
集合绑定的核心元素包括:
ObservableCollection<T>基础
什么是ObservableCollection
ObservableCollection<T>是WPF提供的一个特殊集合类,它继承自Collection<T>并实现了INotifyCollectionChanged接口。这使得它能够在集合内容发生变化时自动通知UI,触发界面更新。
ObservableCollection相比普通集合(如List<T>、Array等)的最大优势在于它能够实时反馈集合变化,无需手动刷新界面。
ObservableCollection的工作原理
基本用法示例
下面是一个简单的ObservableCollection使用示例:
using System;
using System.Collections.ObjectModel;
using System.Windows;
using System.Windows.Controls;namespace CollectionBindingDemo
{public partial class MainWindow : Window{// 创建一个Person类型的ObservableCollection作为数据源private ObservableCollection<Person> _people;public MainWindow(){InitializeComponent();// 初始化集合并添加一些示例数据_people = new ObservableCollection<Person>{new Person { Name = "张三", Age = 28 },new Person { Name = "李四", Age = 32 },new Person { Name = "王五", Age = 25 }};// 设置ListBox的ItemsSource为ObservableCollectionpeopleListBox.ItemsSource = _people;}private void AddButton_Click(object sender, RoutedEventArgs e){// 添加新人员到集合 - 界面会自动更新_people.Add(new Person { Name = "新人员", Age = 30 });}private void RemoveButton_Click(object sender, RoutedEventArgs e){// 如果有选中项,则从集合中移除if (peopleListBox.SelectedItem != null){_people.Remove(peopleListBox.SelectedItem as Person);}}}// 定义Person类public class Person{public string Name { get; set; }public int Age { get; set; }// 重写ToString方法以便在ListBox中显示public override string ToString(){return $"{Name}, {Age}岁";}}
}
对应的XAML:
<Window x:Class="CollectionBindingDemo.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="集合绑定示例" Height="350" Width="500"><Grid><Grid.RowDefinitions><RowDefinition Height="*" /><RowDefinition Height="Auto" /></Grid.RowDefinitions><!-- 使用ListBox显示集合内容 --><ListBox x:Name="peopleListBox" Margin="10" /><!-- 添加和删除按钮 --><StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,10"><Button Content="添加人员" Click="AddButton_Click" Width="100" Margin="5" /><Button Content="删除所选" Click="RemoveButton_Click" Width="100" Margin="5" /></StackPanel></Grid>
</Window>
ObservableCollection与MVVM模式
在MVVM(Model-View-ViewModel)设计模式中,ObservableCollection通常在ViewModel中定义,作为View层和Model层之间的数据桥梁。
// ViewModel类
public class PeopleViewModel : INotifyPropertyChanged
{private ObservableCollection<Person> _people;// 公开的集合属性,供View绑定public ObservableCollection<Person> People{get => _people;set{_people = value;OnPropertyChanged(nameof(People));}}// 构造函数public PeopleViewModel(){// 初始化集合People = new ObservableCollection<Person>();LoadPeople(); // 加载数据}// 加载数据的方法private void LoadPeople(){// 实际应用中可能从数据库或服务加载People.Add(new Person { Name = "张三", Age = 28 });People.Add(new Person { Name = "李四", Age = 32 });People.Add(new Person { Name = "王五", Age = 25 });}// INotifyPropertyChanged实现public event PropertyChangedEventHandler PropertyChanged;protected virtual void OnPropertyChanged(string propertyName){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}
}
XAML绑定示例:
<Window x:Class="CollectionBindingDemo.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:vm="clr-namespace:CollectionBindingDemo.ViewModels"><Window.DataContext><vm:PeopleViewModel /></Window.DataContext><Grid><!-- 使用ListBox显示集合内容,ItemsSource绑定到ViewModel的People属性 --><ListBox ItemsSource="{Binding People}" Margin="10"><ListBox.ItemTemplate><DataTemplate><StackPanel Orientation="Horizontal"><TextBlock Text="{Binding Name}" FontWeight="Bold" /><TextBlock Text=", " /><TextBlock Text="{Binding Age}" /><TextBlock Text="岁" /></StackPanel></DataTemplate></ListBox.ItemTemplate></ListBox></Grid>
</Window>
ObservableCollection的局限性
虽然ObservableCollection非常实用,但它也有一些局限性:
- 对集合元素的属性变化不敏感 - 只有添加/删除/替换整个元素才会触发通知
- 线程安全问题 - 只能在创建集合的线程上修改集合
- 批量操作效率低 - 每次操作都会触发通知,大量操作会导致性能问题
这些局限性的解决方案将在后续章节中讨论。
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控件之间添加了一个视图层,使我们能够对显示的数据进行排序、筛选和分组,而无需修改原始数据源。
当将集合绑定到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:
- 通过CollectionViewSource类(XAML中常用)
- 直接通过CollectionViewSource.GetDefaultView方法(代码中常用)
XAML中使用CollectionViewSource
<Window x:Class="CollectionViewDemo.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="CollectionView示例" Height="450" Width="800"><Window.Resources><!-- 定义CollectionViewSource --><CollectionViewSource x:Key="PeopleViewSource" Source="{Binding People}"><CollectionViewSource.SortDescriptions><!-- 按年龄升序排序 --><scm:SortDescription PropertyName="Age" Direction="Ascending" xmlns:scm="clr-namespace:System.ComponentModel;assembly=WindowsBase"/></CollectionViewSource.SortDescriptions></CollectionViewSource></Window.Resources><Grid><!-- 使用CollectionViewSource作为ItemsSource --><ListBox ItemsSource="{Binding Source={StaticResource PeopleViewSource}}" Margin="10"><ListBox.ItemTemplate><DataTemplate><StackPanel Orientation="Horizontal"><TextBlock Text="{Binding Name}" FontWeight="Bold" /><TextBlock Text=", " /><TextBlock Text="{Binding Age}" /><TextBlock Text="岁" /></StackPanel></DataTemplate></ListBox.ItemTemplate></ListBox></Grid>
</Window>
代码中获取和操作CollectionView
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;namespace CollectionViewDemo
{public partial class MainWindow : Window{// 定义数据源集合private ObservableCollection<Person> _people;// 集合视图对象private ICollectionView _peopleView;public MainWindow(){InitializeComponent();// 初始化数据_people = new ObservableCollection<Person>{new Person { Name = "张三", Age = 28, Gender = "男" },new Person { Name = "李四", Age = 32, Gender = "男" },new Person { Name = "王五", Age = 25, Gender = "男" },new Person { Name = "赵六", Age = 41, Gender = "男" },new Person { Name = "小红", Age = 22, Gender = "女" },new Person { Name = "小芳", Age = 35, Gender = "女" }};// 获取集合的默认视图_peopleView = CollectionViewSource.GetDefaultView(_people);// 设置DataContextDataContext = this;// 将ListView的ItemsSource设置为集合的视图peopleListView.ItemsSource = _peopleView;}// 公开数据集合属性,供绑定使用public ObservableCollection<Person> People => _people;// 获取或创建排序后的视图private ICollectionView GetSortedView(){var view = CollectionViewSource.GetDefaultView(_people);// 清除现有排序view.SortDescriptions.Clear();// 添加排序描述(按年龄排序)view.SortDescriptions.Add(new SortDescription("Age", ListSortDirection.Ascending));return view;}}
}
集合的排序、过滤与分组
排序功能
CollectionView提供了强大的排序功能,可以根据一个或多个属性对集合进行排序:
// 示例:根据多个条件排序
private void ApplySorting(ICollectionView view)
{// 清除现有排序view.SortDescriptions.Clear();// 首先按性别排序(升序)view.SortDescriptions.Add(new SortDescription("Gender", ListSortDirection.Ascending));// 然后按年龄排序(降序)view.SortDescriptions.Add(new SortDescription("Age", ListSortDirection.Descending));// 最后按姓名排序(升序)view.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));
}
自定义排序
对于复杂的排序逻辑,可以使用IComparer实现自定义排序:
using System.Collections;
using System.ComponentModel;
using System.Windows.Data;// 自定义比较器
public class PersonNameComparer : IComparer
{// 实现Compare方法public int Compare(object x, object y){// 转换为Person对象var person1 = x as Person;var person2 = y as Person;if (person1 == null || person2 == null)return 0;// 自定义排序逻辑 - 按姓氏拼音排序string surname1 = GetSurname(person1.Name);string surname2 = GetSurname(person2.Name);return string.Compare(surname1, surname2);}// 获取中文姓氏private string GetSurname(string fullName){// 简单处理:假设第一个字是姓氏if (!string.IsNullOrEmpty(fullName) && fullName.Length > 0)return fullName.Substring(0, 1);return string.Empty;}
}// 应用自定义排序
private void ApplyCustomSorting()
{// 获取集合视图ICollectionView view = CollectionViewSource.GetDefaultView(_people);// 创建自定义排序描述view.SortDescriptions.Clear();// 使用自定义比较器进行排序ListCollectionView listView = view as ListCollectionView;if (listView != null){listView.CustomSort = new PersonNameComparer();}
}
过滤功能
CollectionView的过滤功能允许只显示符合特定条件的项目:
// 应用过滤
private void ApplyFilter(ICollectionView view, string genderFilter)
{// 设置过滤器view.Filter = item =>{// 转换为Person对象Person person = item as Person;if (person == null)return false;// 如果过滤条件为空,显示所有项if (string.IsNullOrEmpty(genderFilter))return true;// 按性别过滤return person.Gender == genderFilter;};
}// 动态更新过滤器
private void FilterComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{ComboBox comboBox = sender as ComboBox;string filterValue = comboBox.SelectedItem as string;// 获取集合视图ICollectionView view = CollectionViewSource.GetDefaultView(_people);// 应用过滤器ApplyFilter(view, filterValue);
}// 清除过滤器
private void ClearFilterButton_Click(object sender, RoutedEventArgs e)
{// 获取集合视图ICollectionView view = CollectionViewSource.GetDefaultView(_people);// 清除过滤器view.Filter = null;
}
分组功能
CollectionView的分组功能允许将集合中的项目按特定规则分组显示:
// 应用分组
private void ApplyGrouping(ICollectionView view)
{// 清除现有分组view.GroupDescriptions.Clear();// 按性别分组view.GroupDescriptions.Add(new PropertyGroupDescription("Gender"));
}
在XAML中使用分组:
<Window x:Class="CollectionViewDemo.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="分组示例" Height="450" Width="800"><Window.Resources><CollectionViewSource x:Key="PeopleViewSource" Source="{Binding People}"><CollectionViewSource.GroupDescriptions><!-- 按性别分组 --><PropertyGroupDescription PropertyName="Gender" /></CollectionViewSource.GroupDescriptions></CollectionViewSource><!-- 分组项目模板 --><DataTemplate x:Key="GroupHeaderTemplate"><TextBlock Text="{Binding Name}" FontWeight="Bold" FontSize="14" Margin="0,10,0,5" /></DataTemplate></Window.Resources><Grid><ListView ItemsSource="{Binding Source={StaticResource PeopleViewSource}}" Margin="10"><!-- 启用分组 --><ListView.GroupStyle><GroupStyle HeaderTemplate="{StaticResource GroupHeaderTemplate}" /></ListView.GroupStyle><ListView.View><GridView><GridViewColumn Header="姓名" DisplayMemberBinding="{Binding Name}" Width="100" /><GridViewColumn Header="年龄" DisplayMemberBinding="{Binding Age}" Width="50" /><GridViewColumn Header="性别" DisplayMemberBinding="{Binding Gender}" Width="50" /></GridView></ListView.View></ListView></Grid>
</Window>
自定义分组
对于复杂的分组规则,可以实现自定义GroupDescription:
// 自定义分组描述 - 按年龄段分组
public class AgeRangeGroupDescription : GroupDescription
{// 重写分组方法public override object GroupNameFromItem(object item, int level, CultureInfo culture){// 转换为Person对象Person person = item as Person;if (person == null)return null;// 根据年龄范围分组if (person.Age < 20)return "20岁以下";else if (person.Age < 30)return "20-29岁";else if (person.Age < 40)return "30-39岁";elsereturn "40岁及以上";}
}// 应用自定义分组
private void ApplyCustomGrouping(ICollectionView view)
{// 清除现有分组view.GroupDescriptions.Clear();// 添加自定义分组view.GroupDescriptions.Add(new AgeRangeGroupDescription());
}
当前项管理
CollectionView提供了强大的当前项(CurrentItem)管理功能,对于实现主从视图(Master-Detail)界面特别有用。
当前项基本操作
// 当前项管理示例代码
public partial class CurrentItemDemo : Window
{private ObservableCollection<Person> _people;private ICollectionView _peopleView;public CurrentItemDemo(){InitializeComponent();// 初始化数据_people = new ObservableCollection<Person>{new Person { Name = "张三", Age = 28, Gender = "男" },new Person { Name = "李四", Age = 32, Gender = "男" },new Person { Name = "王五", Age = 25, Gender = "男" }};// 获取集合视图_peopleView = CollectionViewSource.GetDefaultView(_people);// 设置当前项变更事件处理_peopleView.CurrentChanged += PeopleView_CurrentChanged;// 将ListView绑定到集合视图peopleListView.ItemsSource = _peopleView;// 设置DataContextDataContext = this;}// 当前项变更事件处理private void PeopleView_CurrentChanged(object sender, EventArgs e){// 获取当前项Person currentPerson = _peopleView.CurrentItem as Person;// 更新详情视图if (currentPerson != null){detailsPanel.DataContext = currentPerson;}}// 导航按钮处理private void FirstButton_Click(object sender, RoutedEventArgs e){_peopleView.MoveCurrentToFirst();}private void PreviousButton_Click(object sender, RoutedEventArgs e){_peopleView.MoveCurrentToPrevious();}private void NextButton_Click(object sender, RoutedEventArgs e){_peopleView.MoveCurrentToNext();}private void LastButton_Click(object sender, RoutedEventArgs e){_peopleView.MoveCurrentToLast();}
}
对应的XAML代码:
<Window x:Class="CollectionBindingDemo.CurrentItemDemo"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="主从视图示例" Height="450" Width="800"><Grid><Grid.ColumnDefinitions><ColumnDefinition Width="*" /><ColumnDefinition Width="*" /></Grid.ColumnDefinitions><Grid.RowDefinitions><RowDefinition Height="*" /><RowDefinition Height="Auto" /></Grid.RowDefinitions><!-- 主视图:列表 --><ListView x:Name="peopleListView" Grid.Column="0" Margin="10"IsSynchronizedWithCurrentItem="True"><ListView.View><GridView><GridViewColumn Header="姓名" DisplayMemberBinding="{Binding Name}" Width="100" /><GridViewColumn Header="年龄" DisplayMemberBinding="{Binding Age}" Width="50" /></GridView></ListView.View></ListView><!-- 从视图:详情面板 --><StackPanel x:Name="detailsPanel" Grid.Column="1" Margin="20"><TextBlock Text="详细信息" FontWeight="Bold" FontSize="16" Margin="0,0,0,10" /><Grid><Grid.ColumnDefinitions><ColumnDefinition Width="Auto" /><ColumnDefinition Width="*" /></Grid.ColumnDefinitions><Grid.RowDefinitions><RowDefinition Height="Auto" /><RowDefinition Height="Auto" /><RowDefinition Height="Auto" /></Grid.RowDefinitions><TextBlock Text="姓名:" Grid.Row="0" Grid.Column="0" Margin="0,5" /><TextBlock Text="{Binding Name}" Grid.Row="0" Grid.Column="1" Margin="0,5" /><TextBlock Text="年龄:" Grid.Row="1" Grid.Column="0" Margin="0,5" /><TextBlock Text="{Binding Age}" Grid.Row="1" Grid.Column="1" Margin="0,5" /><TextBlock Text="性别:" Grid.Row="2" Grid.Column="0" Margin="0,5" /><TextBlock Text="{Binding Gender}" Grid.Row="2" Grid.Column="1" Margin="0,5" /></Grid></StackPanel><!-- 导航按钮 --><StackPanel Grid.Row="1" Grid.ColumnSpan="2" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,10"><Button Content="第一条" Click="FirstButton_Click" Width="80" Margin="5" /><Button Content="上一条" Click="PreviousButton_Click" Width="80" Margin="5" /><Button Content="下一条" Click="NextButton_Click" Width="80" Margin="5" /><Button Content="最后一条" Click="LastButton_Click" Width="80" Margin="5" /></StackPanel></Grid>
</Window>
IsSynchronizedWithCurrentItem属性
在XAML中,IsSynchronizedWithCurrentItem="True"
属性是实现列表控件与当前项同步的关键:
<ListBox ItemsSource="{Binding Source={StaticResource PeopleViewSource}}"IsSynchronizedWithCurrentItem="True" />
当此属性设置为True时,ItemsControl会自动跟踪和显示CollectionView的当前项,并在UI上高亮显示。
CurrentItem与SelectedItem的区别
CollectionView的CurrentItem与ItemsControl的SelectedItem是两个不同但相关的概念:
- SelectedItem:是控件自身的属性,仅影响该控件本身
- CurrentItem:是CollectionView的属性,影响所有绑定到该视图的控件
当IsSynchronizedWithCurrentItem="True"
时,这两个属性会保持同步。
实际应用案例
使用集合绑定实现可编辑数据表格
下面是一个使用ObservableCollection和DataGrid实现可编辑数据表格的示例:
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Windows;
using System.Windows.Data;namespace DataGridDemo
{public partial class EditableDataGridWindow : Window{// ViewModel类public class ProductsViewModel : INotifyPropertyChanged{private ObservableCollection<Product> _products;private ICollectionView _productsView;public ProductsViewModel(){// 初始化产品集合Products = new ObservableCollection<Product>{new Product { ID = 1, Name = "笔记本电脑", Category = "电子产品", Price = 5999.00m, InStock = true },new Product { ID = 2, Name = "无线鼠标", Category = "电子产品", Price = 129.00m, InStock = true },new Product { ID = 3, Name = "办公椅", Category = "办公家具", Price = 899.00m, InStock = false },new Product { ID = 4, Name = "显示器", Category = "电子产品", Price = 1299.00m, InStock = true },new Product { ID = 5, Name = "文件柜", Category = "办公家具", Price = 499.00m, InStock = true }};// 获取和配置集合视图ProductsView = CollectionViewSource.GetDefaultView(Products);// 设置默认排序ProductsView.SortDescriptions.Add(new SortDescription("Category", ListSortDirection.Ascending));ProductsView.SortDescriptions.Add(new SortDescription("Name", ListSortDirection.Ascending));// 设置默认分组ProductsView.GroupDescriptions.Add(new PropertyGroupDescription("Category"));}// 产品集合属性public ObservableCollection<Product> Products{get => _products;set{_products = value;OnPropertyChanged(nameof(Products));}}// 产品视图属性public ICollectionView ProductsView{get => _productsView;set{_productsView = value;OnPropertyChanged(nameof(ProductsView));}}// 添加新产品public void AddProduct(Product product){// 设置新ID(简单实现)product.ID = Products.Count > 0 ? Products.Max(p => p.ID) + 1 : 1;// 添加到集合Products.Add(product);// 使新添加的产品成为当前项ProductsView.MoveCurrentTo(product);}// 删除产品public void RemoveProduct(Product product){if (product != null && Products.Contains(product)){Products.Remove(product);}}// INotifyPropertyChanged实现public event PropertyChangedEventHandler PropertyChanged;protected virtual void OnPropertyChanged(string propertyName){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}}// 产品数据模型public class Product : INotifyPropertyChanged{private int _id;private string _name;private string _category;private decimal _price;private bool _inStock;public int ID{get => _id;set{if (_id != value){_id = value;OnPropertyChanged(nameof(ID));}}}public string Name{get => _name;set{if (_name != value){_name = value;OnPropertyChanged(nameof(Name));}}}public string Category{get => _category;set{if (_category != value){_category = value;OnPropertyChanged(nameof(Category));}}}public decimal Price{get => _price;set{if (_price != value){_price = value;OnPropertyChanged(nameof(Price));}}}public bool InStock{get => _inStock;set{if (_inStock != value){_inStock = value;OnPropertyChanged(nameof(InStock));}}}// INotifyPropertyChanged实现public event PropertyChangedEventHandler PropertyChanged;protected virtual void OnPropertyChanged(string propertyName){PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));}}// ViewModel实例private ProductsViewModel _viewModel;public EditableDataGridWindow(){InitializeComponent();// 初始化ViewModel并设置为DataContext_viewModel = new ProductsViewModel();DataContext = _viewModel;// 绑定数据源productsDataGrid.ItemsSource = _viewModel.ProductsView;}// 添加新产品按钮事件处理private void AddButton_Click(object sender, RoutedEventArgs e){// 创建新产品(默认值)var newProduct = new Product{Name = "新产品",Category = "未分类",Price = 0.00m,InStock = true};// 添加到集合_viewModel.AddProduct(newProduct);}// 删除产品按钮事件处理private void DeleteButton_Click(object sender, RoutedEventArgs e){// 获取当前选中产品var selectedProduct = productsDataGrid.SelectedItem as Product;if (selectedProduct != null){// 从集合中删除_viewModel.RemoveProduct(selectedProduct);}}}
}
对应的XAML代码:
<Window x:Class="DataGridDemo.EditableDataGridWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"Title="可编辑数据表格" Height="600" Width="800"><Grid><Grid.RowDefinitions><RowDefinition Height="*" /><RowDefinition Height="Auto" /></Grid.RowDefinitions><!-- 数据表格 --><DataGrid x:Name="productsDataGrid" AutoGenerateColumns="False" CanUserAddRows="False" CanUserDeleteRows="False"CanUserReorderColumns="True" CanUserResizeColumns="True"CanUserSortColumns="True" AlternatingRowBackground="AliceBlue"GridLinesVisibility="Horizontal" Margin="10"><!-- 启用分组 --><DataGrid.GroupStyle><GroupStyle><GroupStyle.HeaderTemplate><DataTemplate><StackPanel Orientation="Horizontal"><TextBlock Text="{Binding Name}" FontWeight="Bold" FontSize="14" Foreground="DarkBlue" /><TextBlock Text=" [" Foreground="DarkBlue" /><TextBlock Text="{Binding ItemCount}" Foreground="DarkBlue" /><TextBlock Text=" 个产品]" Foreground="DarkBlue" /></StackPanel></DataTemplate></GroupStyle.HeaderTemplate></GroupStyle></DataGrid.GroupStyle><!-- 列定义 --><DataGrid.Columns><DataGridTextColumn Header="ID" Binding="{Binding ID}" IsReadOnly="True" Width="50" /><DataGridTextColumn Header="产品名称" Binding="{Binding Name}" Width="150" /><DataGridTextColumn Header="类别" Binding="{Binding Category}" Width="100" /><DataGridTextColumn Header="价格" Binding="{Binding Price, StringFormat={}{0:C}}" Width="100" /><DataGridCheckBoxColumn Header="有库存" Binding="{Binding InStock}" Width="70" /></DataGrid.Columns></DataGrid><!-- 按钮面板 --><StackPanel Grid.Row="1" Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,10"><Button Content="添加产品" Click="AddButton_Click" Width="100" Margin="5" /><Button Content="删除选中" Click="DeleteButton_Click" Width="100" Margin="5" /></StackPanel></Grid>
</Window>
总结与最佳实践
集合绑定的最佳实践
-
选择正确的集合类型
- 使用ObservableCollection作为默认选择
- 对于特殊需求(批量操作、线程安全等),使用增强版实现
-
视图层与数据层分离
- 不要在视图代码中直接修改集合
- 使用MVVM模式分离关注点
-
性能优化
- 对于大集合使用UI虚拟化
- 避免频繁更新UI
- 使用批量操作减少通知事件
-
调试技巧
- 使用PresentationTraceSources.TraceLevel附加属性调试绑定问题
- 检查Output窗口的绑定错误
常见问题解决
-
集合变化但UI不更新
- 确保使用ObservableCollection
- 检查是否在正确的线程修改集合
- 确认绑定路径正确
-
绑定性能问题
- 启用虚拟化:VirtualizingStackPanel.IsVirtualizing=“True”
- 禁用缓存回收:VirtualizingStackPanel.VirtualizationMode=“Recycling”
- 考虑使用延迟加载
-
线程访问问题
- 使用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深入浅出》-- 刘铁锰
相关文章:
WPF之集合绑定深入
文章目录 引言ObservableCollection<T>基础什么是ObservableCollectionObservableCollection的工作原理基本用法示例ObservableCollection与MVVM模式ObservableCollection的局限性 INotifyCollectionChanged接口深入接口定义与作用NotifyCollectionChangedEventArgs详解自…...
第五天 车载系统安全(入侵检测、OTA安全) 数据加密(TLS/SSL、国密算法)
前言 随着汽车智能化程度不断提升,车载系统安全已成为行业关注焦点。本文将从零开始,带大家系统学习车载系统安全的核心技术,重点解析入侵检测、OTA安全、数据加密三大领域。即使没有安全背景,也能通过本文建立起完整的汽车网络安…...
采用SqlSugarClient创建数据库实例引发的异步调用问题
基于SqlSugar编写的多个WebApi接口,项目初始化时采用单例模式注册SqlSugarClient实例对象,前端页面采用layui布局,并在一个按钮事件中通过Ajax连续调用多个WebApi接口获取数据。实际运行时点击按钮会随机报下面几种错误: Execute…...
unity通过transform找子物体只能找子级
unity通过transform找子物体只能找子级,孙级以及更低级别都找不到,只能找到自己的下一级 如果要获取孙级以下的物体,最快的方法还是直接public挂载...
Dockers部署oscarfonts/geoserver镜像的Geoserver
Dockers部署oscarfonts/geoserver镜像的Geoserver 说实话,最后发现要选择合适的Geoserver镜像才是关键,所以所以所以…🐷 推荐oscarfonts/geoserver的镜像! 一开始用kartoza/geoserver镜像一直提示内存不足,不过还好…...
AtCoder AT_abc405_d ABC405D - Escape Route
前言 BFS 算法在 AtCoder 比赛中还是会考的,因为不常练习导致没想到,不仅错误 TLE 了很多,还影响了心态,3 发罚时后才 AC。 思路 首先,我们把所有位置和出口的距离算出来(用 BFS),…...
Redis-x64-3.0.500
E:\Workspace_zwf\Redis-x64-3.0.500 redis.windows.conf...
CUDA编程——性能优化基本技巧
本文主要介绍下面三种技巧: 使用 __restrict__ 让编译器放心地优化指针访存想办法让同一个 Warp 中的线程的访存 Pattern 尽可能连续,以利用 Memory coalescing使用 Shared memory 0. 弄清Kernael函数是Compute-bound 还是 Memory-bound 先摆出一个知…...
图像卷积初识
目录 一、卷积的概念 1、常见卷积核示例 二、使用 OpenCV 实现卷积操作 1、代码说明 2、运行说明 一、卷积的概念 在图像处理中,卷积是一种通过滑动窗口(卷积核)对图像进行局部计算的操作。卷积核是一个小的矩阵,它在图像上…...
K8S服务的请求访问转发原理
开启 K8s 服务异常排障过程前,须对 K8s 服务的访问路径有一个全面的了解,下面我们先介绍目前常用的 K8s 服务访问方式(不同云原生平台实现方式可能基于部署方案、性能优化等情况会存在一些差异,但是如要运维 K8s 服务,…...
VSCode-插件:codegeex:ai coding assistant / 清华智普 AI 插件
一、官网 https://codegeex.cn/ 二、vscode 安装插件 点击安装即可,无需复杂操作,国内软件,无需科学上网,非常友好 三、智能注释 输入 // 或者 空格---后边自动出现注释信息,,按下 Tab 键,进…...
Kubernetes生产实战(十四):Secret高级使用模式与安全实践指南
一、Secret核心类型解析 类型使用场景自动管理机制典型字段Opaque (默认)自定义敏感数据需手动创建data字段存储键值对kubernetes.io/dockerconfigjson私有镜像仓库认证kubelet自动更新.dockerconfigjsonkubernetes.io/tlsTLS证书管理Cert-Manager可自动化tls.crt/tls.keykube…...
【验证码】⭐️集成图形验证码实现安全校验
💥💥✈️✈️欢迎阅读本文章❤️❤️💥💥 🏆本篇文章阅读大约耗时5分钟。 ⛳️motto:不积跬步、无以千里 📋📋📋本文目录如下:🎁🎁&am…...
iOS瀑布流布局的实现(swift)
在iOS开发中,瀑布流布局(Waterfall Flow)是一种常见的多列不等高布局方式,适用于图片、商品展示等场景。以下是基于UICollectionView实现瀑布流布局的核心步骤和优化方法: 一、实现原理 瀑布流的核心在于动态计算每个…...
TGRS | FSVLM: 用于遥感农田分割的视觉语言模型
论文介绍 题目:FSVLM: A Vision-Language Model for Remote Sensing Farmland Segmentation 期刊:IEEE Transactions on Geoscience and Remote Sensing 论文:https://ieeexplore.ieee.org/document/10851315 年份:2025 单位…...
#Redis黑马点评#(四)优惠券秒杀
目录 一 生成全局id 二 添加优惠券 三 实现秒杀下单 方案一(会出现超卖问题) 方案二(解决了超卖但是错误率较高) 方案三(解决了错误率较高和超卖但是会出现一人抢多张问题) 方案四(解决一人抢多张问题“非分布式…...
https,http1,http2,http3的一些知识
温故知新,突然有人问我项目中🤔有使用http3么,一下不知从何说起,就有了这篇文章的出现。 https加密传输,ssltls https 验证身份 提供加密,混合加密 : 对称加密 非对称加密 原理:…...
《设计数据密集型应用》——阅读小记
设计数据密集型应用 这本书非常推荐看英语版,如果考过了CET-6就可以很轻松的阅读这本书。 当前计算机软件已经不是单体的时代了,分布式系统,微服务现在是服务端开发的主流,如果没有读过这本书,则强力建议读这本书。 …...
SpringCloud之Gateway基础认识-服务网关
0、Gateway基本知识 Gateway 是在 Spring 生态系统之上构建的 API 网关服务,基于 Spring ,Spring Boot 和 Project Reactor 等技术。 Gateway 旨在提供一种简单而有效的方式来对 API 进行路由,以及提供一些强大的过滤器功能,例如…...
MySQL 从入门到精通(三):日志管理详解 —— 从排错到恢复的核心利器
在 MySQL 数据库的日常运维中,日志是定位问题、优化性能、数据恢复的核心工具。无论是排查服务器启动异常,还是分析慢查询瓶颈,亦或是通过二进制日志恢复误删数据,日志都扮演着 “数据库黑匣子” 的角色。本文将深入解析 MySQL 的…...
单脉冲前视成像多目标分辨算法——论文阅读
单脉冲前视成像多目标分辨算法 1. 论文的研究目标及实际意义1.1 研究目标1.2 实际问题与产业意义2. 论文的创新方法及公式解析2.1 核心思路2.2 关键公式与模型2.2.1 单脉冲雷达信号模型2.2.2 匹配滤波输出模型2.2.3 多目标联合观测模型2.2.4 对数似然函数与优化2.2.5 MDL准则目…...
SpringBoot项目容器化进行部署,meven的docker插件远程构建docker镜像
需求:将Spring Boot项目使用容器化进行部署 前提 默认其他环境,如mysql,redis等已经通过docker部署完毕, 这里只讨论,如何制作springboot项目的镜像 要将Spring Boot项目使用docker容器进行部署,就需要将Spring Boot项目构建成一个docker镜像 一、手动…...
【金仓数据库征文】政府项目数据库迁移:从MySQL 5.7到KingbaseES的蜕变之路
摘要:本文详细阐述了政府项目中将 MySQL 5.7 数据库迁移至 KingbaseES 的全过程,涵盖迁移前的环境评估、数据梳理和工具准备,迁移实战中的数据源与目标库连接配置、迁移任务详细设定、执行迁移与过程监控,以及迁移后的质量验证、系…...
C++GO语言微服务和服务发现②
01 创建go-micro项目-查看生成的 proto文件 02 创建go-micro项目-查看生成的main文件和handler ## 创建 micro 服务 命令:micro new --type srv test66 框架默认自带服务发现:mdns。 使用consul服务发现: 1. 初始consul服务发现&…...
手机银行怎么打印流水账单(已解决)
一、中国银行 登录中国银行手机银行APP。 在首页点击“更多”,向左滑动找到并点击“助手”。 在助手页面选择“交易流水打印”。 点击“立即申请”,选择需要打印的账户和时间段。 输入接收流水账单的电子邮箱地址。 提交申请后,在“申请…...
单片机-STM32部分:10-2、逻辑分析仪
飞书文档https://x509p6c8to.feishu.cn/wiki/VrdkwVzOnifH8xktu3Bcuc4Enie 安装包如下:根据自己的系统选择,目前这个工具只有window版本哦 安装方法比较简单,都按默认下一步即可,注意不要安装到中文路径哦。 其余部分参考飞书文档…...
Scala与Go的异同教程
当瑞士军刀遇到电锯:Scala vs Go的相爱相杀之旅 各位准备秃头的程序猿们(放心,用Go和Scala不会加重你的发际线问题),今天我们来聊聊编程界的"冰与火之歌"——Scala和Go的异同。准备好瓜子饮料,我…...
【算法-哈希表】常见算法题的哈希表套路拆解
算法相关知识点可以通过点击以下链接进行学习一起加油!双指针滑动窗口二分查找前缀和位运算模拟链表 在刷题的过程中,我们会频繁遇到一些“高频套路”——而哈希表正是其中最常用也最高效的工具之一。它能帮助我们在 O(1) 的时间复杂度内完成查找、插入与…...
前端取经路——现代API探索:沙僧的通灵法术
大家好,我是老十三,一名前端开发工程师。在现代Web开发中,各种强大的API就像沙僧的通灵法术,让我们的应用具备了超乎想象的能力。本文将带你探索从离线应用到实时通信,从多线程处理到3D渲染的九大现代Web API,让你的应用获得"通灵"般的超能力。 在前端取经的第…...
深入了解 ArkTS:HarmonyOS 开发的关键语言与应用实践
随着 HarmonyOS(鸿蒙操作系统)的推出,华为为开发者提供了一套全新的开发工具和编程语言,使得跨设备、跨平台的应用开发成为可能。在这些工具中,ArkTS(Ark TypeScript)作为一种专为 HarmonyOS 设…...
Flask 调试的时候进入main函数两次
在 Flask 开启 Debug 模式时,程序会因为自动重载(reloader)的机制而启动两个进程,导致if __name__ __main__底层的程序代码被执行两次。以下说明其原理与常见解法。 Flask Debug 模式下自动重载机制 Flask 使用的底层服务器 Wer…...
Git 时光机:修改Commit信息
前言 列位看官都知道,Git 的每一次 git commit,其中会包含作者(Author)和提交者(Committer)的姓名与邮箱。有时可能会因为配置错误、切换了开发环境,或者只是单纯的手滑,导致 commi…...
DAY 21 常见的降维算法
知识点回顾: LDA线性判别PCA主成分分析t-sne降维 还有一些其他的降维方式,也就是最重要的词向量的加工,我们未来再说 作业: 自由作业:探索下什么时候用到降维?降维的主要应用?或者让ai给你出题&…...
Docker使用小结
概念 镜像( Image ) :相当于一个 root 文件系统;镜像构建时,分层存储、层层构建;容器( Container ) :镜像是静态的定义,容器是镜像运行时的实体;…...
kubectl top 查询pod连接数
在 Kubernetes 中,kubectl top 命令默认仅支持查看 Pod 或节点的 CPU/内存资源使用情况,并不直接提供 TCP 连接数的统计功能。若要获取 Pod 的 TCP 连接数,需结合其他工具和方法。以下是具体实现方案: 1. 直接进入容器查看 TCP 连…...
Kubernetes生产实战(十七):负载均衡流量分发管理实战指南
在Kubernetes集群中,负载均衡是保障应用高可用、高性能的核心机制。本文将从生产环境视角,深入解析Kubernetes负载均衡的实现方式、最佳实践及常见问题解决方案。 一、Kubernetes负载均衡的三大核心组件 1)Service资源:集群内流…...
Git 分支指南
什么是 Git 分支? Git 分支是仓库内的独立开发线,你可以把它想象成一个单独的工作空间,在这里你可以进行修改,而不会影响主分支(或 默认分支)。分支允许开发者在不影响项目实际版本的情况下,开…...
自动泊车技术—相机模型
一、相机分类及特性 传感器类型深度感知原理有效工作范围环境适应性功耗水平典型成本区间数据丰富度单目相机运动视差/几何先验1m~∞光照敏感1-2W5−5−502D纹理中双目相机立体匹配 (SGM/SGBM算法)0.3m~20m纹理依赖3-5W50−50−3002D稀疏深度多摄像头系统多视角三角测量0.1m~5…...
程序代码篇---esp32视频流处理
文章目录 前言一、ESP32摄像头设置1.HTTP视频流(最常见)2.RTSP视频流3.MJPEG流 二、使用OpenCV读取视频流1. 读取HTTP视频流2. 读取RTSP视频流 三、使用requests库读取MJPEG流四、处理常见问题1. 连接不稳定或断流2. 提高视频流性能2.1降低分辨率2.2跳过…...
数据结构与算法分析实验12 实现二叉查找树
实现二叉查找树 1、二叉查找树介绍2.上机要求3.上机环境4.程序清单(写明运行结果及结果分析)4.1 程序清单4.1.1 头文件 TreeMap.h 内容如下:4.1.2 实现文件 TreeMap.cpp 文件内容如下:4.1.3 源文件 main.cpp 文件内容如下: 4.2 实现展效果示5…...
深入浅出之STL源码分析2_类模版
1.引言 我在上面的文章中讲解了vector的基本操作,然后提出了几个问题。 STL之vector基本操作-CSDN博客 1.刚才我提到了我的编译器版本是g 11.4.0,而我们要讲解的是STL(标准模板库),那么二者之间的关系是什么&#x…...
Docker、Docker-compose、K8s、Docker swarm之间的区别
1.Docker docker是一个运行于主流linux/windows系统上的应用容器引擎,通过docker中的镜像(image)可以在docker中构建一个独立的容器(container)来运行镜像对应的服务; 例如可以通过mysql镜像构建一个运行mysql的容器,既可以直接进入该容器命…...
【Linux】线程的同步与互斥
目录 1. 整体学习思维导图 2. 线程的互斥 2.1 互斥的概念 2.2 见一见数据不一致的情况 2.3 引入锁Mutex(互斥锁/互斥量) 2.3.1 接口认识 2.3.2 Mutex锁的理解 2.3.3 互斥量的封装 3. 线程同步 3.1 条件变量概念 3.2 引入条件变量Cond 3.2.1 接口认识 3.2.2 同步的…...
C++发起Https连接请求
需要下载安装openssl //stdafx.h #pragma once #include<iostream> #include <openssl/ssl.h> #include <openssl/err.h> #include <iostream> #include <string>#pragma comment(lib, "libssl.lib") #pragma comment(lib, "lib…...
Linux 内核链表宏的详细解释
🔧 Linux 内核链表结构概览 Linux 内核中的链表结构定义在头文件 <linux/list.h> 中。核心结构是: struct list_head {struct list_head *next, *prev; }; 它表示一个双向循环链表的节点。链表的所有操作都围绕这个结构体展开。 🧩 …...
[架构之美]Spring Boot集成MyBatis-Plus高效开发(十七)
[架构之美]Spring Boot集成MyBatis-Plus高效开发(十七) 摘要:本文通过图文代码实战,详细讲解Spring Boot整合MyBatis-Plus全流程,涵盖代码生成器、条件构造器、分页插件等核心功能,助你减少90%的SQL编写量…...
游戏引擎学习第270天:生成可行走的点
回顾并为今天的内容定下基调 今天的计划虽然还不完全确定,可能会做一些内存分析,也有可能暂时不做,因为目前并没有特别迫切的需求。最终我们会根据当下的状态随性决定,重点是持续推动项目的进展,无论是 memory 方面还…...
批量统计PDF页数,统计图像属性
软件介绍: 1、支持批量统计PDF、doc\docx、xls\xlsx页数 2、支持统计指定格式文件数量(不填格式就是全部) 3、支持统计JPG、JPEG、PNG图像属性 4、支持统计多页TIF页数、属性 5、支持统计PDF、JPG画幅 统计图像属性 「托马斯的文件助手」…...
QT Creator配置Kit
0、背景:qt5.12.12vs2022 记得先增加vs2017编译器 一、症状: 你是否有以下症状? 1、用qt新建的工程,用qmake,可惜能看见的只有一个pro文件? 2、安装QT Creator后,使用MSVC编译显示no c com…...
[架构之美]IntelliJ IDEA创建Maven项目全流程(十四)
[架构之美]IntelliJ IDEA创建Maven项目全流程(十四) 摘要:本文将通过图文结合的方式,详细讲解如何使用IntelliJ IDEA快速创建Maven项目,涵盖环境配置、项目初始化、依赖管理及常见问题解决方案。适用于Java开发新手及…...