借助 DataGrid 控件,可以在单元格级别和行级别执行验证。 通过单元格级别验证,可以在用户更新值时验证绑定数据对象的单个属性。 通过行级别验证,可以在用户提交对行的更改时验证整个数据对象。 还可以提供针对验证错误的自定义可视化反馈,或使用 DataGrid 控件提供的默认可视化反馈。

以下过程介绍如何将验证规则应用于 DataGrid 绑定并自定义可视化反馈。

验证各单元格值

  • 针对与列一起使用的绑定指定一个或多个验证规则。 这类似于在简单控件中验证数据,如 数据绑定概述 中所述。

    以下示例显示了一个 DataGrid 控件,其中四列绑定到业务对象的不同属性。 其中三列通过将 ValidatesOnExceptions 属性设置为 true 来指定 ExceptionValidationRule

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

    当用户输入无效值(例如在课程 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.");
                return ValidationResult.ValidResult;
    
    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.")
                Return ValidationResult.ValidResult
            End If
        End Function
    End Class
    
  • 将验证规则添加到 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"

    若要测试验证,请尝试以下操作:

  • 在“课程 ID”列中输入一个非整数值。

  • 在“结束日期”列中输入一个早于开始日期的日期。

  • 删除“课程 ID”、“开始日期”或“结束日期”中的值。

  • 若要撤消无效的单元格值,请将光标放回到单元格中,然后按 ESC 键。

  • 若要在当前单元格处于编辑模式时撤消整个行的更改,请按 ESC 键两次。

  • 发生验证错误时,将鼠标指针移到行标题中的标记上,以查看关联的错误消息。

    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
                    return _name;
                    if (_name == value) return;
                    _name = value;
                    OnPropertyChanged("Name");
            private int _number;
            public int Id
                    return _number;
                    if (_number == value) return;
                    _number = value;
                    OnPropertyChanged("Id");
            private DateTime _startDate;
            public DateTime StartDate
                    return _startDate;
                    if (_startDate == value) return;
                    _startDate = value;
                    OnPropertyChanged("StartDate");
            private DateTime _endDate;
            public DateTime EndDate
                    return _endDate;
                    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.");
                    return ValidationResult.ValidResult;
    
    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
                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
                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
                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
                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.")
                Return ValidationResult.ValidResult
            End If
        End Function
    End Class
    
    <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.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>
    
  • DataGrid
  • DataGrid
  • 实现绑定验证
  • 在自定义对象上实现验证逻辑
  •