我有一个绑定到视图模型的 DataGrid,它有一个
ItemSource
的 ObservableCollection
。 ObservableCollection
又通过 EF / DbContext
绑定到 SQL Server 数据库。
我可以添加和删除当前使用 Prism
DelegateCommand
(继承自 ICommand
)命令和委托按钮的行。
我想要做的是编辑 DataGrid,当行/单元格失去焦点时自动将值保存回数据库。
我认为这个算法走在正确的轨道上,但是当编辑数据网格内容时,
PropertyChanged
事件没有被触发。
public class StudentEntity(
string studentId,
string firstName,
string lastName,
int cohortYear,
string unitCode,
string? email) : INotifyPropertyChanged
{
[MaxLength(50)] public Guid Id { get; set; } = Guid.NewGuid();
[MaxLength(10)] public string StudentId { get; set; } = studentId;
[MaxLength(50)] public string FirstName { get; set; } = firstName;
[MaxLength(50)] public string LastName { get; set; } = lastName;
[MaxLength(100)] public string? Email { get; set; } = email;
public int CohortYear { get; set; } = cohortYear;
[MaxLength(10)] public string UnitCode { get; set; } = unitCode;
public DateTime? CreatedDt { get; set; }= DateTime.UtcNow;
public event PropertyChangedEventHandler? PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
{
// this code does not execute when datagrid values are changed
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected bool SetField<T>(ref T field, T value, [CallerMemberName] string? propertyName = null)
{
// this code does not execute when datagrid values are changed
if (EqualityComparer<T>.Default.Equals(field, value))
return false;
field = value;
OnPropertyChanged(propertyName);
return true;
}
}
摘自视图模型:
public class StudentsViewModel : AbstractViewModel // AbstractViewModel inherits from Prism :BindableBase
{
public ObservableCollection<StudentEntity> StudentsObservableCollection;
public DelegateCommand<StudentEntity> DeleteStudentCommand { get; }
public StudentsViewModel()
{
StudentsObservableCollection.Clear();
StudentsObservableCollection.CollectionChanged += StudentsObservableCollection_CollectionChanged;
DeleteStudentCommand = new DelegateCommand<StudentEntity>(DeleteStudentDelegate);
}
private void StudentsObservableCollection_CollectionChanged(object? sender, NotifyCollectionChangedEventArgs e)
{
if (e.NewItems != null)
{
var c = e.NewItems.Count;
foreach (INotifyPropertyChanged added in e.NewItems)
{
// this code is triggered when new students added to the Students collection
added.PropertyChanged += Student_PropertyChanged;
}
}
if (e.OldItems == null)
return;
foreach (INotifyPropertyChanged removed in e.OldItems)
{
removed.PropertyChanged -= Student_PropertyChanged;
}
}
private void Student_PropertyChanged(object? sender, PropertyChangedEventArgs e)
{
// this code does not execute on datagrid change
if (sender is not StudentEntity row)
{
return;
}
SaveData(row);
}
private async void SaveData(StudentEntity student)
{
// save data goes here.
}
private async void DeleteStudentDelegate(StudentEntity student)
{
// delete code goes here (works)
}
}
XAML:
<UserControl x:Class="ScriptGen.Views.Students.StudentDataGrid"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
xmlns:materialDesign="http://materialdesigninxaml.net/winfx/xaml/themes">
<d:UserControl.DataContext>
<d:DesignInstance Type="viewModels:StudentsViewModel" />
</d:UserControl.DataContext>
<DataGrid
CanUserResizeColumns="True"
AutoGenerateColumns="False"
Name="StudentsDataGrid"
DataContext="{Binding }"
ItemsSource="{Binding StudentsObservableCollection, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<DataGrid.Columns>
<DataGridTextColumn Header="Student ID" Binding="{Binding StudentId}" />
<DataGridTextColumn Header="First Name" Binding="{Binding FirstName}" />
<DataGridTextColumn Header="Last Name" Binding="{Binding LastName}" />
<DataGridTextColumn Header="Year" Binding="{Binding CohortYear}" />
<DataGridTextColumn Header="Unit Code" Binding="{Binding UnitCode}" />
<DataGridTextColumn Header="Email" Binding="{Binding Email}" />
<DataGridTemplateColumn>
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Button
Cursor="Hand"
Style="{StaticResource MaterialDesignFlatLightButton}"
Command="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}},
Path= DataContext.DeleteStudentCommand}"
CommandParameter="{Binding SelectedItem, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGrid}}}"
ToolTip="Delete this student's record">
<materialDesign:PackIcon Kind="Delete" />
</Button>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</UserControl>
[MaxLength(50)] 公共字符串名字 { get;放; } = 名字;
我看到这里没有人打电话给
SetField
,我不希望 PropertyChanged
被加注。
话虽这么说,Entity Framework Core 可以使用实现
INotifyPropertyChanged
的实体,请查看微软的文档:
public class StudentEntity( string firstName ) : INotifyPropertyChanged
{
[MaxLength(50)]
public string FirstName
{
get => firstName;
set => SetField( ref firstName, value );
};
// TODO add more properties
// TODO implement INotifyPropertyChanged
}