关于WPF输入框数据验证的解决方案

文章目录

  • 前言
  • 一、ValidationAttribute
  • 二、ValidationRule
  • 三、FluentValidation
  • 总结


前言

输入框验证的方式有很多种,这里主要讲述我用到的几种方式。


一、ValidationAttribute


MVVM基类:

public abstract class BindableBase : INotifyPropertyChanged
{
     public event PropertyChangedEventHandler PropertyChanged;

     public virtual void RaisePropertyChanged([CallerMemberName] string propertyName = null)
     {
         PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
     }
 }

实体基类:

internal class EntityBase : BindableBase
{
    private ValidationObjectErrors _errors;
    public ValidationObjectErrors Errors
    {
        get => _errors;
        set { _errors = value; RaisePropertyChanged(); }
    }

    public EntityBase()
    {

    }

    public override void RaisePropertyChanged([CallerMemberName] string propertyName = null)
    {
        base.RaisePropertyChanged(propertyName);
        _errors = new ValidationObjectErrors(this);//触发校验
        base.RaisePropertyChanged(nameof(Errors));
    }
}

错误集ValidationObjectErrors:

public class ValidationObjectErrors : IReadOnlyList<string>
{
    private List<string> _messages = new List<string>();

    private List<ValidationResult> _results = new List<ValidationResult>();

    internal ValidationObjectErrors(EntityBase entityBase)
    {
        ValidationContext context = new ValidationContext(entityBase);
        Validator.TryValidateObject(entityBase, context, _results, true);//校验
        foreach (ValidationResult result in _results)
        {
            _messages.Add(result.ErrorMessage);
        }
    }

    public int Count => _messages.Count;

    public string this[int index] => _messages[index];

    public string this[string propertyName]
    {
        get
        {
            foreach (ValidationResult result in _results)
            {
                if (result.MemberNames.Contains(propertyName))
                {
                    return result.ErrorMessage;
                }
            }
            return null;
        }
    }

    public IEnumerator<string> GetEnumerator()
    {
        return _messages.GetEnumerator();
    }

    IEnumerator IEnumerable.GetEnumerator()
    {
        return GetEnumerator();
    }
}

实体类:

internal class PersonInfo : EntityBase
{
    private string _no;
    [StringLength(20, MinimumLength = 6, ErrorMessage = "最少 6 位,最长 20 位")]
    public string No
    {
        get => _no;
        set { _no = value; RaisePropertyChanged(); }
    }

    private string _name;
    //[String(IsAllowedNull = false, MaxLenth = 10)]//自定义的
    public string Name
    {
        get => _name;
        set { _name = value; RaisePropertyChanged(); }
    }

    private int _age;
    [Range(10, 100, ErrorMessage = "10岁-100岁")]
    public int Age
    {
        get => _age;
        set { _age = value; RaisePropertyChanged(); }
    }
    public string Height { get; set; }
    public string Weight { get; set; }
}

MainWindowViewModel:

internal class MainWindowViewModel
{
     public MainWindowViewModel()
     {
         PersonInfo = new PersonInfo();
     }

     public PersonInfo PersonInfo { get; set; }
 }

界面MainWindow:
可直接绑定属性种的errors集,显示错误提示

<Window.DataContext>
 <local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
    <Style x:Key="TextBlockStyle" TargetType="TextBlock">
        <Style.Setters>
            <Setter Property="Width" Value="30"/>
            <Setter Property="Height" Value="Auto"/>
            <Setter Property="Margin" Value="0,0,5,0"/>
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="HorizontalAlignment" Value="Center"/>
        </Style.Setters>
    </Style>

    <Style x:Key="TextBoxStyle" TargetType="TextBox">
        <Style.Setters>
            <Setter Property="Width" Value="150"/>
            <Setter Property="Height" Value="30"/>
            <Setter Property="VerticalContentAlignment" Value="Center"/>
        </Style.Setters>
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="True">
                <Setter Property="BorderBrush"  Value="Red"/>
            </Trigger>
        </Style.Triggers>
    </Style>
</Window.Resources>
<Grid>
    <Border Width="250" Height="350" BorderThickness="2" BorderBrush="Black">
        <StackPanel Margin="10">
            <StackPanel Orientation="Horizontal" Margin="5">
                <TextBlock Text="编号:" Style="{StaticResource TextBlockStyle}"/>
                <TextBox Style="{StaticResource TextBoxStyle}" Text="{Binding PersonInfo.No, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
            </StackPanel>
            <TextBlock Text="{Binding Path =PersonInfo.Errors[No]}" Foreground="Red"/>
            <StackPanel Orientation="Horizontal" Margin="5">
                <TextBlock Text="姓名:" Style="{StaticResource TextBlockStyle}"/>
                <TextBox Style="{StaticResource TextBoxStyle}" Text="{Binding PersonInfo.Name, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
            </StackPanel>
            <TextBlock Text="{Binding Path =PersonInfo.Errors[Name]}" Foreground="Red"/>
            <StackPanel Orientation="Horizontal" Margin="5">
                <TextBlock Text="年龄:" Style="{StaticResource TextBlockStyle}"/>
                <TextBox Style="{StaticResource TextBoxStyle}" Text="{Binding PersonInfo.Age, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
            </StackPanel>
            <TextBlock Text="{Binding Path =PersonInfo.Errors[Age]}" Foreground="Red"/>
            <StackPanel Orientation="Horizontal" Margin="5">
                <TextBlock Text="身高:" Style="{StaticResource TextBlockStyle}"/>
                <TextBox Style="{StaticResource TextBoxStyle}" Text="{Binding PersonInfo.Height, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
            </StackPanel>
            <TextBlock Text="{Binding Path =PersonInfo.Errors[Height]}" Foreground="Red"/>
            <StackPanel Orientation="Horizontal" Margin="5">
                <TextBlock Text="体重:" Style="{StaticResource TextBlockStyle}"/>
                <TextBox Style="{StaticResource TextBoxStyle}" Text="{Binding PersonInfo.Weight, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
            </StackPanel>
            <TextBlock Text="{Binding Path =PersonInfo.Errors[Weight]}" Foreground="Red"/>
            <Button Height="30" Width="60" Content="保存"/>
        </StackPanel>
    </Border>
</Grid>

当然可以自定义约束特性:

[AttributeUsage(AttributeTargets.Property)]
public class StringAttribute : ValidationAttribute
{
    /// 
    /// 最小长度
    /// 
    public int MinLenth { get; set; }

    /// 
    /// 最大长度
    /// 
    public int MaxLenth { get; set; }

    /// 
    /// 是否允许为空
    /// 
    public bool IsAllowedNull { get; set; }

    /// 
    /// 是否允许有中文
    /// 
    public bool IsAllowedChinese { get; set; }

    /// 
    /// 是否允许有空格
    /// 
    public bool IsAllowedSpace { get; set; }

    public StringAttribute()
        : base()
    {
    }

    public override string FormatErrorMessage(string name)
    {
        return string.Format(CultureInfo.CurrentUICulture, ErrorMessageString, name, ErrorMessage);
    }

    public override bool IsValid(object value)
    {
        string valueString = value?.ToString();
        if (!string.IsNullOrEmpty(valueString))
        {
            if (valueString.Length < MinLenth || valueString.Length > MaxLenth)
            {
                ErrorMessage = "字符串长度" + MinLenth + "-" + MaxLenth;
                return false;
            }
        }

        if (!IsAllowedNull)
        {
            if (string.IsNullOrEmpty(valueString))
            {
                ErrorMessage = "不能为空!";
                return false;
            }
        }

        if (!IsAllowedChinese)
        {
            if (Regex.IsMatch(valueString, @"[\u4e00-\u9fa5]"))
            {
                ErrorMessage = "不能包含中文!";
                return false;
            }
        }

        if (!IsAllowedSpace)
        {
            if (valueString.Contains(" "))
            {
                ErrorMessage = "不能包含空格!";
                return false;
            }
        }
        return true;
    }
}

结果展示:
关于WPF输入框数据验证的解决方案_第1张图片
源代码:https://download.csdn.net/download/weixin_48083386/85406762

二、ValidationRule

这个比较简单,直接上代码:
ViewModel:

public class MainWindowViewModel : INotifyPropertyChanged
{
    private string _name;
    public string Name
    {
        get => _name;
        set
        {
            _name = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
        }
    }

    private int _age;
    public int Age
    {
        get => _age;
        set
        {
            _age = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Age)));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

界面:

<Window.DataContext>
    <local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
    <Style x:Key="TextBlockStyle" TargetType="TextBlock">
        <Style.Setters>
            <Setter Property="Width" Value="30"/>
            <Setter Property="Height" Value="Auto"/>
            <Setter Property="Margin" Value="0,0,5,0"/>
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="HorizontalAlignment" Value="Center"/>
        </Style.Setters>
    </Style>

    <Style x:Key="TextBoxStyle" TargetType="TextBox">
        <Style.Setters>
            <Setter Property="Width" Value="150"/>
            <Setter Property="Height" Value="30"/>
            <Setter Property="VerticalContentAlignment" Value="Center"/>
        </Style.Setters>
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="True">
                <Setter Property="BorderBrush"  Value="Red"/>
            </Trigger>
        </Style.Triggers>
    </Style>
</Window.Resources>
<Grid>
<Border Width="250" Height="150" BorderThickness="2" BorderBrush="Black">
    <StackPanel Margin="10">
        <TextBlock Text="{Binding Path =PersonInfo.Errors[No]}" Foreground="Red"/>
        <StackPanel Orientation="Horizontal" Margin="5">
            <TextBlock Text="姓名:" Style="{StaticResource TextBlockStyle}"/>
            <TextBox Style="{StaticResource TextBoxStyle}" Name="NameTb" ToolTip="{Binding RelativeSource={x:Static RelativeSource.Self},Path=(Validation.Errors)[0].ErrorContent}">
                <TextBox.Text>
                    <Binding Path="Name"  UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True" >
                        <Binding.ValidationRules>
                            <local:CustomValidationRule  ValidateType="Name"/>
                        </Binding.ValidationRules>
                    </Binding>
                </TextBox.Text>
            </TextBox>
        </StackPanel>
        <TextBlock Text="{Binding  ElementName=NameTb,Path=(Validation.Errors)[0].ErrorContent}" Foreground="Red"/>
        <StackPanel Orientation="Horizontal" Margin="5">
            <TextBlock Text="年龄:" Style="{StaticResource TextBlockStyle}"/>
            <TextBox Style="{StaticResource TextBoxStyle}" Name="AgeTb" ToolTip="{Binding RelativeSource={x:Static RelativeSource.Self},Path=(Validation.Errors)[0].ErrorContent}">
                <TextBox.Text>
                    <Binding Path="Age"  UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True" >
                        <Binding.ValidationRules>
                            <local:CustomValidationRule  ValidateType="Age"/>
                        </Binding.ValidationRules>
                    </Binding>
                </TextBox.Text>
            </TextBox>
        </StackPanel>
        <TextBlock Text="{Binding  ElementName=AgeTb,Path=(Validation.Errors)[0].ErrorContent}" Foreground="Red"/>
    </StackPanel>
</Border>
</Grid>

自定义规则:

class CustomValidationRule : ValidationRule
{
    public string ValidateType { get; set; }
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        try
        {
            string valueString = value?.ToString().Trim();
            if (ValidateType == "Name")//姓名
            {
                string name = valueString;
                if (!string.IsNullOrEmpty(name))
                {
                    string errorMessage = string.Empty;

                    return name.Length > 10 ? new ValidationResult(false, "名称过长,名称长度不能超过10") : new ValidationResult(true,null);
                }
            }
            else if (ValidateType == "Age")//年龄
            {
                string ageString = valueString;
                if (!string.IsNullOrEmpty(ageString))
                {
                    if (int.TryParse(ageString, out int age))
                    {
                        if (age is < 10 or > 150)
                        {
                            return new ValidationResult(false, "年龄不能小于10岁,也不能大于150岁");
                        }
                    }

                    return new ValidationResult(true, null);
                }
            }
            return new ValidationResult(true, null);
        }
        catch (Exception e)
        {
            return new ValidationResult(false, e.Message);
        }
    }
}

结果:
关于WPF输入框数据验证的解决方案_第2张图片

三、FluentValidation

其实.Net中的数据验证方法有很多,可以参考这篇:WPF数据验证

这里写一个不一样的,FluentValidation。可以直接在NuGet中下载。
首先还是View和ViewModel:

class MainWindowViewModel : INotifyPropertyChanged
{
    private string _name;
    public string Name
    {
        get => _name;
        set
        {
            _name = value;
            MainWindowValid();
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Name)));
        }
    }

    private int _age;
    public int Age
    {
        get => _age;
        set
        {
            _age = value;
            MainWindowValid();
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Age)));
        }
    }

    private string _errorMessage;
    public string ErrorMessage
    {
        get => _errorMessage;
        set
        {
            _errorMessage = value;
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(ErrorMessage)));
        }
    }
    private ValidationFailure MainWindowValid()
    {
        ValidationFailure failure = ValidatorHelper.GetFirstError<MainWindowValidator, MainWindowViewModel>(this);
        // 获取校验失败的提示以及属性名称
        if (failure != null)
        {
            ErrorMessage = failure.ErrorMessage;
        }
        return failure;
    }
    public event PropertyChangedEventHandler PropertyChanged;
}

界面:

<Window.DataContext>
    <local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
    <Style x:Key="TextBlockStyle" TargetType="TextBlock">
        <Style.Setters>
            <Setter Property="Width" Value="30"/>
            <Setter Property="Height" Value="Auto"/>
            <Setter Property="Margin" Value="0,0,5,0"/>
            <Setter Property="VerticalAlignment" Value="Center"/>
            <Setter Property="HorizontalAlignment" Value="Center"/>
        </Style.Setters>
    </Style>

    <Style x:Key="TextBoxStyle" TargetType="TextBox">
        <Style.Setters>
            <Setter Property="Width" Value="150"/>
            <Setter Property="Height" Value="30"/>
            <Setter Property="VerticalContentAlignment" Value="Center"/>
        </Style.Setters>
        <Style.Triggers>
            <Trigger Property="Validation.HasError" Value="True">
                <Setter Property="BorderBrush"  Value="Red"/>
            </Trigger>
        </Style.Triggers>
    </Style>
</Window.Resources>
<Grid>
    <Border Width="250" Height="150" BorderThickness="2" BorderBrush="Black">
        <StackPanel Margin="10">
            <TextBlock Text="{Binding Path =PersonInfo.Errors[No]}" Foreground="Red"/>
            <StackPanel Orientation="Horizontal" Margin="5">
                <TextBlock Text="姓名:" Style="{StaticResource TextBlockStyle}"/>
                <TextBox Style="{StaticResource TextBoxStyle}" Name="NameTb" Text="{Binding Name, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
            </StackPanel>
            <StackPanel Orientation="Horizontal" Margin="5">
                <TextBlock Text="年龄:" Style="{StaticResource TextBlockStyle}"/>
                <TextBox Style="{StaticResource TextBoxStyle}" Name="AgeTb" Text="{Binding Age, Mode=TwoWay,UpdateSourceTrigger=PropertyChanged}"/>
            </StackPanel>
            <TextBlock Text="{Binding ErrorMessage}" Foreground="Red"/>
        </StackPanel>
    </Border>
</Grid>

校验器:

 class MainWindowValidator : AbstractValidator<MainWindowViewModel>
{
    public MainWindowValidator()
    {
        RuleFor(x => x.Name)
            .NotNull().WithMessage("请输入姓名!")
            .MinimumLength(5).WithMessage("姓名长度不能小于5!")
            .MaximumLength(50).WithMessage("姓名长度不能大于50!");


        RuleFor(x => x.Age)
            .NotNull().WithMessage("请输入年龄!")
            .Must(IsBetween10And150).WithMessage("年龄在范围10-150岁之间!");
    }

    public bool IsBetween10And150(int age)
    {
        return age is >= 10 and <= 150;
    }    
}

帮助类ValidatorHelper:

/// 
/// 实体验证
/// 
public static class ValidatorHelper
{
    /// 
    /// 验证,抛出异常信息
    /// 
    /// 验证器
    /// 待验证实体
    /// 
    public static void ValidateMsg<TValidator, TDto>(TDto dto) where TValidator : AbstractValidator<TDto>, new()
    {
        TValidator validator = new TValidator();
        ValidationResult validationResult = validator.Validate(dto);
        if (!validationResult.IsValid)
        {
            throw new Exception(validationResult.Errors.FirstOrDefault().ErrorMessage);
        }
    }

    /// 
    /// 验证,抛出异常信息
    /// 
    /// 验证器
    /// 待验证实体
    /// 
    public static void ValidateMsgs<TValidator, TDto>(TDto dto) where TValidator : AbstractValidator<TDto>, new()
    {
        TValidator validator = new TValidator();
        ValidationResult validationResult = validator.Validate(dto);
        if (!validationResult.IsValid)
        {
            var msgs = string.Join(";\r\n", validationResult.Errors.Select(a => a.ErrorMessage));
            throw new Exception(msgs);
        }
    }

    public static ValidationFailure GetFirstError<TValidator, TDto>(TDto dto) where TValidator : AbstractValidator<TDto>, new()
    {
        TValidator validator = new TValidator();
        ValidationResult result = validator.Validate(dto);
        if(!result.IsValid)
        {
            return result.Errors[0];
        }
        else
        {
            return null;
        }
    }   

结果:
关于WPF输入框数据验证的解决方案_第3张图片


总结

在.Net中校验的方法有很多种,具体看业务需求。

你可能感兴趣的:(wpf,c#,开发语言)