You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

基于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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 22:04:57