image_thumb9

使用 WPF 数据绑定模型可以将 ValidationRulesBinding 对象相关联。验证在从绑定目标到绑定源的值传输过程中调用转换器之前发生。下面描述了验证过程:

  1. 在将值从目标属性传输到源属性时,数据绑定引擎首先移除可能已添加到所绑定元素的 Validation.Errors 附加属性的任何 ValidationError。然后,数据绑定引擎检查是否为该 Binding 定义了自定义 ValidationRule;如果定义了自定义验证规则,那么它将调用每个 ValidationRule 上的 Validate 方法,直到其中一个规则出错或者全部规则都通过为止。

  2. 如果某个自定义规则未通过,则绑定引擎会创建一个 ValidationError 对象,并将该对象添加到绑定元素的 Validation.Errors 集合。如果 Validation.Errors 不为空,则元素的 Validation.HasError 附加属性被设置为 true。此外,如果 BindingNotifyOnValidationError 属性设置为 true,则绑定引擎将引发该元素上的 Validation.Error 附加事件。

  3. 如果所有规则都通过,则绑定引擎会调用转换器(如果存在的话)。

  4. 如果转换器通过,则绑定引擎会调用源属性的 setter。

  5. 如果绑定具有与其关联的 ExceptionValidationRule,并且在步骤 4 中引发异常,则绑定引擎将检查是否存在 UpdateSourceExceptionFilter。您可以选择使用 UpdateSourceExceptionFilter 回调来提供用于处理异常的自定义处理程序。如果未对 Binding 指定 UpdateSourceExceptionFilter,则绑定引擎将对异常创建 ValidationError 并将其添加到绑定元素的 Validation.Errors 集合中。

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

.net提供了三种ValidationRule:
1. CustomerValidationRule 用户提供的Rule
2. ExceptionValidationRule 更新源时,如果有异常(比如类型不匹配)或不满足条件,会向UI上报异常
3. DataErrorValidationRule 更新源时,如果值不满足条件(无法判断值类型异常),会向UI上报异常。要求对象继承自IDataErrorInfo,并且异常是在该对象中抛出的,下面会有示例说明


下面的示例来自msdn,显示了Binding Validation的使用:

image_thumb11

图1
 image_thumb13
图2

图中第一个TextBox使用了自定义的ErrorTemplate,在TextBox左边显示一个红色的叹号
第二个TextBox使用的是系统默认的ErrorTemplate,把TextBox边框用红色表示
第三个TextBox使用的是ExceptionValidationRule,这个类在目标更新源时,发生异常(这里是输入是string,而源需要的是int,而引起的异常)时,把异常抛到UI,如果Binding有UpdateSourceExceptionFilter,会调用该方法(使用户可以更人性化的显示异常或做些自己的事),结果如图2。这个步骤解释的是上面第5点。
如果更新源时出现异常,而没有使用ExceptionValidationRule,那么即使输入的不符合条件,界面上也不会有任何提示。
另一种在UI上显示更新源异常的方案是使用Binding.ValidatesOnExceptions属性,如果设置为True,那么也会显示异常,默认是False。ValidatesOnExceptions和ExceptionValidationRule作用是相同的,是.net3.5中新增的一属性。如果两者同时存在,ValidatesOnExceptions为False,也会显示异常的。
<Window
  xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
  xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  xmlns:c="clr-namespace:SDKSample"
  x:Class="SDKSample.Window1"
  Title="Binding Validation Sample"
  SizeToContent="WidthAndHeight"
  ResizeMode="NoResize">
    <Window.Resources>
      <c:MyDataSource x:Key="ods"/>
      <ControlTemplate x:Key="validationTemplate">
        <DockPanel>
          <TextBlock Foreground="Red" FontSize="20">!</TextBlock>
          <AdornedElementPlaceholder/>
        </DockPanel>
      </ControlTemplate>


      <Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
        <Style.Triggers>
          <Trigger Property="Validation.HasError" Value="true">
            <Setter Property="ToolTip"
              Value=
"{Binding RelativeSource={x:Static RelativeSource.Self},
                              Path=(Validation.Errors)[0].ErrorContent}"
/>
          </Trigger>
        </Style.Triggers>
      </Style>
    </Window.Resources>

  <Grid>
    <Grid.ColumnDefinitions>
      <ColumnDefinition />
      <ColumnDefinition />
      <ColumnDefinition />
    </Grid.ColumnDefinitions>
    <Grid.RowDefinitions>
      <RowDefinition />
      <RowDefinition />
      <RowDefinition />
      <RowDefinition/>
      <RowDefinition />
    </Grid.RowDefinitions>

    <TextBlock
      Grid.Row="0" Grid.ColumnSpan="2"
      FontSize="20" Margin="8"
      Text="Enter a number between 21-130 or there will be a validation error:"/>

    <Label Grid.Column="0" Grid.Row="1" FontSize="15" Margin="2"
           Target="{Binding ElementName=textBox1}">TextBox with _custom ErrorTemplate and ToolTip:</Label>
    <TextBox Name="textBox1" Width="50" FontSize="15"
             Validation.ErrorTemplate="{StaticResource validationTemplate}"
             Style="{StaticResource textBoxInError}"
             Grid.Row="1" Grid.Column="1" Margin="2">
      <TextBox.Text>
        <Binding Path="Age" Source="{StaticResource ods}"
                 UpdateSourceTrigger="PropertyChanged" >
          <Binding.ValidationRules>
            <c:AgeRangeRule Min="21" Max="130"/>
          </Binding.ValidationRules>
        </Binding>
      </TextBox.Text>
    </TextBox>

    <Label Grid.Row="2" Grid.Column="0" FontSize="15" Margin="2"
       Target="{Binding ElementName=textBox2}">TextBox with _default ErrorTemplate:</Label>
    <TextBox Name="textBox2" Width="50" FontSize="15"
             Grid.Row="2" Grid.Column="1" Margin="2">
      <TextBox.Text>
        <Binding Path="Age2" Source="{StaticResource ods}"
                 UpdateSourceTrigger="PropertyChanged" >
          <Binding.ValidationRules>
            <c:AgeRangeRule Min="21" Max="130"/>
          </Binding.ValidationRules>
        </Binding>
      </TextBox.Text>
    </TextBox>

    <TextBlock Grid.Row="3" Grid.ColumnSpan="3" FontSize="20" Margin="8"
               Text="The following TextBox uses the ExceptionValidationRule and UpdateSourceExceptionFilter handler:"/>
    <Label Grid.Row="4" Grid.Column="0" FontSize="15" Margin="2"
   Target="{Binding ElementName=textBox3}">TextBox with UpdateSourceExceptionFilter _handler:</Label>
    <TextBox Name="textBox3" Width="50" FontSize="15"
             Grid.Row="4" Grid.Column="1" Margin="2"
            Validation.ErrorTemplate="{StaticResource validationTemplate}"
             Style="{StaticResource textBoxInError}">
      <TextBox.Text>
        <Binding Path="Age3" Source="{StaticResource ods}"
                 UpdateSourceTrigger="PropertyChanged">
          <Binding.ValidationRules>
            <ExceptionValidationRule/>
          </Binding.ValidationRules>
        </Binding>
      </TextBox.Text>
    </TextBox>
    <CheckBox Name="cb" FontSize="15" HorizontalAlignment="Left"
         Grid.Row="4" Grid.Column="2" Margin="5"
         Checked="UseCustomHandler" Unchecked="DisableCustomHandler">Enable Custom Handler (see ToolTip)</CheckBox>
  </Grid>
</Window>

    public class AgeRangeRule : ValidationRule
    {
        private int _min;
        private int _max;

        public AgeRangeRule()
        {
        }

        public int Min
        {
            get { return _min; }
            set { _min = value; }
        }

        public int Max
        {
            get { return _max; }
            set { _max = value; }
        }

        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            int age = 0;

            try
            {
                if (((string)value).Length > 0)
                    age = Int32.Parse((String)value);
            }
            catch (Exception e)
            {
                return new ValidationResult(false, "Illegal characters or " + e.Message);
            }

            if ((age < Min) || (age > Max))
            {
                return new ValidationResult(false,
                  "Please enter an age in the range: " + Min + " - " + Max + ".");
            }
            else
            {
                return new ValidationResult(true, null);
            }
        }
    }


 

  public partial class Window1 : Window
  {
      public Window1()
      {
          InitializeComponent();
      }

      void UseCustomHandler(object sender, RoutedEventArgs e)
      {

          BindingExpression myBindingExpression = textBox3.GetBindingExpression(TextBox.TextProperty);
          Binding myBinding = myBindingExpression.ParentBinding;
          myBinding.UpdateSourceExceptionFilter = new UpdateSourceExceptionFilterCallback(ReturnExceptionHandler);
          myBindingExpression.UpdateSource();//加入UpdateSourceExceptionFilter后,更新源,以触发异常
      }

      void DisableCustomHandler(object sender, RoutedEventArgs e)
      {
          // textBox3 is an instance of a TextBox
          // the TextProperty is the data-bound dependency property
          Binding myBinding = BindingOperations.GetBinding(textBox3, TextBox.TextProperty);
          myBinding.UpdateSourceExceptionFilter -= new UpdateSourceExceptionFilterCallback(ReturnExceptionHandler);
          BindingOperations.GetBindingExpression(textBox3, TextBox.TextProperty).UpdateSource();
      }

      object ReturnExceptionHandler(object bindingExpression, Exception exception)
      {
          return "This is from the UpdateSourceExceptionFilterCallBack.";
      }
  }

 

    public class MyDataSource
    {
        private int _age;
        private int _age2;
        private int _age3;

        public MyDataSource()
        {
            Age = 0;
            Age2 = 0;
        }

        public int Age
        {
            get { return _age; }
            set { _age = value; }
        }
        public int Age2
        {
            get { return _age2; }
            set { _age2 = value; }
        }

        public int Age3
        {
            get { return _age3; }
            set { _age3 = value; }
        }
    }


ValidationRule.ValidationStep用来设置rule是什么时候被调用:
RawProposedValue            在进行任何转换之前运行 ValidationRule

ConvertedProposedValue    在转换了值后运行 ValidationRule

UpdatedValue                  在更新了源后运行 ValidationRule

CommittedValue               在将值提交到源后运行 ValidationRule

默认值是RawProposedValue。

DataErrorValidationRule 的示例如下:

<Binding Path="Age" Source="{StaticResource data}"
                         ValidatesOnExceptions="True"
                         UpdateSourceTrigger="PropertyChanged">
     <Binding.ValidationRules>
     <!--DataErrorValidationRule checks for validation 
      errors raised by the IDataErrorInfo object.-->
     <!--Alternatively, you can set ValidationOnDataErrors="True" on the Binding.—>
           <DataErrorValidationRule/>
     </Binding.ValidationRules>
</Binding>



    public class Person : IDataErrorInfo
    {
        private int age;

        public int Age
        {
            get { return age; }
            set { age = value; }
        }

        public string Error
        {
            get
            {
                return null;
            }
        }

        public string this[string name]
        {
            get
            {
                string result = null;

                if (name == "Age")
                {
                    if (this.age < 0 || this.age > 150)
                    {
                        result = "Age must not be less than 0 or greater than 150.";
                    }
                }
                return result;
            }
        }
    }
和ExceptionValidationRule与ValidatesOnExceptions这对一样,DataErrorValidationRule 也有一个叫ValidatesOnDataErrors的属性与之配对。
 
关于validation,zlgcool的这篇文章可作参考
下面的文章会介绍使用BindingGroup对整个对象或表单的验证。