使用英语阅读

通过


如何:用 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>
    

    当用户输入无效值(例如在课程 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 来实现更广泛的自定义。

验证单个行中的多个值

  1. 实现一个检查绑定数据对象的多个属性的 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;
            }
        }
    }
    
  2. 将验证规则添加到 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
        {
            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>

另请参阅