基于MVVM/PRISM模式,如何触发DataGrid的OnPropertyChanged事件?
问题:WPF DataGrid编辑后PropertyChanged未触发,无法自动保存到数据库
我有一个绑定到ViewModel的DataGrid,ItemsSource是ObservableCollection<StudentEntity>,该集合通过EF/DbContext关联SQL Server数据库。目前已通过Prism的DelegateCommand实现行的添加和删除,但编辑DataGrid内容后,行/单元格失去焦点时无法自动保存到数据库,因为PropertyChanged事件未触发。
核心问题分析
- StudentEntity属性未触发PropertyChanged:实体的属性是自动实现的get/set,没有调用封装好的
SetField方法,属性值变化时不会触发事件。 - ViewModel集合是字段而非属性:WPF数据绑定要求绑定目标是公共属性,
StudentsObservableCollection是字段,可能导致绑定异常或事件订阅失效。 - 构造函数集合初始化错误:ViewModel构造函数中直接调用
Clear()但集合未初始化,会引发空引用异常,后续CollectionChanged订阅也不会生效。 - DataGrid列绑定未显式设置更新时机:默认
UpdateSourceTrigger为LostFocus,但如果属性本身不触发事件,即使失去焦点也无法通知ViewModel。
解决方案及代码修改
1. 修复StudentEntity的属性实现,确保触发PropertyChanged
将自动属性改为私有字段+调用SetField的方式,保证属性值变化时触发PropertyChanged:
public class StudentEntity( string studentId, string firstName, string lastName, int cohortYear, string unitCode, string? email) : INotifyPropertyChanged { private Guid _id = Guid.NewGuid(); private string _studentId = studentId; private string _firstName = firstName; private string _lastName = lastName; private string? _email = email; private int _cohortYear = cohortYear; private string _unitCode = unitCode; private DateTime? _createdDt = DateTime.UtcNow; [MaxLength(50)] public Guid Id { get => _id; set => SetField(ref _id, value); } [MaxLength(10)] public string StudentId { get => _studentId; set => SetField(ref _studentId, value); } [MaxLength(50)] public string FirstName { get => _firstName; set => SetField(ref _firstName, value); } [MaxLength(50)] public string LastName { get => _lastName; set => SetField(ref _lastName, value); } [MaxLength(100)] public string? Email { get => _email; set => SetField(ref _email, value); } public int CohortYear { get => _cohortYear; set => SetField(ref _cohortYear, value); } [MaxLength(10)] public string UnitCode { get => _unitCode; set => SetField(ref _unitCode, value); } public DateTime? CreatedDt { get => _createdDt; set => SetField(ref _createdDt, value); } public event PropertyChangedEventHandler? PropertyChanged; protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null) { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); } protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null) { if (EqualityComparer<T>.Default.Equals(field, value)) return false; field = value; OnPropertyChanged(propertyName); return true; } }
2. 修复ViewModel的集合定义与初始化
将StudentsObservableCollection改为公共只读属性,正确初始化并订阅事件:
public class StudentsViewModel : AbstractViewModel // AbstractViewModel 继承自 Prism.BindableBase { public ObservableCollection<StudentEntity> StudentsObservableCollection { get; } = new(); public DelegateCommand<StudentEntity> DeleteStudentCommand { get; } public StudentsViewModel() { StudentsObservableCollection.CollectionChanged += StudentsObservableCollection_CollectionChanged; DeleteStudentCommand = new DelegateCommand<StudentEntity>(DeleteStudentDelegate); } private void StudentsObservableCollection_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) { if (e.NewItems != null) { foreach (INotifyPropertyChanged added in e.NewItems) { added.PropertyChanged += Student_PropertyChanged; } } if (e.OldItems != null) { foreach (INotifyPropertyChanged removed in e.OldItems) { removed.PropertyChanged -= Student_PropertyChanged; } } } private void Student_PropertyChanged(object? sender, PropertyChangedEventArgs e) { if (sender is StudentEntity row) { SaveData(row); } } private async void SaveData(StudentEntity student) { // 此处实现数据库保存逻辑,示例:await _dbContext.SaveChangesAsync(); } private async void DeleteStudentDelegate(StudentEntity student) { // 已实现的删除逻辑 } }
3. 优化DataGrid列的绑定设置
显式设置UpdateSourceTrigger=LostFocus,确保单元格失去焦点时立即更新数据源,同时优化删除按钮的参数绑定:
<DataGrid CanUserResizeColumns="True" AutoGenerateColumns="False" Name="StudentsDataGrid" ItemsSource="{Binding StudentsObservableCollection, Mode=TwoWay}"> <DataGrid.Columns> <DataGridTextColumn Header="Student ID" Binding="{Binding StudentId, UpdateSourceTrigger=LostFocus}" /> <DataGridTextColumn Header="First Name" Binding="{Binding FirstName, UpdateSourceTrigger=LostFocus}" /> <DataGridTextColumn Header="Last Name" Binding="{Binding LastName, UpdateSourceTrigger=LostFocus}" /> <DataGridTextColumn Header="Year" Binding="{Binding CohortYear, UpdateSourceTrigger=LostFocus}" /> <DataGridTextColumn Header="Unit Code" Binding="{Binding UnitCode, UpdateSourceTrigger=LostFocus}" /> <DataGridTextColumn Header="Email" Binding="{Binding Email, UpdateSourceTrigger=LostFocus}" /> <DataGridTemplateColumn> <DataGridTemplateColumn.CellTemplate> <DataTemplate> <Button Cursor="Hand" Style="{StaticResource MaterialDesignFlatLightButton}" Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}, Path=DataContext.DeleteStudentCommand}" CommandParameter="{Binding}" ToolTip="Delete this student's record"> <materialDesign:PackIcon Kind="Delete" /> </Button> </DataTemplate> </DataGridTemplateColumn.CellTemplate> </DataGridTemplateColumn> </DataGrid.Columns> </DataGrid>
注:删除按钮的
CommandParameter直接绑定{Binding}即可获取当前行实体,避免选中行与操作行不一致的问题。
额外注意事项
- 确保EF的
DbContext实例正确跟踪StudentEntity对象,否则SaveChangesAsync不会生效。 - 频繁编辑时可添加防抖逻辑(如延迟500ms再保存),避免多次触发数据库操作。
内容的提问来源于stack exchange,提问作者pgee70
相关产品推荐
相关产品推荐

