如何:用 DataGrid 控件实现验证
利用 DataGrid 控件,您既可以在单元格级别也可以在行级别执行验证。 通过单元格级别验证,将可在用户更新值时验证绑定数据对象的个别属性。 通过行级别验证,将可在用户提交对行的更改时验证整个数据对象。 您也可以为验证错误提供自定义的可视反馈,或使用 DataGrid 控件提供的默认可视反馈。
以下过程介绍如何将验证规则应用于 DataGrid 绑定,并自定义可视反馈。
验证个别单元格值
-
对用于列的绑定指定一条或多条验证规则。 这与在简单控件中验证数据类似,如数据绑定概述中所述。
下面的示例显示一个 DataGrid 控件,其中包含绑定到业务对象的不同属性的四个列。 其中三个列通过将 ValidatesOnExceptions 属性设置为 true 来指定ExceptionValidationRule。
<Grid> <Grid.Resources> <local:Courses x:Key="courses"/> </Grid.Resources> <DataGrid Name="dataGrid1" FontSize="20" ItemsSource="{StaticResource courses}" AutoGenerateColumns="False"> <DataGrid.Columns> <DataGridTextColumn Header="Course Name" Binding="{Binding Name, TargetNullValue=(enter a course name)}"/> <DataGridTextColumn Header="Course ID" Binding="{Binding Id, ValidatesOnExceptions=True}"/> <DataGridTextColumn Header="Start Date" Binding="{Binding StartDate, ValidatesOnExceptions=True, StringFormat=d}"/> <DataGridTextColumn Header="End Date" Binding="{Binding EndDate, ValidatesOnExceptions=True, StringFormat=d}"/> </DataGrid.Columns> </DataGrid> </Grid>
当用户输入无效的值,例如在“Course ID”(课程 ID)列中输入非整数时,单元格周围将出现一个红色边框。 可以按照下面过程中所述的方式更改此默认验证反馈。
自定义单元格验证反馈
-
将列的 EditingElementStyle 属性设置为适合于列的编辑控件的样式。 由于编辑控件是在运行时创建的,因此您无法像处理简单控件一样使用Validation.ErrorTemplate 附加属性。
下面的示例通过添加由具有验证规则的三个列共享的错误样式来更新前面的示例。 当用户输入无效的值时,样式会更改单元格背景颜色,并添加一个工具提示。 请注意使用触发器来确定是否存在验证错误的做法。 这是必要的,因为当前没有专门用于单元格的错误模板。
<DataGrid.Resources> <Style x:Key="errorStyle" TargetType="{x:Type TextBox}"> <Setter Property="Padding" Value="-2"/> <Style.Triggers> <Trigger Property="Validation.HasError" Value="True"> <Setter Property="Background" Value="Red"/> <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/> </Trigger> </Style.Triggers> </Style> </DataGrid.Resources> <DataGrid.Columns> <DataGridTextColumn Header="Course Name" Binding="{Binding Name, TargetNullValue=(enter a course name)}"/> <DataGridTextColumn Header="Course ID" EditingElementStyle="{StaticResource errorStyle}" Binding="{Binding Id, ValidatesOnExceptions=True}"/> <DataGridTextColumn Header="Start Date" EditingElementStyle="{StaticResource errorStyle}" Binding="{Binding StartDate, ValidatesOnExceptions=True, StringFormat=d}"/> <DataGridTextColumn Header="End Date" EditingElementStyle="{StaticResource errorStyle}" Binding="{Binding EndDate, ValidatesOnExceptions=True, StringFormat=d}"/> </DataGrid.Columns>
可通过替换列使用的 CellStyle 来实现更广泛的自定义。
验证单个行中的多个值
-
实现一个 ValidationRule 子类,该子类将检查绑定的数据对象的多个属性。 在 Validate 方法实现中,将 value 参数值强制转换为 BindingGroup 实例。 然后,即可通过 Items 属性访问数据对象。
下面的示例演示此过程,以验证 Course 对象的 StartDate 属性值是否早于其 EndDate 属性值。
public class CourseValidationRule : ValidationRule { public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) { Course course = (value as BindingGroup).Items[0] as Course; if (course.StartDate > course.EndDate) { return new ValidationResult(false, "Start Date must be earlier than End Date."); } else { return ValidationResult.ValidResult; } } }
-
将验证规则添加到 DataGrid.RowValidationRules 集合。 RowValidationRules 属性提供对 BindingGroup 实例的 ValidationRules 属性的直接访问,该实例对控件使用的所有绑定进行分组。
下面的示例在 XAML 中设置 RowValidationRules 属性。 ValidationStep 属性设置为 UpdatedValue,以使验证只会在更新了绑定数据对象之后进行。
<DataGrid.RowValidationRules> <local:CourseValidationRule ValidationStep="UpdatedValue"/> </DataGrid.RowValidationRules>
当用户指定早于开始日期的结束日期时,行标题中将出现一个红色感叹号 (!)。 可以按照下面过程中所述的方式更改此默认验证反馈。
自定义行验证反馈
-
设置 DataGrid.RowValidationErrorTemplate 属性。 利用此属性,您可以为个别 DataGrid 控件自定义行验证反馈。 通过使用隐式行样式来设置DataGridRow.ValidationErrorTemplate 属性,您也可以影响多个控件。
下面的示例将默认行验证反馈替换为更直观的指示符。 当用户输入无效的值时,行标题中将出现一个带有白色感叹号的红色圆圈。 对于行验证错误和单元格验证错误,都将发生此情况。 关联的错误消息显示在工具提示中。
<DataGrid.RowValidationErrorTemplate> <ControlTemplate> <Grid Margin="0,-2,0,-2" ToolTip="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}}, Path=(Validation.Errors)[0].ErrorContent}"> <Ellipse StrokeThickness="0" Fill="Red" Width="{TemplateBinding FontSize}" Height="{TemplateBinding FontSize}" /> <TextBlock Text="!" FontSize="{TemplateBinding FontSize}" FontWeight="Bold" Foreground="White" HorizontalAlignment="Center" /> </Grid> </ControlTemplate> </DataGrid.RowValidationErrorTemplate>
下面的示例提供了单元格验证和行验证的完整演示。 Course 类提供了一个示例数据对象,该对象实现 IEditableObject 以支持事务。 DataGrid 控件与 IEditableObject交互,使用户能够通过按 Esc 来恢复更改。
说明 |
---|
如果使用的是 Visual Basic,请在 MainWindow.xaml 的第一行中将 x:Class="DataGridValidation.MainWindow" 替换为 x:Class="MainWindow"。 |
若要测试验证,请尝试以下操作:
-
在“Course ID”(课程 ID)列中,输入一个非整数值。
-
在“End Date”(结束日期)列中,输入一个早于开始日期的日期。
-
删除“Course ID”(课程 ID)、“Start Date”(开始日期)或“End Date”(结束日期)中的值。
-
若要撤消无效的单元格值,请将光标重新放在单元格中,并按 Esc 键。
-
若要在当前单元格处于编辑模式中时撤消整行的更改,请按 Esc 键两次。
-
发生验证错误时,将鼠标指针移到行标题中的指示符上即可看到关联的错误消息。
Imports System.Collections.ObjectModel Imports System.ComponentModel Public Class MainWindow Private Sub dataGrid1_InitializingNewItem(ByVal sender As System.Object, _ ByVal e As System.Windows.Controls.InitializingNewItemEventArgs) _ Handles dataGrid1.InitializingNewItem Dim newCourse As Course = CType(e.NewItem, Course) newCourse.StartDate = DateTime.Today newCourse.EndDate = DateTime.Today End Sub End Class Public Class Courses Inherits ObservableCollection(Of Course) Sub New() Me.Add(New Course With { _ .Name = "Learning WPF", _ .Id = 1001, _ .StartDate = New DateTime(2010, 1, 11), _ .EndDate = New DateTime(2010, 1, 22) _ }) Me.Add(New Course With { _ .Name = "Learning Silverlight", _ .Id = 1002, _ .StartDate = New DateTime(2010, 1, 25), _ .EndDate = New DateTime(2010, 2, 5) _ }) Me.Add(New Course With { _ .Name = "Learning Expression Blend", _ .Id = 1003, _ .StartDate = New DateTime(2010, 2, 8), _ .EndDate = New DateTime(2010, 2, 19) _ }) Me.Add(New Course With { _ .Name = "Learning LINQ", _ .Id = 1004, _ .StartDate = New DateTime(2010, 2, 22), _ .EndDate = New DateTime(2010, 3, 5) _ }) End Sub End Class Public Class Course Implements IEditableObject, INotifyPropertyChanged Private _name As String Public Property Name As String Get Return _name End Get Set(ByVal value As String) If _name = value Then Return _name = value OnPropertyChanged("Name") End Set End Property Private _number As Integer Public Property Id As Integer Get Return _number End Get Set(ByVal value As Integer) If _number = value Then Return _number = value OnPropertyChanged("Id") End Set End Property Private _startDate As DateTime Public Property StartDate As DateTime Get Return _startDate End Get Set(ByVal value As DateTime) If _startDate = value Then Return _startDate = value OnPropertyChanged("StartDate") End Set End Property Private _endDate As DateTime Public Property EndDate As DateTime Get Return _endDate End Get Set(ByVal value As DateTime) If _endDate = value Then Return _endDate = value OnPropertyChanged("EndDate") End Set End Property #Region "IEditableObject" Private backupCopy As Course Private inEdit As Boolean Public Sub BeginEdit() Implements IEditableObject.BeginEdit If inEdit Then Return inEdit = True backupCopy = CType(Me.MemberwiseClone(), Course) End Sub Public Sub CancelEdit() Implements IEditableObject.CancelEdit If Not inEdit Then Return inEdit = False Me.Name = backupCopy.Name Me.Id = backupCopy.Id Me.StartDate = backupCopy.StartDate Me.EndDate = backupCopy.EndDate End Sub Public Sub EndEdit() Implements IEditableObject.EndEdit If Not inEdit Then Return inEdit = False backupCopy = Nothing End Sub #End Region #Region "INotifyPropertyChanged" Public Event PropertyChanged As PropertyChangedEventHandler _ Implements INotifyPropertyChanged.PropertyChanged Private Sub OnPropertyChanged(ByVal propertyName As String) RaiseEvent PropertyChanged(Me, _ New PropertyChangedEventArgs(propertyName)) End Sub #End Region End Class Public Class CourseValidationRule Inherits ValidationRule Public Overrides Function Validate(ByVal value As Object, _ ByVal cultureInfo As System.Globalization.CultureInfo) _ As ValidationResult Dim course As Course = _ CType(CType(value, BindingGroup).Items(0), Course) If course.StartDate > course.EndDate Then Return New ValidationResult(False, _ "Start Date must be earlier than End Date.") Else Return ValidationResult.ValidResult End If End Function End Class
using System; using System.Collections.ObjectModel; using System.ComponentModel; using System.Windows; using System.Windows.Controls; using System.Windows.Data; namespace DataGridValidation { public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); dataGrid1.InitializingNewItem += (sender, e) => { Course newCourse = e.NewItem as Course; newCourse.StartDate = newCourse.EndDate = DateTime.Today; }; } } public class Courses : ObservableCollection<Course> { public Courses() { this.Add(new Course { Name = "Learning WPF", Id = 1001, StartDate = new DateTime(2010, 1, 11), EndDate = new DateTime(2010, 1, 22) }); this.Add(new Course { Name = "Learning Silverlight", Id = 1002, StartDate = new DateTime(2010, 1, 25), EndDate = new DateTime(2010, 2, 5) }); this.Add(new Course { Name = "Learning Expression Blend", Id = 1003, StartDate = new DateTime(2010, 2, 8), EndDate = new DateTime(2010, 2, 19) }); this.Add(new Course { Name = "Learning LINQ", Id = 1004, StartDate = new DateTime(2010, 2, 22), EndDate = new DateTime(2010, 3, 5) }); } } public class Course : IEditableObject, INotifyPropertyChanged { private string _name; public string Name { get { return _name; } set { if (_name == value) return; _name = value; OnPropertyChanged("Name"); } } private int _number; public int Id { get { return _number; } set { if (_number == value) return; _number = value; OnPropertyChanged("Id"); } } private DateTime _startDate; public DateTime StartDate { get { return _startDate; } set { if (_startDate == value) return; _startDate = value; OnPropertyChanged("StartDate"); } } private DateTime _endDate; public DateTime EndDate { get { return _endDate; } set { if (_endDate == value) return; _endDate = value; OnPropertyChanged("EndDate"); } } #region IEditableObject private Course backupCopy; private bool inEdit; public void BeginEdit() { if (inEdit) return; inEdit = true; backupCopy = this.MemberwiseClone() as Course; } public void CancelEdit() { if (!inEdit) return; inEdit = false; this.Name = backupCopy.Name; this.Id = backupCopy.Id; this.StartDate = backupCopy.StartDate; this.EndDate = backupCopy.EndDate; } public void EndEdit() { if (!inEdit) return; inEdit = false; backupCopy = null; } #endregion #region INotifyPropertyChanged public event PropertyChangedEventHandler PropertyChanged; private void OnPropertyChanged(string propertyName) { if (PropertyChanged != null) { PropertyChanged(this, new PropertyChangedEventArgs(propertyName)); } } #endregion } public class CourseValidationRule : ValidationRule { public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo) { Course course = (value as BindingGroup).Items[0] as Course; if (course.StartDate > course.EndDate) { return new ValidationResult(false, "Start Date must be earlier than End Date."); } else { return ValidationResult.ValidResult; } } } }
<Window x:Class="DataGridValidation.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:DataGridValidation" Title="DataGrid Validation Example" Height="240" Width="600"> <Grid> <Grid.Resources> <local:Courses x:Key="courses"/> </Grid.Resources> <DataGrid Name="dataGrid1" FontSize="20" RowHeaderWidth="27" ItemsSource="{StaticResource courses}" AutoGenerateColumns="False"> <DataGrid.Resources> <Style x:Key="errorStyle" TargetType="{x:Type TextBox}"> <Setter Property="Padding" Value="-2"/> <Style.Triggers> <Trigger Property="Validation.HasError" Value="True"> <Setter Property="Background" Value="Red"/> <Setter Property="ToolTip" Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}"/> </Trigger> </Style.Triggers> </Style> </DataGrid.Resources> <DataGrid.Columns> <DataGridTextColumn Header="Course Name" Binding="{Binding Name, TargetNullValue=(enter a course name)}"/> <DataGridTextColumn Header="Course ID" EditingElementStyle="{StaticResource errorStyle}" Binding="{Binding Id, ValidatesOnExceptions=True}"/> <DataGridTextColumn Header="Start Date" EditingElementStyle="{StaticResource errorStyle}" Binding="{Binding StartDate, ValidatesOnExceptions=True, StringFormat=d}"/> <DataGridTextColumn Header="End Date" EditingElementStyle="{StaticResource errorStyle}" Binding="{Binding EndDate, ValidatesOnExceptions=True, StringFormat=d}"/> </DataGrid.Columns> <DataGrid.RowValidationRules> <local:CourseValidationRule ValidationStep="UpdatedValue"/> </DataGrid.RowValidationRules> <DataGrid.RowValidationErrorTemplate> <ControlTemplate> <Grid Margin="0,-2,0,-2" ToolTip="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type DataGridRow}}, Path=(Validation.Errors)[0].ErrorContent}"> <Ellipse StrokeThickness="0" Fill="Red" Width="{TemplateBinding FontSize}" Height="{TemplateBinding FontSize}" /> <TextBlock Text="!" FontSize="{TemplateBinding FontSize}" FontWeight="Bold" Foreground="White" HorizontalAlignment="Center" /> </Grid> </ControlTemplate> </DataGrid.RowValidationErrorTemplate> </DataGrid> </Grid> </Window>