WPF 数据验证

WPF提供了能与数据绑定系统紧密协作的验证功能。提供了两种方法用于捕获非法值:

1、可在数据对象中引发错误。

可以在设置属性时抛出异常,通常WPF会忽略所有在设置属性时抛出的异常,但可以进行配置,从而显示更有帮助的可视化指示。另一种选择是在自定义的数据类中实现 INotifyDataErrorInfo 或 IDataErrorInfo 接口,从而可得到指示错误的功能而不会抛出异常。

2、可在绑定级别上定义验证。

这种方法可获得使用相同验证的灵活性,而不必考虑使用的是哪个控件。更好的是,因为是在不同类中定义验证,可以很容易的在存储类似数据类型的多个绑定中重用验证。

错误模板

错误模板使用的是装饰层,装饰层是位于普通窗口内容之上的绘图层。使用装饰层,可添加可视化装饰来指示错误,而不用替换控件背后的控件模板或改变窗口的布局。文本框的标准错误模板通过在相应文本框的上面添加红色的Border元素来指示发生了错误。可使用错误模板添加其他细节。

在数据对象中引发错误

我这里分别尝试了 IDataErrorInfo 与 INotifyDataErrorInfo 接口,这俩需要分别对应使用  与 

IDataErrorInfo 接口定义了Error字段,这个字段在结构有多个字段时,难以映射,于是在[]下标下实现逻辑。

INotifyDataErrorInfo 接口需要实现 HasError,GetErrors 两个函数,并需要在属性的set 访问器内实现校验逻辑,并管理Error信息。

总体来说,在数据对象中引发错误不是一个好选择,验证逻辑硬编码在数据对象中,不利于复用。

在绑定级别上定义验证

在绑定级别上定义验证是针对于数据类型自定义验证规则,可以对同一种数据类型进行复用。自定义验证规则需要继承自 ValidationRule 类,需要实现 Validate 函数,在其中完成自定义验证内容。

public class PriceRule : ValidationRule
{
    private decimal min = 0;
    private decimal max = decimal.MaxValue;
    public decimal Min
    {
        get => min;
        set => min = value;
    }
    public decimal Max
    {
        get => max;
        set => max = value;
    }
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        decimal price = 0;
        try
        {
            if (((string)value).Length > 0)
            {
                price = Decimal.Parse((string)value, NumberStyles.Any, cultureInfo);
            }
        }
        catch
        {
            return new ValidationResult(false, "Illegal characters.");
        }
        if (price < min || price > max)
        {
            return new ValidationResult(false, "Not in the range " + Min + " to " + Max + ".");
        }
        else
        {
            return new ValidationResult(true, null);
        }
    }
}

验证多个值

如果需要执行对两个或更多个绑定值的验证,可以通过 BindingGroup 来实现,将需要校验的多个控件放置于同一个容器中,在容器级别应用验证规则,需要通过事件主动触发验证,通常是子组件失去焦点时。


    
        
        
    
    
        
        
    
    
        
            
                
            
        
    

    Person ID:
    

    Person Name:
    
public class Person : ViewModelBase
{
    private string name = string.Empty;
    public string Name { get=>name; set=> SetProperty(ref name, value); }
    
    private string id = string.Empty;
    public string ID { get => id; set => SetProperty(ref id, value); }
}
public class PersonRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        BindingGroup bindingGroup = (BindingGroup)value;
        Person? person = bindingGroup.Items[0] as Person;

        string name = (string)bindingGroup.GetValue(person, "Name");
        string id = (string)bindingGroup.GetValue(person, "ID");
        if ((name == "") && (id == ""))
        {
            return new ValidationResult(false, "A Person requires a ID or Name.");
        }
        else
        {
            return new ValidationResult(true, null);
        }
    }
}

下面贴出完整的测试代码:

MainWindow.xaml


    
        
            
                
            
            
                
                
            
            
                
                
                
                
            
           
            PersonAge:
            
                
                    
                        
                            
                        
                    
                
            

            PersonName:
            
                
                    
                        
                            
                        
                    
                
            

            PersonPrice:
            
                
                    
                        
                            
                        
                    
                
            

            
                
                    
                    
                
                
                    
                    
                
                
                    
                        
                            
                        
                    
                

                Person ID:
                

                Person Name:
                
            
        
    

MainWindow.xaml.cs

using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Runtime.CompilerServices;
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;

namespace TestValidation;


public class ViewModelBase : INotifyPropertyChanged
{
    public event PropertyChangedEventHandler? PropertyChanged;
    protected virtual void OnPropertyChanged([CallerMemberName] string? propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    protected virtual bool SetProperty(ref T member, T value, [CallerMemberName] string? propertyName = null)
    {
        if (EqualityComparer.Default.Equals(member, value))
        {
            return false;
        }
        member = value;
        OnPropertyChanged(propertyName);
        return true;
    }
}
public class PersonAge : ViewModelBase, IDataErrorInfo
{
    public string this[string columnName]
    {
        get
        {
            if(columnName == "Age")
            {
                if(Age < 0 || Age > 150)
                {
                    return "Age must not be less than 0 or greater than 150.";
                }
            }
            return null;
        }
    }
    private int age;
    public int Age { get => age; set => SetProperty(ref age, value); }
    public string Error => null;
}
public class PersonName : ViewModelBase, INotifyDataErrorInfo
{
    private string name = string.Empty;
    public string Name 
    {
        get => name;
        set
        {
            bool valid = true;
            foreach (char c in value)
            {
                if (!char.IsLetterOrDigit(c))
                {
                    valid = false;
                    break;
                }
            }
            if(!valid)
            {
                List errors = new List ();
                errors.Add("The Name can only contain letters and numbers.");
                SetErrors("Name", errors);
            }
            else
            {
                ClearErrors("Name");
            }
            SetProperty(ref name, value);
        }
    }

    private Dictionary> errors = new Dictionary>();
    private void SetErrors(string propertyName, List propertyErrors)
    {
        errors.Remove(propertyName);
        errors.Add(propertyName, propertyErrors);
        if (ErrorsChanged != null)
            ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
    }
    private void ClearErrors(string propertyName)
    {
        errors.Remove(propertyName);
        if(ErrorsChanged != null)
            ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
    }

    public bool HasErrors
    {
        get { return errors.Count > 0; }
    }
    public event EventHandler? ErrorsChanged;
    public IEnumerable GetErrors(string? propertyName)
    {
        if(!string.IsNullOrEmpty(propertyName) && errors.ContainsKey(propertyName))
        {
            return errors[propertyName];
        }
        return new List();
    }
}
public class PersonPrice : ViewModelBase, INotifyPropertyChanged
{
    private decimal price = 0;
    public decimal Price
    {
        get => price; 
        set
        {
            SetProperty(ref price, value);
        }
    }
}

public class PriceRule : ValidationRule
{
    private decimal min = 0;
    private decimal max = decimal.MaxValue;
    public decimal Min
    {
        get => min;
        set => min = value;
    }
    public decimal Max
    {
        get => max;
        set => max = value;
    }
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        decimal price = 0;
        try
        {
            if (((string)value).Length > 0)
            {
                price = Decimal.Parse((string)value, NumberStyles.Any, cultureInfo);
            }
        }
        catch
        {
            return new ValidationResult(false, "Illegal characters.");
        }
        if (price < min || price > max)
        {
            return new ValidationResult(false, "Not in the range " + Min + " to " + Max + ".");
        }
        else
        {
            return new ValidationResult(true, null);
        }
    }
}

public class Person : ViewModelBase
{
    private string name = string.Empty;
    public string Name { get=>name; set=> SetProperty(ref name, value); }
    
    private string id = string.Empty;
    public string ID { get => id; set => SetProperty(ref id, value); }
}
public class PersonRule : ValidationRule
{
    public override ValidationResult Validate(object value, CultureInfo cultureInfo)
    {
        BindingGroup bindingGroup = (BindingGroup)value;
        Person? person = bindingGroup.Items[0] as Person;

        string name = (string)bindingGroup.GetValue(person, "Name");
        string id = (string)bindingGroup.GetValue(person, "ID");
        if ((name == "") && (id == ""))
        {
            return new ValidationResult(false, "A Person requires a ID or Name.");
        }
        else
        {
            return new ValidationResult(true, null);
        }
    }
}
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
        myGrid.DataContext = this;
    }

    public PersonAge PersonAge { get; set; } = new PersonAge();
    public PersonName PersonName { get; set; } = new PersonName();
    public PersonPrice PersonPrice { get; set; } = new PersonPrice();
    public Person Person { get; set; } = new Person();

    private void Grid_LostFocus(object sender, RoutedEventArgs e)
    {
        personValidation.CommitEdit();
    }
}

你可能感兴趣的:(紫云的程序人生,C#,WPF,wpf,数据验证)