WPF Validation

Data Validation (数据验证)

大部分应用程序获取用户输入需要验证逻辑去确保用户输入期望信息。验证能够基于类型、范围、格式或其他应用程序指定要求进行验证检查。

Binding 配置验证规则

ExceptionValidationRule 目标类型转换异常验证

使用ExceptionValidationRule规则验证输入的内容转换到目标类型是否正确。

<TextBox Height="40" Width="300" VerticalContentAlignment="Center">
    <TextBox.Text>
        <Binding Path="Number" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <ExceptionValidationRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

以上的Binding能够进行简化,例子如下:

<TextBox Height="40" Width="300" VerticalContentAlignment="Center">
    <TextBox.Text>
        <Binding Path="Number" UpdateSourceTrigger="PropertyChanged" ValidatesOnExceptions="True" />
    </TextBox.Text>
</TextBox>

TIP: 验证规则一般只有从Target to source 才会进行验证。

DataErrorValidation 数据错误验证

DataErrorValidation 数据验证,针对实现IDataErrorInfo接口的绑定源对象, 当Target传递输入到Source的时候,将触发IDataErrorInfo索引器触发验证数据项逻辑, Binding系统将验证的结果保存到(Validation.Errors)[0].ErrorContent字段中。

public string this[string columnName]
{
    get
    {
        if(columnName == nameof(Number))
        {
            if (Number == 0) return "Number is zero";
            return $"Number is {Number}";
        }
        if(columnName == nameof(Email))
        {
            if (string.IsNullOrEmpty(Email))
            {
                return "Email is emtpty.";
            }
            else
            {
                return (RegexUtility.IsEmail(Email) ? "Valid Email." : "Invalid Email.");
            }
        }

        return "";
    }
}
public string Error => throw new NotImplementedException();

自定义验证规则

根据上面两节中为WPF系统内置的验证规则,在生产环境中需要用到各种各样的数据验证,自定义验证规则的需求自然就来了. 实现一个自定义验证规则只需要重写一个Validation方法就可以了。Validation方法根据输入的内容,能够支持验证格式、类型,如果验证结果失败,可以返回一个错误信息给UI层,WPF会将验证后的错误信息保存到(Validation.Errors)[0].ErrorContent中。

一个验证DateTime格式的例子:

public class DateTimeValidationRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
       if (DateTime.TryParse(value.ToString(), out DateTime dateTime))
        {
            return ValidationResult.ValidResult;
        }
        else
        {
            // Date is not a valid date, fail
            return new ValidationResult(false, "Value is not a valid date.");
        }
    }
}

以及如何使用验证规则:

<TextBox x:Name="tbDateTime" Height="40" Width="300" VerticalContentAlignment="Center"
         ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}">
    <TextBox.Text>
        <Binding Path="DateTime" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <local:DateTimeValidationRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

定制验证错误显示模板

默认情况下,错误模板(ErrorTemplate)会用一个红色的边框框住输入对象TextBox,看上去会不太好看。

在这里插入图片描述

定义一个验证模板:

<ControlTemplate x:Key="validationTemplate">
    <Border BorderThickness="1" 
            BorderBrush="Blue"  
            UseLayoutRounding="True">
        <DockPanel>
            <TextBlock DockPanel.Dock="Right" 
                       Foreground="Red" 
                       FontSize="20" 
                       VerticalAlignment="Center"
                       Text="!" />

            <!--Hold住元素的装饰器-->
            <AdornedElementPlaceholder/>
        </DockPanel>
    </Border>
</ControlTemplate>

使用验证错误模板:

<TextBox Validation.ErrorTemplate="{StaticResource validationTemplate}"
         ToolTip="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}">
    <TextBox.Text>
        <Binding Path="DateTime" UpdateSourceTrigger="PropertyChanged">
            <Binding.ValidationRules>
                <local:DateTimeValidationRule />
            </Binding.ValidationRules>
        </Binding>
    </TextBox.Text>
</TextBox>

在这里插入图片描述

验证过程(Validation process)

验证通常发生在目标值被传输到Binding Source属性的时候。传输会在Binding为TwoWayOneWayToSource中发送。 对于接收操作,什么会引起一个目标源更新依赖于UpdateSourceTrigger属性。

以下步骤描述验证的过程,如果有一个验证错误或者其他类型错误在以下任何步骤发生,过程将会被终止。

  1. 绑定引擎检查Binding中是否有任何ValidationStep属性是RawProposedValueValidationRule的验证规则。在此例子中将依次调用每一个ValidationRuleValidate方法是否有发生错误或者全部通过。

  2. 如果存在Converter,绑定引擎将调用转换器。

  3. 如果转换器转换成功,绑定引擎检查Binding中所有 ValidationStep属性为ConvertedProposedValue的验证规则。在此例子中将依次调用每一个ValidationRuleValidate方法是否有发生错误或者全部通过。

  4. 绑定引擎将设置Source属性

  5. 绑定引擎检查Binding中是否有任何ValidationStep属性是UpdateValueValidationRule的验证规则。在此例子中将依次调用每一个ValidationRuleValidate方法是否有发生错误或者全部通过。如果在一个绑定中设置了一个DataErroValidationRule并且ValidationStep默认为UpdateValue的值时,数据错误验证规则将会进行检查。

  6. 绑定引擎检查Binding中是否有任何ValidationStep属性是CommittedValueValidationRule的验证规则。在此例子中将依次调用每一个ValidationRuleValidate方法是否有发生错误或者全部通过。

如果在验证过程中有一个ValidationRule没有通过,则绑定引擎会创建一个ValidationError对象并添加到绑定元素的Validation.Errors集合中。 在绑定引擎运行任何ValidationRule之前,它将移除目标元素所有添加在Validation.Errors附加属性的集合中的所有ValidationError。对于例子,如果任何ValidationStepUpdateValue验证失败,在下一次验证过程中,如果任何ValidationStepUpdateValue执行,绑定引擎将立即移除目标元素的ValidationError

如果Validation.Errors不为空,则目标元素的属性Validation.HasError将设置为True,并且,如果绑定的NotifyOnValidationError属性设置为True,则绑定引擎将触发目标元素的路由事件Validation.Error

并且注意当一个有效的值传送到目标或者目标传送到源,都将清除Validation.HasError附加属性。

如果绑定中设置了ExceptionValidationRule验证规则或者设置VlidationsOnException属性为True,并且当目标值设置到源中发送了一个异常时,绑定引擎将检查是否存在UpdateSourceExceptionFilter异常过滤器并过滤,如果你设置了UpdateSourceExceptionFilter回调。 如果绑定没有指定UpdateSourceExceptionFilter,则绑定引擎将创建一个ValidationError并附带一个Exception添加到目标元素的Validation.Errors集合中。

调试机制(Debugging mechanism)

你可以设置一个目标相关元素的PresentationTraceSources.TraceLevel属性,让其能够接收有关于特定绑定的相关状态。

实践提示

当验证错误时设置明显标记

当在验证错误时,需要对一个验证错误显示的模板,并将错误同步到目标元素的Tooltip 供用户了解错误内容。

ViewModel输入验证,实现IDataErrorInfo

public string this[string columnName]
{
    get
    {
        if(columnName == nameof(Number))
        {
            if (Number == 0) return "Number is zero";
            return $"Number is {Number}";
        }
        if(columnName == nameof(Email))
        {
            if (string.IsNullOrEmpty(Email))
            {
                return "Email is emtpty.";
            }
            else
            {
                return (RegexUtility.IsEmail(Email) ? "Valid Email." : "Invalid Email.");
            }
        }

        return null;
    }
}
public string Error => null; // 这里只有在使用BindingGroup在有用

在Code-behind中进行数据验证

using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;

namespace SDKSample
{
    public partial class MarginsDialogBox : Window
    {
        void okButton_Click(object sender, RoutedEventArgs e)
        {
            // Don't accept the dialog box if there is invalid data
            if (!IsValid(this)) return;
        }

        // Validate all dependency objects in a window
        bool IsValid(DependencyObject node)
        {
            // Check if dependency object was passed
            if (node != null)
            {
                // Check if dependency object is valid.
                // NOTE: Validation.GetHasError works for controls that have validation rules attached
                bool isValid = !Validation.GetHasError(node);
                if (!isValid)
                {
                    // If the dependency object is invalid, and it can receive the focus,
                    // set the focus
                    if (node is IInputElement) Keyboard.Focus((IInputElement)node);
                    return false;
                }
            }

            // If this dependency object is valid, check all child dependency objects
            foreach (object subnode in LogicalTreeHelper.GetChildren(node))
            {
                if (subnode is DependencyObject)
                {
                    // If a child dependency object is invalid, return false immediately,
                    // otherwise keep checking
                    if (IsValid((DependencyObject)subnode) == false) return false;
                }
            }

            // All dependency objects are valid
            return true;
        }
    }
}

参考文献

Data binding overview - WPF .NET Framework | Microsoft Docs

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值