WPF 数据编辑与验证实战指南
1. 数据性能优化选项
在处理数据时,有几种性能优化选项可供选择:
-
回收模式
:不再需要的数据模板对象会进入一个池,并根据需要从池中重用。
-
延迟滚动选项
:设置
ScrollViewer.IsDeferredScrollingEnabled
附加属性为
true
时,滚动时只有滚动条移动,列表框中的项目不会移动。直到用户完成滚动,才需要创建数据模板。从性能角度看,这是最佳选项,但可能会影响用户体验,因此在大多数情况下可能不可行。
2. 数据编辑操作
接下来将介绍如何进行数据编辑,包括检索数据、更新数据、添加验证以及使用
DataGrid
控件进行批量更改。
2.1 更新单条数据
以下示例展示了如何编辑和保存单条数据项。为了实现可取消的编辑功能,
ViewModel
类
EditRacerViewModel
实现了
IEditableObject
接口。该接口定义了
BeginEdit
、
CancelEdit
和
EndEdit
方法。
EditRacerViewModel
类还定义了四个命令:
-
GetRacerCommand
:从数据库中获取赛车手信息。
-
EditCommand
:进入编辑模式。
-
CancelCommand
:取消编辑,恢复原始值。
-
SaveCommand
:保存编辑后的信息到数据库。
using System.ComponentModel;
using System.Data;
using System.Linq;
using Formula1.Infrastructure;
using Formula1.Model;
namespace Formula1.ViewModels
{
public class EditRacerViewModel : ViewModelBase, IEditableObject
{
private DelegateCommand getRacerCommand;
public DelegateCommand GetRacerCommand
{
get
{
return getRacerCommand ??
(getRacerCommand = new DelegateCommand(param =>
this.GetRacer()));
}
}
private void GetRacer()
{
using (Formula1Entities data = new Formula1Entities())
{
var racer = (from r in data.Racers
where r.Id == this.Id
select r).Single();
RacerToVM(racer);
}
}
private void UpdateRacer()
{
using (Formula1Entities data = new Formula1Entities())
{
Racer r = data.GetObjectByKey(this.key) as Racer;
Racer current = VMToRacer();
data.Racers.ApplyCurrentValues(current);
data.SaveChanges();
}
}
private EntityKey key;
private int id;
public int Id
{
get { return id; }
set
{
if (!object.Equals(id, value))
{
id = value;
RaisePropertyChanged("Id");
}
}
}
private string firstName;
public string FirstName
{
get { return firstName; }
set
{
if (!object.Equals(firstName, value))
{
firstName = value;
RaisePropertyChanged("FirstName");
}
}
}
private string lastName;
public string LastName
{
get { return lastName; }
set
{
if (!object.Equals(lastName, value))
{
lastName = value;
RaisePropertyChanged("LastName");
}
}
}
private string nationality;
public string Nationality
{
get { return nationality; }
set
{
if (!object.Equals(nationality, value))
{
nationality = value;
RaisePropertyChanged("Nationality");
}
}
}
private int? starts;
public int? Starts
{
get { return starts; }
set
{
if (!object.Equals(starts, value))
{
starts = value;
RaisePropertyChanged("Starts");
}
}
}
private int? wins;
public int? Wins
{
get { return wins; }
set
{
if (!object.Equals(wins, value))
{
wins = value;
RaisePropertyChanged("Wins");
}
}
}
private int? points;
public int? Points
{
get { return points; }
set
{
if (!object.Equals(points, value))
{
points = value;
RaisePropertyChanged("Points");
}
}
}
private void RacerToVM(Racer r)
{
this.key = r.EntityKey;
this.Id = r.Id;
this.FirstName = r.FirstName;
this.LastName = r.LastName;
this.Nationality = r.Nationality;
this.Starts = r.Starts;
this.Wins = r.Wins;
this.Points = r.Points;
}
private Racer VMToRacer()
{
var current = Racer.CreateRacer(this.Id, this.FirstName,
this.LastName);
current.Nationality = this.Nationality;
current.Starts = this.Starts;
current.Wins = this.Wins;
current.Points = this.Points;
return current;
}
private DelegateCommand editCommand;
public DelegateCommand EditCommand
{
get
{
return editCommand ??
(editCommand = new DelegateCommand(
param => this.BeginEdit()));
}
}
private DelegateCommand cancelCommand;
public DelegateCommand CancelCommand
{
get
{
return cancelCommand ??
(cancelCommand = new DelegateCommand(param =>
this.CancelEdit()));
}
}
private DelegateCommand saveCommand;
public DelegateCommand SaveCommand
{
get
{
return saveCommand ??
(saveCommand = new DelegateCommand(
param => this.EndEdit()));
}
}
public void BeginEdit()
{
IsEditMode = true;
}
public void CancelEdit()
{
GetRacer();
IsEditMode = false;
}
public void EndEdit()
{
UpdateRacer();
IsEditMode = false;
}
private bool isEditMode;
public bool IsEditMode
{
get { return isEditMode; }
private set
{
if (!object.Equals(isEditMode, value))
{
isEditMode = value;
RaisePropertyChanged("IsEditMode");
}
}
}
}
}
2.2 数据验证
默认情况下,如果用户输入的数据不被
ViewModel
接受,
ViewModel
的属性不会反映这些更改,并且用户不会收到关于更改失败的通知。为了解决这个问题,可以使用 WPF 数据绑定提供的验证规则。
.NET 框架定义了两种具体实现:
-
ExceptionValidationRule
:处理
ViewModel
类抛出的异常。
-
DataErrorValidationRule
:需要实现
IDataErrorInfo
接口。
以下是在
EditRacerViewModel
类中实现
IDataErrorInfo
接口的示例:
string IDataErrorInfo.Error
{
get { throw new System.NotImplementedException(); }
}
string IDataErrorInfo.this[string columnName]
{
get
{
string message = null;
switch (columnName)
{
case "Wins":
case "Starts":
if (Wins > Starts)
message = "Wins must be smaller or equal to Starts";
break;
default:
break;
}
return message;
}
}
在 XAML 中,可以使用以下代码为
TextBox
添加验证规则:
<TextBox IsEnabled="{Binding IsEditMode}" Grid.Row="3" Grid.Column="1">
<TextBox.Text>
<Binding Path="Starts">
<Binding.ValidationRules>
<DataErrorValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
3. 错误显示
为了显示通过
IDataErrorInfo
接口返回的错误消息,可以为
TextBox
创建一个自定义样式,其中包含一个触发器。当
Validation.HasError
附加属性为
true
时,触发器会将
TextBox
的
ToolTip
属性设置为错误消息。
<Style TargetType="TextBox">
<Setter Property="Margin" Value="5" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="Validation.ErrorTemplate"
Value="{StaticResource validationTemplate}" />
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="True">
<Setter Property="ToolTip" Value="{Binding RelativeSource=
{x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}" />
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red" FontSize="32" FontWeight="Bold"
Text="!" DockPanel.Dock="Left" />
<AdornedElementPlaceholder DockPanel.Dock="Right" />
</DockPanel>
</ControlTemplate>
4. 使用
DataGrid
进行批量编辑
当需要一次性更改多条记录时,可以使用
DataGrid
控件。以下是一个使用
DataGrid
控件添加比赛结果到数据库的示例。
4.1 界面布局
界面包含两个按钮:“Add a Race” 和 “Edit a Race”,分别绑定到
AddRaceCommand
和
EditRaceCommand
。
<StackPanel Orientation="Horizontal" Grid.Row="0">
<Button Content="Add a Race" Command="{Binding AddRaceCommand}"
Margin="5" Padding="3" />
<Button Content="Edit a Race" Command="{Binding EditRaceCommand}"
Margin="5" Padding="3" />
</StackPanel>
4.2
ViewModel
实现
EditRaceViewModel
类定义了相关命令和属性:
public class EditRaceViewModel : ViewModelBase, IDisposable
{
private Formula1Entities data;
public EditRaceViewModel()
{
AddRaceVisibility = Visibility.Collapsed;
EditRaceVisibility = Visibility.Collapsed;
RaceResultVisibility = Visibility.Collapsed;
AddRacerVisibility = Visibility.Collapsed;
if (!IsDesignTime)
{
data = new Formula1Entities();
}
}
private DelegateCommand addRaceCommand;
public DelegateCommand AddRaceCommand
{
get
{
return addRaceCommand ??
(addRaceCommand = new DelegateCommand(
param => this.AddRace()));
}
}
public void AddRace()
{
RaceDate = DateTime.Today;
AddRaceVisibility = Visibility.Visible;
}
private Visibility addRaceVisibility;
public Visibility AddRaceVisibility
{
get
{
return addRaceVisibility;
}
set
{
addRaceVisibility = value;
RaisePropertyChanged("AddRaceVisibility");
}
}
private List<Circuit> circuits;
public IEnumerable<Circuit> Circuits
{
get
{
if (!IsDesignTime)
{
return circuits ??
(circuits = new List<Circuit>(
from c in data.Circuits
orderby c.Country
select c));
}
else
return null;
}
}
//...
}
4.3
DataGrid
配置
DataGrid
控件用于显示和编辑比赛结果。以下是
DataGrid
的配置示例:
<Grid Grid.Row="1" Visibility="{Binding RaceResultVisibility}">
<Grid.RowDefinitions>
<RowDefinition Height="Auto" />
<RowDefinition Height="Auto" />
</Grid.RowDefinitions>
<Grid DataContext="{Binding RaceResults}" Grid.Row="0">
<DataGrid x:Name="raceGrid" ItemsSource="{Binding}"
AutoGenerateColumns="False" SelectionMode="Single"
SelectionUnit="FullRow">
<DataGrid.Columns>
<DataGridTextColumn Width="Auto" Header="Pos"
Binding="{Binding Position}" />
<DataGridTemplateColumn Width="*" Header="Racer"
MinWidth="210">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox MinWidth="190"
ItemsSource="{Binding Racers}"
TextSearch.TextPath="Name"
SelectedItem=
"{Binding SelectedRacer,
Mode=OneWayToSource,
UpdateSourceTrigger=LostFocus}"
IsEditable="True"
Text="{Binding NewRacer,
Mode=OneWayToSource,
UpdateSourceTrigger=LostFocus}"
/>
<Button Content="+"
Command="{Binding
AddRacerCommand}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTemplateColumn Width="*" Header="Team"
MinWidth="120">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ComboBox ItemsSource="{Binding Teams}"
TextSearch.TextPath="Name"
SelectedItem=
"{Binding SelectedTeam,
Mode=OneWayToSource,
UpdateSourceTrigger=LostFocus}"
IsEditable="True"
Text="{Binding NewTeam,
Mode=OneWayToSource,
UpdateSourceTrigger=LostFocus}" />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
<DataGridTextColumn Width="Auto" Header="Grid"
Binding="{Binding Grid}" />
<DataGridTextColumn Width="Auto" Header="Pts"
Binding="{Binding Points}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Grid>
总结
通过以上介绍,我们了解了 WPF 中数据编辑、验证和批量编辑的相关技术。回收模式和延迟滚动选项可以优化数据处理性能;实现
IEditableObject
接口可以实现可取消的单条数据编辑;使用验证规则可以确保用户输入的数据的有效性;自定义样式可以显示错误消息;
DataGrid
控件可以方便地进行批量数据编辑。这些技术可以帮助我们开发出更加高效、稳定和用户友好的 WPF 应用程序。
流程图
graph TD;
A[开始] --> B[选择性能优化选项];
B --> C{是否使用延迟滚动};
C -- 是 --> D[设置 ScrollViewer.IsDeferredScrollingEnabled 为 true];
C -- 否 --> E[使用回收模式];
D --> F[进行数据编辑操作];
E --> F;
F --> G[更新单条数据];
G --> H[添加数据验证];
H --> I[显示错误消息];
I --> J[使用 DataGrid 进行批量编辑];
J --> K[结束];
表格
| 功能 | 实现方式 |
|---|---|
| 性能优化 |
回收模式、延迟滚动(设置
ScrollViewer.IsDeferredScrollingEnabled
为
true
)
|
| 单条数据编辑 |
实现
IEditableObject
接口,定义相关命令
|
| 数据验证 |
使用
ExceptionValidationRule
或
DataErrorValidationRule
,实现
IDataErrorInfo
接口
|
| 错误显示 |
为
TextBox
创建自定义样式,设置
ToolTip
为错误消息
|
| 批量编辑 |
使用
DataGrid
控件,配置列绑定和模板
|
WPF 数据编辑与验证实战指南(续)
5. 数据编辑流程详解
在前面的内容中,我们已经介绍了数据编辑的各个方面,下面将详细梳理整个数据编辑的流程,以便更好地理解和应用这些技术。
5.1 数据检索与初始化
当用户触发
GetRacerCommand
时,会从数据库中检索赛车手信息,并将其填充到
ViewModel
类的属性中。具体步骤如下:
1. 执行
GetRacer
方法,使用
Formula1Entities
上下文从数据库中查询指定
Id
的赛车手。
2. 将查询结果的属性值复制到
ViewModel
类的对应属性中,以便进行数据绑定。
private void GetRacer()
{
using (Formula1Entities data = new Formula1Entities())
{
var racer = (from r in data.Racers
where r.Id == this.Id
select r).Single();
RacerToVM(racer);
}
}
5.2 进入编辑模式
当用户点击 “Edit” 按钮时,会触发
EditCommand
,将
ViewModel
类的
IsEditMode
属性设置为
true
,从而进入编辑模式。此时,UI 中的
TextBox
元素会根据
IsEditMode
属性的绑定状态变为可编辑状态。
private DelegateCommand editCommand;
public DelegateCommand EditCommand
{
get
{
return editCommand ??
(editCommand = new DelegateCommand(
param => this.BeginEdit()));
}
}
public void BeginEdit()
{
IsEditMode = true;
}
5.3 数据验证
在用户输入数据时,会根据配置的验证规则进行验证。如果输入的数据不符合规则,会显示相应的错误消息。验证规则的实现主要通过
IDataErrorInfo
接口,在
EditRacerViewModel
类中实现该接口的索引器方法,根据属性名进行验证。
string IDataErrorInfo.this[string columnName]
{
get
{
string message = null;
switch (columnName)
{
case "Wins":
case "Starts":
if (Wins > Starts)
message = "Wins must be smaller or equal to Starts";
break;
default:
break;
}
return message;
}
}
5.4 保存或取消编辑
-
保存编辑
:当用户点击 “Save” 按钮时,会触发
SaveCommand,调用EndEdit方法,将ViewModel类的属性值更新到数据库中,并将IsEditMode属性设置为false,退出编辑模式。
private DelegateCommand saveCommand;
public DelegateCommand SaveCommand
{
get
{
return saveCommand ??
(saveCommand = new DelegateCommand(
param => this.EndEdit()));
}
}
public void EndEdit()
{
UpdateRacer();
IsEditMode = false;
}
-
取消编辑
:当用户点击 “Cancel” 按钮时,会触发
CancelCommand,调用CancelEdit方法,重新从数据库中检索原始数据,填充到ViewModel类的属性中,并将IsEditMode属性设置为false,退出编辑模式。
private DelegateCommand cancelCommand;
public DelegateCommand CancelCommand
{
get
{
return cancelCommand ??
(cancelCommand = new DelegateCommand(param =>
this.CancelEdit()));
}
}
public void CancelEdit()
{
GetRacer();
IsEditMode = false;
}
6.
DataGrid
高级配置
在使用
DataGrid
进行批量编辑时,除了基本的配置外,还可以进行一些高级配置,以满足更复杂的需求。
6.1 自定义列模板
DataGridTemplateColumn
可以让我们自定义列的显示和编辑方式。例如,在显示赛车手信息的列中,使用
ComboBox
控件让用户可以选择现有的赛车手,也可以输入新的赛车手名称。
<DataGridTemplateColumn Width="*" Header="Racer"
MinWidth="210">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<ComboBox MinWidth="190"
ItemsSource="{Binding Racers}"
TextSearch.TextPath="Name"
SelectedItem=
"{Binding SelectedRacer,
Mode=OneWayToSource,
UpdateSourceTrigger=LostFocus}"
IsEditable="True"
Text="{Binding NewRacer,
Mode=OneWayToSource,
UpdateSourceTrigger=LostFocus}"
/>
<Button Content="+"
Command="{Binding
AddRacerCommand}" />
</StackPanel>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
6.2 数据绑定模式和触发更新
在
ComboBox
的绑定中,使用
OneWayToSource
模式和
LostFocus
触发更新,确保只有在用户输入完成并失去焦点时才更新绑定源。
<ComboBox SelectedItem=
"{Binding SelectedRacer,
Mode=OneWayToSource,
UpdateSourceTrigger=LostFocus}"
IsEditable="True"
Text="{Binding NewRacer,
Mode=OneWayToSource,
UpdateSourceTrigger=LostFocus}"
/>
7. 总结与回顾
通过本文的介绍,我们全面了解了 WPF 中数据编辑、验证和批量编辑的相关技术。从性能优化选项的选择,到单条数据的编辑和验证,再到使用
DataGrid
进行批量编辑,每个环节都有详细的实现方法和示例代码。
在实际开发中,我们可以根据具体需求选择合适的技术和实现方式,提高应用程序的性能和用户体验。同时,通过合理的配置和优化,确保数据的准确性和完整性。
流程图
graph TD;
A[开始数据编辑] --> B[检索数据];
B --> C[进入编辑模式];
C --> D{是否验证通过};
D -- 是 --> E[保存编辑];
D -- 否 --> F[显示错误消息];
F --> C;
E --> G{是否继续编辑};
G -- 是 --> C;
G -- 否 --> H[结束编辑];
表格
| 步骤 | 操作内容 |
|---|---|
| 数据检索 |
触发
GetRacerCommand
,从数据库中查询数据并填充到
ViewModel
|
| 进入编辑模式 |
触发
EditCommand
,设置
IsEditMode
为
true
|
| 数据验证 |
使用
IDataErrorInfo
接口进行验证
|
| 保存编辑 |
触发
SaveCommand
,更新数据库并退出编辑模式
|
| 取消编辑 |
触发
CancelCommand
,恢复原始数据并退出编辑模式
|
| 批量编辑 |
使用
DataGrid
控件,配置列模板和绑定
|
超级会员免费看
430

被折叠的 条评论
为什么被折叠?



