王彦为

聚沙成塔
  1. 首页
  2. WPF
  3. 正文

WPF的TextBox输入验证之IDataErrorInfo验证

2016-12-26 5342点热度 3人点赞 1条评论

在介绍IDataErrorInfo验证之前,我们先看一段视频。

视频中我们可以看到,用很少的代码量就完成了数据验证的功能,并且在XAML代码中只加了一条语“ValidatesOnDataErrors=True”,后端也仅仅是在属性加入了一组自定义特性。之所以能够使用简洁的代码实现验证功能,这得益于IDataErrorInfo、Attribute以及Reflection的应用。

实现方式

第一步:创建自定义特性,该特性用于验证属性的值是否满足要求。该类主要有一个IsValid方法和ErrorMessage属性,IsValid方法是用来判断该属性值是否满足要求,ErrorMessage是如果不满足要求情况下,返回的错误信息。

public abstract class AttributeBase : Attribute
{
    /// <summary>
    ///  验证失败时给出的消息
    /// </summary>
    public string ErrorMessage { get; protected set; }

    /// <summary>
    /// 是否满足验证规则,如果满足返回true
    /// </summary>
    /// <param name="obj"></param>
    /// <returns></returns>
    public virtual bool IsValid(object obj)
    {
        return true;
    }
}

public class NumberRuleAttribute : AttributeBase
{
    public NumberRuleAttribute(double minValue, double maxValue, string errorMessage = null)
    {
        MinValue = minValue;
        MaxValue = maxValue;
        if (string.IsNullOrEmpty(errorMessage))
        {
            var sb = new StringBuilder(1024);
            sb.Append("The value should between ");
            sb.Append(minValue);
            sb.Append(" and ");
            sb.Append(maxValue);
            ErrorMessage = sb.ToString();
        }
        else
        {
            ErrorMessage = errorMessage;
        }

    }
    private double MinValue { get; }
    private double MaxValue { get; }

    public override bool IsValid(object obj)
    {
        if (obj == null)
        {
            return false;
        }

        // 如果输入的是非数值
        if (!double.TryParse(obj.ToString(), out var value))
        {
            return false;
        }
        // 如果不满足最大值和最小值限制
        if (value > MaxValue || value < MinValue)
            return false;
        return true;
    }
}

第二步:创建实体类基类,该类继承并实现IDataErrorInfo接口,主要属性string this[string propertyName]的实现方法可以参考如下代码。在使用反射获取该类的属性时,会获取到Error和Item属性,该属性是父类的属性,不需要进行判断。在遍历子类属性时,如果存在自定义特性,则按照自定义特性的验证方法进行验证。该基类中,我们添加了一个函数IsValidated(),该函数的功能是验证该类及子类中所有的属性是否满足要求,这个是用于页面提交时使用的函数。该该函数与string this[string propertyName]的区别是:string this[string propertyName]只验证特定属性是否满足要求,UI显示用的;IsValidated()方法用于判断所有属性是否满足要求,后台commit使用的。

class ViewModeBase : INotifyPropertyChanged, IDataErrorInfo
{
    private string error;
    public event PropertyChangedEventHandler PropertyChanged;
    protected void InvokePropertyChanged(string property)
    {
        if (this.PropertyChanged != null)
        {
            this.PropertyChanged.Invoke(this, new PropertyChangedEventArgs(property));
        }
    }
    public string Error
    {
        get
        {
            return error;
        }
    }
    public string this[string propertyName]
    {
        get
        {
            var property = GetType().GetProperty(propertyName);
            if (property != null)
            {
                var value = property.GetValue(this, null);
                var attributes = property.GetCustomAttributes(false);
                foreach (var attribute in attributes)
                {
                    if (attribute is NumberRuleAttribute numberRule)
                    {
                        if (!numberRule.IsValid(value))
                        {
                            error = numberRule.ErrorMessage;
                            return error;
                        }
                    }
                    else if (attribute is StringRuleAttribute stringRule)
                    {
                        if (!stringRule.IsValid(value))
                        {
                            error = stringRule.ErrorMessage;
                            return error;
                        }
                    }
                }
            }
            return null;
        }
    }

    public bool IsValidated()
    {

        // 通过反射获取所有带自定义特性的属性,判断属性值是否满足要求
        var properties = GetType().GetProperties(BindingFlags.Public| BindingFlags.Instance);
        foreach (var property in properties)
        {
            // 属性Error和Item来自接口IDataErrorInfo,无需进行验证
            if (property.Name == "Error" || property.Name == "Item")
            {
                continue;
            }
            var value = property.GetValue(this, null);
            var attributes = property.GetCustomAttributes(false);
            foreach (var attribute in attributes)
            {
                if (attribute is NumberRuleAttribute numberRule)
                {
                    if (!numberRule.IsValid(value))
                    {
                        error = numberRule.ErrorMessage;
                        return false;
                    }
                }
                else if (attribute is StringRuleAttribute stringRule)
                {
                    if (!stringRule.IsValid(value))
                    {
                        error = stringRule.ErrorMessage;
                        return false;
                    }
                }
            }
        }
        return true;
    }
}

第三步:创建实体类子类。该类就比较简单,只需要在属性上加一个特性就行,特性名称可以省略Attribute,例如NumberRule和NumberRuleAttribute是等效的,特性是可以叠加的。

class Student : ViewModeBase
{
    private string name;
    private double age;
    private string telephone;

    [StringRule(2, 5)]
    public string Name
    {
        get { return name; }
        set
        {
            name = value;
            InvokePropertyChanged("Name");
        }
    }
    [NumberRule(0,150)]
    public double Age
    {
        get { return age; }
        set
        {
            age = value;
            InvokePropertyChanged("Age");
        }
    }

    [StringRule(11, 11,"手机号必须是11位数")]
    public string Telephone
    {
        get { return telephone; }
        set
        {
            if (telephone != value)
            {
                telephone = value;
                InvokePropertyChanged("Telephone");
            }
        }
    }
}

第四步:修改TextBox的模板,具体代码可参考《WPF的TextBox输入验证之Exception验证》或者下载源代码,关键代码如下。

<Setter Property="Validation.ErrorTemplate">
    <Setter.Value>
        <ControlTemplate>
            <StackPanel Orientation="Horizontal" >
                <Border  >
                    <Grid>
                        <AdornedElementPlaceholder x:Name="adorner"  />
                    </Grid>
                </Border>
                <Grid Width="10"/>
                <Popup Name="popup" AllowsTransparency="True" Placement="Right">
                    <Border x:Name="errorBorder" Background="#ffdc000c" Opacity="0" MinHeight="30" >
                        <TextBlock Margin="5,0" Text="{Binding ElementName=adorner, 
                                Path=AdornedElement.(Validation.Errors).CurrentItem.ErrorContent}"
                                Foreground="White" TextWrapping="Wrap" VerticalAlignment="Center"/>
                    </Border>
                </Popup>
            </StackPanel>
        </ControlTemplate>
    </Setter.Value>
</Setter>

第五步:创建View。核心代码是“ValidatesOnDataErrors=True”

// 前台绑定
TextBox Text="{Binding Path=Age,UpdateSourceTrigger=PropertyChanged,
ValidatesOnDataErrors=True}" />

// 后台提交验证
private void btnSubmit_Click(object sender, RoutedEventArgs e)
{
    if (stu.IsValidated())
    {
        MessageBox.Show("Verificate successfully.");
    }
    else
    {
        MessageBox.Show("Please input correct data in red textbox", "Warning", MessageBoxButton.OK, MessageBoxImage.Warning);
    }
}

总结

通过以上5个步骤,使用IDataErrorInfo来验证对象的数据有效性,对于无效的属性及时通知UI界面。其中用到了反射技术和特性,节约了大量的代码量,但是会有性能损失。至此WPF的TextBox输入验证系列的文章已经完工,谢谢大家的阅读。

源码下载

立即下载
标签: 暂无
最后更新:2022-03-31

王彦为

新生代农民工,十年医疗器械行业从业经验,现居苏州。爱生活,爱做梦。

打赏 点赞
< 上一篇

文章评论

  • WST

    很强

    2022-01-14
    回复
  • 取消回复

    COPYRIGHT © 2022 王彦为. ALL RIGHTS RESERVED.

    苏ICP备16063331号-1

    苏公网安备32050702011313号