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详解自…...
LeetCode 每日一题 3341. 到达最后一个房间的最少时间 I + II
3341. 到达最后一个房间的最少时间 I II 有一个地窖,地窖中有 n x m 个房间,它们呈网格状排布。 给你一个大小为 n x m 的二维数组 moveTime ,其中 moveTime[i][j] 表示在这个时刻 以后 你才可以 开始 往这个房间 移动 。你在时刻 t 0 时从…...

(C语言)超市管理系统(测试2版)(指针)(数据结构)(清屏操作)
目录 前言: 源代码: product.h product.c fileio.h fileio.c main.c 代码解析: 一、程序结构概述 二、product.c 函数详解 1. 初始化商品列表 Init_products 2. 添加商品 add_product 3. 显示商品 display_products 4. 修改商品 mo…...
什么是虚拟同步发电机
虚拟同步发电机(Virtual Synchronous Generator, VSG) 是一种基于电力电子技术的先进控制策略,通过模拟传统同步发电机的机电特性和动态行为,使逆变器或储能系统能够像传统发电机一样为电网提供惯性支撑、频率调节和电压稳定性支持…...
Python字符串全面指南:从基础到高级
文章目录 Python字符串全面指南:从基础到高级1. 字符串基础概念2. 字符串的基本操作2.1 字符串拼接2.2 字符串索引和切片 3. 字符串常用方法3.1 大小写转换3.2 字符串查找和替换3.3 字符串分割和连接3.4 字符串格式化3.5 字符串验证 4. 字符串的不可变性5. 字符串编…...
基于大模型的TIA诊疗全流程智能决策系统技术方案
目录 一、多模态数据融合与预处理系统1.1 数据接入模块1.2 数据预处理伪代码二、TIA智能预测模型系统2.1 模型训练流程2.2 混合模型架构伪代码三、术中智能监测系统3.1 实时监测流程3.2 实时预测伪代码四、智能诊疗决策系统4.1 手术方案推荐流程4.2 麻醉方案生成伪代码五、预后…...

编译openssl源码
openssl版本 1.1.1c windows 安装环境 perl 先安装perl,生成makefile需要 https://strawberryperl.com/releases.html nasm nasm 也是生成makefile需要 https://www.nasm.us/ 安装完perl输入一下nasm,看看能不能找到,找不到的话需要配…...
CMake入门与实践:现代C++项目的构建利器
文章目录 CMake入门与实践:现代C项目的构建利器引言什么是CMake?快速入门:从Hello World开始1. 安装CMake2. 最小项目示例3. 构建项目 核心概念详解1. 项目结构组织2. 常用指令3. 变量与条件控制 进阶技巧1. 多目录项目管理2. 集成第三方库3.…...

OpenCV实现数字水印的相关函数和示例代码
OpenCV计算机视觉开发实践:基于Qt C - 商品搜索 - 京东 实现数字水印的相关函数 用OpenCV来实现数字水印功能,需要使用一些位操作函数,我们需要先了解一下这些函数。 1. bitwise_and函数 bitwise_and函数是OpenCV中的位运算函数之一&…...
BMS工具箱用来执行贝叶斯模型平均(BMA)计算模块
贝叶斯模型平均(Bayesian Model Averaging,BMA)是一种用于处理模型不确定性的统计方法,通过结合多个模型的预测结果来提高预测的准确性和鲁棒性。在 MATLAB 中,可以使用专门的工具箱(如 BMS 工具箱…...

坐席业绩数据分析
豆包提示词: 使用papaparse.js,chart.js,tailwindcss和font-awesome,生成一个可以交互的简洁且可以运行的HTML代码,不要输出无关内容。 具体要求如下: 1、按坐席姓名输出业绩折线图。 2、系统导航区域&…...
国产大模型 “五强争霸”,决战 AGI
中国 AI 大模型市场正经历一场史无前例的洗牌!曾经 “百模混战” 的局面已落幕,字节、阿里、阶跃星辰、智谱和 DeepSeek 五大巨头强势崛起,形成 “基模五强” 新格局。这场竞争不仅是技术实力的较量,更是资源、人才与生态的全面博…...

怎样将MM模块常用报表设置为ALV默认格式(MB52、MB5B、ME2M、ME1M等)
【SAP系统研究】 对SAP系统中的报表,最方便的格式就是ALV了,可排序、可导出,非常友好。 但有些常见报表却不是默认ALV界面的,譬如MB52: 是不是有点别扭?但其实是可以后台配置进行调整的。 现将一些常用报表修改为默认ALV的方法进行总结,便于大家使用。 一、MB52、MB5…...
Spark 集群配置、启动与监控指南
Spark 集群的配置和启动需要几个关键步骤。以下是完整的操作流程,包含配置修改、集群启动、任务提交和常见错误排查方法。 1. 修改 Spark 配置文件 首先需要编辑 Spark 配置文件,设置集群参数: bash # 进入 Spark 配置目录 cd $SPARK_HOM…...
前端面试每日三题 - Day 34
这是我为准备前端/全栈开发工程师面试整理的第34天每日三题练习: ✅ 题目1:WebGPU图形编程实战指南 核心概念 // WebGPU初始化流程 const adapter await navigator.gpu.requestAdapter(); const device await adapter.requestDevice();// 渲染管线配…...
比亚迪固态电池突破:王传福的技术哲学与产业重构|创客匠人热点评述
合肥某车间凌晨两点依然灯火通明,工程师正在调试的银白色设备,即将颠覆整个电动车行业 —— 比亚迪全固态电池产线的曝光,标志着中国新能源汽车产业正式迈入 “技术定义市场” 的新纪元。 一、技术突破的底层逻辑 比亚迪全固态电池的核心竞…...

Arduino使用红外收发模块
目录 Arduino UNO连接红外发射模块: Arduino D1连接红外接收模块: 有一个Arduini UNO板子和一个Arduino D1板子,我想通过红外发射模块和红外接收模块让他们进行通信。 先看结果: Arduino UNO连接红外发射模块: 发射模…...
【强化学习】强化学习算法 - 马尔可夫决策过程
马尔可夫决策过程 (Markov Decision Process, MDP) 1. MDP 原理介绍 马尔可夫决策过程 (MDP) 是强化学习 (Reinforcement Learning, RL) 中用于对序贯决策 (Sequential Decision Making) 问题进行数学建模的标准框架。它描述了一个智能体 (Agent) 与环境 (Environment) 交互的…...

机器学习 Day16 聚类算法 ,数据降维
聚类算法 1.简介 1.1 聚类概念 无监督学习:聚类是一种无监督学习算法,不需要预先标记的训练数据 相似性分组:根据样本之间的相似性自动将样本归到不同类别 相似度度量:常用欧式距离作为相似度计算方法 1.2 聚类vs分类 聚类&…...
开源Heygem本地跑AI数字人视频教程
图文教程: 点击跳转 视频教程 资料包下载 点击下载:...

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

Yolov8的详解与实战-深度学习目标检测
Yolov8的详解与实战- 文章目录 摘要 模型详解 C2F模块 Loss head部分 模型实战 训练COCO数据集 下载数据集 COCO转yolo格式数据集(适用V4,V5,V6,V7,V8) 配置yolov8环境 训练 测试 训练自定义数据集 Labelme…...

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

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

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

万文c++继承
1、继承的概念与定义 1.1继承的概念 继承:是c代码复用的手段,允许在原有的基础上扩展,在此之前都是函数层次的复用,继承是类设计层次的复用。 下面有两个类Student和Teacher都有姓名/地址/电话/年龄等成员变量。都有identity身…...
HTTP GET报文解读
考虑当浏览器发送一个HTTP GET报文时,通过Wireshark 俘获到下列ASCII字符串: GET /cs453/index.html HTTP/1.1 Host: gaia.cs.umass.edu User-Agent: Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.7.2) Gecko/20040804 Netscape/7.2 (ax) Acc…...

Linux grep -r 查找依赖包是否存在依赖类 Class
方法一:通过 Linux ,grep -r ClassPath 命令 grep -f org.apache.kafka.connect.source.SourceRecord在 jar 包所在 lib 或者 lib/plugins 目录下执行,grep -r, flink-sql-connector-sqlserver-cdc-3.3.0.jar 中此 kafka Source…...

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

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