Binding数据校验ValidationRule

数据校验

只有当来自目标的值正被用于更新源时才会被应用验证,也就是只有当使用TwoWay模式或OneWayToSource模式的绑定时才应用验证。

如果需要在Source数据更新时,检验数据,则需要设置ValidatesOnTargetUpdated

通常是在转换值之前进行验证,但ExceptionValidationRule验证规则是例外。

验证失败:

  • Validation.HasError附加属性设置为true。
  • 包含错误细节的ValidationError对象添加到关联的ValidationErrors集合中。
  • 如果Binding.NotifyOnValidationError 属性设置为true,WPF就在元素上引发Validation.Error附加事件。

Validation.HasError属性设置为true时,WPF自动将控件模板切换为Validation.ErrorTemplate附加属性定义的模板。

WPF其他捕获错误的方式:INotifyDataErrorInfo、IDataErrorInfo接口及自定义验证规则,这2个接口允许你构建报告错误的对象而不抛出异常。

INotifyDataErrorInfo接口

定义数据实体类可实现以提供自定义同步和异步验证支持的成员。

public interface INotifyDataErrorInfo
类型名称备注权限
属性

HasErrors

获取一个值,该值指示实体是否包含验证错误。get;
方法

GetErrors

获取针对指定属性或整个实体的验证错误。public
事件

ErrorsChanged

当验证错误针对属性或整个实体更改时发生。(添加、删除错误时引发)

最简单的方法是使用Dictionary<T,K>集合来跟踪错误。

为告知WPF使用INotifyDataErrorInfo接口,并通过该接口在修改属性时检查错误,Binding的ValidatesOnNotifyDataErrors属性必须设置为true(默认)。

响应验证错误

Binding的ValidatesOnNotifyDataErrors属性设置为true(默认),当存储或删除错误时会引发错误事件;

Error事件是使用冒泡策略的路由事件,所以可以在父容器中关联事件处理程序来为多个控件处理Error事件。

ValidationErrorEventArgs.Error提供了一个ValidationError对象,该对象将引起问题的异常(Exception)、违反的验证规则(ValidationRule)、关键的绑定对象(BindingInError)以及ValidationRule对象返回的任何自定义信息(ErrorContent)捆绑在一起。

自定义验证规则,一般把错误信息放在ErrorContent中。而使用ExceptionValidationRule(预先构建的验证规则)验证规则时,ErrorContent属性将返回响应异常的Message属性。

获取错误列表

遍历元素树,测试每个元素的Validation.HasError属性。

foreach(object child in LogicalTreeHelper.GetChildren(obj))
{
    ...
    if(Validation.GethasError(element))
    {
        sb.Append(...)
        foreach(ValidationError in Validation.GethasError(element))
        {
        ...
        }
    }
}

显示错误

一般是自定义错误模板。

AdornedElementPlaceholder表示 ControlTemplate 中使用的元素,该元素用于指定修饰控件相对于 ControlTemplate 中的其他元素所放置的位置。

数据绑定验证错误

<ControlTemplate x:Key="validationTemplate">
  <DockPanel>
    <TextBlock Foreground="Red" FontSize="20">!</TextBlock>
    <AdornedElementPlaceholder/>
  </DockPanel>
</ControlTemplate>
<TextBox Name="StartDateEntryForm" Grid.Row="3" Grid.Column="1" 
    Validation.ErrorTemplate="{StaticResource validationTemplate}" 
    Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5">
    <TextBox.Text>
        <Binding Path="StartDate" UpdateSourceTrigger="PropertyChanged" 
            Converter="{StaticResource dateConverter}" >
            <Binding.ValidationRules>
                <src:FutureDateRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

验证多个值

BindingGroup

BindingGroup可用于验证对象的多个属性的值。 

BindingGroup属性
名称备注权限
BindingExpressions获取 BindingExpression 对象的集合,其中包含有关 BindingGroup 中每个绑定的信息。get;
CanRestoreValues获取绑定中的每个源是否都可以放弃挂起的更改并还原原始值。get;
HasValidationError获取一个值,该值指示 BindingGroup 是否具有一个失败的验证规则。get;
IsDirty获取或设置指示 BindingGroup 是否包含尚未写入到源中的建议值的值。get;
Items获取 BindingGroup 中的绑定对象所使用的源。get;
Name获取或设置标识 BindingGroup 的名称,该名称可用于在 BindingGroup 中包括和排除绑定对象。get; set;
NotifyOnValidationError获取或设置在 Error 的状态更改时是否发生 ValidationRule 事件。get; set;
Owner获取 BindingGroup 分配到的对象。get;
SharesProposedValues获取或设置一个值,该值指示 BindingGroup 是否重新使用尚未提交到源的目标值。get; set;
ValidatesOnNotifyDataError获取或设置一个值,该值指示是否包含 NotifyDataErrorValidationRuleget; set;
ValidationErrors获取促使 ValidationError 有效的 BindingGroup 对象的集合。get;
ValidationRules获取 ValidationRule 对象的集合,这些对象验证 BindingGroup 中的源对象。get;
BindingGroup方法
名称备注权限
BeginEdit开始 BindingGroup 中源上的编辑事务。public
CancelEdit结束编辑事务并放弃挂起的更改。public
CommitEdit运行所有 ValidationRule 对象,并且在所有验证规则都成功时,更新绑定源。public

GetValue

返回指定的属性和项的建议值。public
TryGetValue尝试获取指定属性和项的建议值。public
UpdateSources如果所有验证规则都成功,则对绑定和将 ValidationRule 属性设置为 ValidationStepRawProposedValue 或 ConvertedProposedValue 的 UpdatedValue 对象运行转换器,并将目标值保存到源对象。public
ValidateWithoutUpdate对绑定和将 ValidationRule 属性设置为 ValidationStep 或 RawProposedValue 的 ConvertedProposedValue 对象运行转换器。public

 例如,假设应用程序提示用户输入地址,然后使用用户提供的值填充类型为Address 的对象 ,该对象具有属性 Street 、 City 、 ZipCode 和 Country 。 应用程序有一个面板,其中包含四个 TextBox 控件,其中每个控件都绑定到对象的一个属性。 可以在ValidationRule 中使用 BindingGroup 来验证 Address 对象。 例如, ValidationRule 可以确保邮政编码对地址的国家/地区有效。

子元素继承其BindingGroup 父元素,就像任何其他可继承的属性一样。

以下示例是应用程序的一部分,用于检查用户是否已将两个对象的属性设置为相等值。 第一个示例创建两个 TextBox 控件,其中每个控件都绑定到不同的数据源。 StackPanel具有一个 BindingGroup ,它包含 ValidationRule 用于检查两个字符串是否相等的。

<StackPanel>
  <StackPanel.Resources>
    <src:Type1 x:Key="object1" />
    <src:Type2 x:Key="object2" />
  </StackPanel.Resources>

  <StackPanel Name="sp1"
              Margin="5"
              DataContext="{Binding Source={StaticResource object1}}"
              Validation.ValidationAdornerSite="{Binding ElementName=label1}"
              Orientation="Horizontal"
              HorizontalAlignment="Center">

    <StackPanel.BindingGroup>
      <BindingGroup Name="bindingGroup">
        <BindingGroup.ValidationRules>
          <src:BindingGroupValidationRule ValidatesOnTargetUpdated="True" />
        </BindingGroup.ValidationRules>
      </BindingGroup>
    </StackPanel.BindingGroup>

    <TextBlock Text="First string" />

    <TextBox Width="150"
             Text="{Binding Path=PropertyA}" />

    <TextBlock Text="Second string" />

    <TextBox Width="150"
             Text="{Binding Source={StaticResource object2}, 
      Path=PropertyB, BindingGroupName=bindingGroup, 
      TargetNullValue=please enter a string}" />

  </StackPanel>

  <Label Name="label1"
         Content="{Binding ElementName=sp1, Path=(Validation.Errors)[0].ErrorContent}"
         Margin="5"
         Foreground="Red"
         HorizontalAlignment="Center" />

  <Button HorizontalAlignment="Center"
          Click="Button_Click"
          IsDefault="True">
    _Submit
  </Button>

  <StackPanel Orientation="Horizontal">
    <TextBlock Text="First string:"
               FontWeight="Bold" />
    <TextBlock Text="{Binding Source={StaticResource object1}, 
      Path=PropertyA, TargetNullValue=--}" />
  </StackPanel>

  <StackPanel Orientation="Horizontal">
    <TextBlock Text="Second string:"
               FontWeight="Bold" />
    <TextBlock Text="{Binding Source={StaticResource object2}, 
      Path=PropertyB, TargetNullValue=--}" />
  </StackPanel>
</StackPanel>

下面的示例演示 ValidationRule 前面的示例使用的。 在 Validate 方法重写中,该示例从获取每个源对象, BindingGroup 并检查对象的属性是否相等。

public class Type1
{
    public string PropertyA { get; set; }

    public Type1()
    {
        PropertyA = "Default Value";
    }
}

public class Type2
{
    public string PropertyB { get; set; }

    public Type2()
    {
    }
}

public class BindingGroupValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
    {
        BindingGroup bg = value as BindingGroup;

        Type1 object1 = null;
        Type2 object2 = null;

        foreach (object item in bg.Items)
        {
            if (item is Type1)
            {
                object1 = item as Type1;
            }

            if (item is Type2)
            {
                object2 = item as Type2;
            }
        }

        if (object1 == null || object2 == null)
        {
            return new ValidationResult(false, "BindingGroup did not find source object.");
        }

        string string1 = bg.GetValue(object1, "PropertyA") as string;
        string string2 = bg.GetValue(object2, "PropertyB") as string;

        if (string1 != string2)
        {
            return new ValidationResult(false, "The two strings must be identical.");
        }

        return ValidationResult.ValidResult;
    }
}

若要调用 ValidationRule ,请调用 UpdateSources 方法。 下面的示例在 UpdateSources 按钮的 click 事件发生时调用。

private void Button_Click(object sender, RoutedEventArgs e)
{
    sp1.BindingGroup.UpdateSources();
}

获取用于检查用户输入有效性的规则的集合。

public System.Collections.ObjectModel.Collection<System.Windows.Controls.ValidationRule> ValidationRules { get; }

获取一个值,该值指示根据 ValidationRule,选中值是否有效。

public bool IsValid { get; }

获取提供有关无效性的附加信息的对象。

public object ErrorContent { get; }

获取或设置一个值,该值指示当 Binding 的目标更新时是否运行验证规则。

public bool ValidatesOnTargetUpdated { get; set; }

获取或设置一个值,该值指示是否对绑定对象引发 Error 附加事件。

public bool NotifyOnValidationError { get; set; }

为指定的路由事件添加路由事件处理程序,并将该处理程序添加到当前元素的处理程序集合中。

public void AddHandler (System.Windows.RoutedEvent routedEvent, Delegate handler);

为指定的路由事件添加路由事件处理程序,并将该处理程序添加到当前元素的处理程序集合中。 将 handledEventsToo 指定为 true,可为已标记为由事件路由中的其他元素处理的路由事件调用所提供的处理程序。

public void AddHandler (System.Windows.RoutedEvent routedEvent, Delegate handler, bool handledEventsToo);


范例-参考自《深入浅出WPF》刘铁锰

<Window
    x:Class="BindingSourceDemo.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:local="clr-namespace:BindingSourceDemo"    
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    xmlns:collection="clr-namespace:System.Collections;assembly=mscorlib"
    xmlns:sys="clr-namespace:System;assembly=mscorlib"
    Title="MainWindow"
    Width="400"
    Height="200"
    mc:Ignorable="d">
    <StackPanel>
        <TextBox x:Name="tbx1"  />
        <Slider x:Name="sld1"  Value="5" AutoToolTipPlacement="BottomRight" AutoToolTipPrecision="6" Minimum="-10"/>
    </StackPanel>
</Window>
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.Xml;

namespace BindingSourceDemo
{
    /// <summary>
    /// MainWindow.xaml 的交互逻辑
    /// </summary>
    public partial class MainWindow : Window
    {

        public MainWindow()
        {
            InitializeComponent();
            Binding binding = new Binding("Value") { ElementName="sld1",UpdateSourceTrigger= UpdateSourceTrigger .PropertyChanged};
            RangeValidationRule rvr = new RangeValidationRule();
            binding.ValidationRules.Add(rvr);           
            

            //更新源检验数据
            rvr.ValidatesOnTargetUpdated = true;

            //提示错误
            binding.NotifyOnValidationError = true;
            tbx1.AddHandler(Validation.ErrorEvent,new RoutedEventHandler(this.ValidationErrorMethord));

            tbx1.SetBinding(TextBox.TextProperty, binding);
        }

        private void ValidationErrorMethord(object sender, RoutedEventArgs e)
        {
            if (Validation.GetErrors(tbx1).Count > 0) tbx1.ToolTip = Validation.GetErrors(tbx1)[0].ErrorContent.ToString();
        }

        public class RangeValidationRule : ValidationRule
        {
            public override ValidationResult Validate(object value, CultureInfo cultureInfo)
            {
                double d = 0;
                if(double.TryParse(value.ToString(),out d))
                {
                    if (d >= 0 && d <= 100) return new ValidationResult(true, null);
                }
                return new ValidationResult(false, "Out of Range");
            }
        }


    }
}

微软关于数据验证

WPF 数据绑定模型允许将 ValidationRules 与 Binding 对象关联。 例如,以下示例将 TextBox 绑定到名为 StartPrice 的属性,并将 ExceptionValidationRule 对象添加到 Binding.ValidationRules 属性。

<TextBox Name="StartPriceEntryForm" Grid.Row="2"
         Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2">
    <TextBox.Text>
        <Binding Path="StartPrice" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <ExceptionValidationRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

ValidationRule 对象检查属性的值是否有效。 WPF 有两种类型的内置 ValidationRule 对象:

还可以通过从 ValidationRule 类派生并实现 Validate 方法来创建自己的验证规则。 以下示例演示了 什么是数据绑定部分中 添加产品清单“起始日期”TextBox 所用的规则。

public class FutureDateRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        // Test if date is valid
        if (DateTime.TryParse(value.ToString(), out DateTime date))
        {
            // Date is not in the future, fail
            if (DateTime.Now > date)
                return new ValidationResult(false, "Please enter a date in the future.");
        }
        else
        {
            // Date is not a valid date, fail
            return new ValidationResult(false, "Value is not a valid date.");
        }

        // Date is valid and in the future, pass
        return ValidationResult.ValidResult;
    }
}

StartDateEntryForm TextBox 使用此 FutureDateRule,如以下示例所示。

<TextBox Name="StartDateEntryForm" Grid.Row="3"
         Validation.ErrorTemplate="{StaticResource validationTemplate}" 
         Style="{StaticResource textStyleTextBox}" Margin="8,5,0,5" Grid.ColumnSpan="2">
    <TextBox.Text>
        <Binding Path="StartDate" UpdateSourceTrigger="PropertyChanged" 
                 Converter="{StaticResource dateConverter}" >
            <Binding.ValidationRules>
                <src:FutureDateRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

因为 UpdateSourceTrigger 值为 PropertyChanged,所以绑定引擎会在每次击键时更新源值,这意味着它还会在每次击键时检查 ValidationRules 集合中的每条规则。 我们会在“验证过程”一节中对此深入讨论。

提供视觉反馈

如果用户输入的值无效,你可能希望在应用 UI 上提供一些有关错误的反馈。 提供此类反馈的一种方法是将 Validation.ErrorTemplate 附加属性设置为自定义 ControlTemplate。 如前面部分所示,StartDateEntryForm TextBox 使用名为 validationTemplate 的 ErrorTemplate。 以下示例显示了 validationTemplate 的定义。

<ControlTemplate x:Key="validationTemplate">
    <DockPanel>
        <TextBlock Foreground="Red" FontSize="20">!</TextBlock>
        <AdornedElementPlaceholder/>
    </DockPanel>
</ControlTemplate>

AdornedElementPlaceholder 元素指定应放置待装饰控件的位置。

此外,还可以使用 ToolTip 来显示错误消息。 StartDateEntryForm 和 StartPriceEntryFormTextBox 都使用样式 textStyleTextBox,该样式创建显示错误消息的 ToolTip。 以下示例显示了 textStyleTextBox 的定义。 如果绑定元素属性上的一个或多个绑定出错,则附加属性 Validation.HasError 为 true

<Style x:Key="textStyleTextBox" TargetType="TextBox">
    <Setter Property="Foreground" Value="#333333" />
    <Setter Property="MaxLength" Value="40" />
    <Setter Property="Width" Value="392" />
    <Style.Triggers>
        <Trigger Property="Validation.HasError" Value="true">
            <Setter Property="ToolTip" 
                    Value="{Binding (Validation.Errors).CurrentItem.ErrorContent, RelativeSource={RelativeSource Self}}" />
        </Trigger>
    </Style.Triggers>
</Style>

使用自定义 ErrorTemplate 和 ToolTip 时,StartDateEntryForm TextBox 在发生验证错误时如下所示。

日期的数据绑定验证错误

如果 Binding 具有关联的验证规则,但未在绑定控件上指定 ErrorTemplate,则发生验证错误时,将使用默认的 ErrorTemplate 通知用户。 默认的 ErrorTemplate 是一个控件模板,它在装饰层中定义红色边框。 使用默认的 ErrorTemplate 和 ToolTip 时,StartPriceEntryForm TextBox 的 UI 在发生验证错误时如下所示。

价格的数据绑定验证错误

有关如何提供逻辑以验证对话框中所有控件的示例,请参阅对话框概述中的“自定义对话框”部分。

验证过程

通常,在目标的值传输到绑定源属性时会进行验证。 此传输在 TwoWay 和 OneWayToSource 绑定上发生。 重申一下,导致源更新的因素取决于 UpdateSourceTrigger 属性的值,如触发源更新的因素部分所述。

以下各项描述了 验证 过程。 只要验证过程中发生验证错误或其他类型的错误,该过程就会中断:

  1. 绑定引擎检查是否为该 Binding 定义了任何将 ValidationStep 设置为 RawProposedValue 的自定义 ValidationRule 对象,在这种情况下,绑定引擎将对每个 ValidationRule 调用 Validate 方法,直到其中一个出错或直到全部通过。

  2. 绑定引擎随后会调用转换器(如果存在)。

  3. 如果转换器成功,则绑定引擎会检查是否为该 Binding 定义了任何将 ValidationStep 设置为 ConvertedProposedValue 的自定义 ValidationRule 对象,在这种情况下,绑定引擎将对每个 ValidationRule(将 ValidationStep 设置为 ConvertedProposedValue)调用 Validate 方法,直到其中一个出错或直到全部通过。

  4. 绑定引擎设置源属性。

  5. 绑定引擎检查是否为该 Binding 定义了任何将 ValidationStep 设置为 UpdatedValue 的自定义 ValidationRule 对象,在这种情况下,绑定引擎将对每个 ValidationRule(将 ValidationStep 设置为 UpdatedValue)调用 Validate 方法,直到其中一个出错或直到全部通过。 如果 DataErrorValidationRule 与绑定关联并且其 ValidationStep 设置为默认的 UpdatedValue,则此时将检查 DataErrorValidationRule。 此时检查将 ValidatesOnDataErrors 设置为 true 的所有绑定。

  6. 绑定引擎检查是否为该 Binding 定义了任何将 ValidationStep 设置为 CommittedValue 的自定义 ValidationRule 对象,在这种情况下,绑定引擎将对每个 ValidationRule(将 ValidationStep 设置为 CommittedValue)调用 Validate 方法,直到其中一个出错或直到全部通过。

如果 ValidationRule 在整个过程中的任何时间都没有通过,则绑定引擎会创建 ValidationError 对象并将其添加到绑定元素的 Validation.Errors 集合中。 绑定引擎在任何给定步骤运行 ValidationRule 对象之前,它会删除在执行该步骤期间添加到绑定元素的 Validation.Errors 附加属性的所有 ValidationError。 例如,如果将 ValidationStep 设置为 UpdatedValue 的 ValidationRule 失败,则下次执行验证过程时,绑定引擎会在调用将 ValidationStep 设置为 UpdatedValue 的任何 ValidationRule 之前删除 ValidationError

如果 Validation.Errors 不为空,则元素的 Validation.HasError 附加属性设置为 true。 此外,如果 Binding 的 NotifyOnValidationError 属性设置为 true,则绑定引擎将在元素上引发 Validation.Error 附加事件。

另请注意,任何方向(目标到源或源到目标)的有效值传输操作都会清除 Validation.Errors 附加属性。

如果绑定具有关联的 ExceptionValidationRule,或将 ValidatesOnExceptions 属性设置为 true,并且在绑定引擎设置源时引发异常,则绑定引擎将检查是否存在 UpdateSourceExceptionFilter。 可以使用 UpdateSourceExceptionFilter 回叫来提供用于处理异常的自定义处理程序。 如果未在 Binding 上指定 UpdateSourceExceptionFilter,则绑定引擎会创建具有异常的 ValidationError 并将其添加到绑定元素的 Validation.Errors 集合中。

如何:实现绑定验证 - WPF .NET Framework | Microsoft Docs

ValidationRule 类 (System.Windows.Controls) | Microsoft Docs

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值