目录
最完美的是最后一个。
在输入数据的时候,数据验证是必不可少的一部分,提高用户的交互性和使用性。
看了很多数据验证的,而且官网的demo也有问题(切换点击的时候,会产生白色的点),本人进行一一整理和对比。
首先验证有3种,本文只说2种,另一种性能比较低,就不说了。
分别是IDataErrorInfo和ValidationRule。
第一部分,IDataErrorInfo
1.建立一个wpf项目
2. Person类代码
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfApp2
{
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 = string.Empty;
if (name == "Age")
{
if (this.age < 0 || this.age > 150)
{
result = "年龄大于0或者小于150";
}
}
return result;
}
}
}
}
3.xaml界面
数据源是:
<local:Person x:Key="data"/>
style是:
其中(Validation.Errors)/ErrorContent}可以修改成(Validation.Errors)[0].ErrorContent},都会报错。
<Style x:Key="textBoxInError" TargetType="TextBox">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)/ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
整体代码
<Window x:Class="WpfApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:Person x:Key="data"/>
<Style x:Key="textBoxInError" TargetType="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>
<StackPanel Margin="20">
<TextBlock>输入age:</TextBlock>
<TextBox Style="{StaticResource textBoxInError}">
<TextBox.Text>
<Binding Path="Age" Source="{StaticResource data}"
ValidatesOnExceptions="True"
UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<DataErrorValidationRule/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBlock>鼠标悬停查看验证错误消息。</TextBlock>
</StackPanel>
</Grid>
</Window>
4.效果
清空的时候会报错
不要使用这个方法
第二部分,ValidationRule
ValidationRule验证有3种。
第一种:
1.建立一个程序
2.AgeRangeRule类
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace WpfApp2
{
public class AgeRangeRule : ValidationRule
{
public int Min { get; set; }
public int Max { get; set; }
public AgeRangeRule()
{
}
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, $"非法字符或{e.Message}");
}
if ((age < Min) || (age > Max))
{
return new ValidationResult(false, $"请输入年龄范围: {Min}-{Max}.");
}
return ValidationResult.ValidResult;
}
}
}
3.实现属性通知的功能,在输入数据的时候,可以进行提示
BindingBase类
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace WpfApp2
{
public class BindingBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
//protected virtual void OnPropertyChanged(string propertyName)
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")//此处使用特性
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
4.建立VM,MainViewModel类,继承BindingBase
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfApp2
{
public class MainViewModel : BindingBase
{
public MainViewModel()
{
}
private string name;
public string Name
{
get { return name; }
set
{
name = value; OnPropertyChanged();//OnPropertyChanged(nameof(name),使用特性,去掉括号的值
}
}
private int number;
public int Number
{
get { return number; }
set
{
number = value; OnPropertyChanged();//OnPropertyChanged(nameof(name),使用特性,去掉括号的值
}
}
}
}
5.xaml界面
<Window x:Class="WpfApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<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)/ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
</Window.Resources>
<Grid>
<TextBox Name="textBox1" Width="50" FontSize="15"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
Style="{StaticResource textBoxInError}">
<TextBox.Text>
<Binding Path="Number" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<local:AgeRangeRule Min="21" Max="130"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
</Grid>
</Window>
别忘记进行绑定
using System;
using System.Collections.Generic;
using System.Linq;
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 WpfApp2
{
/// <summary>
/// MainWindow.xaml 的交互逻辑
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
this.DataContext = new MainViewModel();
}
}
}
6.效果
这个可以使用,切换的瞬间有白色的点。
第二种:
1.建立一个wpf程序
2.BindingBase类
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace WpfApp1
{
public class BindingBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
//protected virtual void OnPropertyChanged(string propertyName)
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")//此处使用特性
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
3.建立MV,MainViewModel代码,继承BindingBase,NumberValidationRule是规则,自己可以进行修改
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace WpfApp1
{
public class MainViewModel : BindingBase
{
public MainViewModel()
{
}
private int name;
public int Name
{
get { return name; }
set
{
name = value; OnPropertyChanged();//OnPropertyChanged(nameof(name),使用特性,去掉括号的值
}
}
private string number;
public string Number
{
get { return number; }
set
{
number = value; OnPropertyChanged();//OnPropertyChanged(nameof(name),使用特性,去掉括号的值
}
}
}
//继承一个ValidationRule,重写Validate方法
public class NumberValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
double myValue = 0;
if (double.TryParse(value.ToString(), out myValue))
{
if (myValue >= 0 && myValue <= 100)
{
return new ValidationResult(true, null);
}
}
return new ValidationResult(false, "请输入 0 至 100的数字");
}
}
}
4.xaml界面,其中资源中是弹框的信息的模板
<Window x:Class="WpfApp1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<ControlTemplate x:Key="validationTemplate">
<StackPanel Orientation="Horizontal" x:Name="stack">
<Border>
<Grid>
<AdornedElementPlaceholder x:Name="adorner"/>
</Grid>
</Border>
<Popup x:Name="popup" AllowsTransparency="True" Placement="Right" >
<Border x:Name="errorBorder" Background="#ffdc000c" Opacity="0" MinHeight="30" >
<TextBlock Margin="0" Text="{Binding ElementName=adorner, Path=AdornedElement.(Validation.Errors)/ErrorContent}" Foreground="White" TextWrapping="Wrap" VerticalAlignment="Center"/>
</Border>
</Popup>
</StackPanel>
<ControlTemplate.Triggers>
<DataTrigger Value="True" Binding="{Binding ElementName=adorner,Path=AdornedElement.IsKeyboardFocused}">
<DataTrigger.Setters>
<Setter Property="IsOpen" TargetName="popup" Value="true"/>
</DataTrigger.Setters>
<DataTrigger.EnterActions>
<BeginStoryboard x:Name="fadeInStoryboard">
<Storyboard>
<DoubleAnimation Duration="00:00:00.15" Storyboard.TargetName="errorBorder" Storyboard.TargetProperty="Opacity" To="1"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<StopStoryboard BeginStoryboardName="fadeInStoryboard"/>
<BeginStoryboard x:Name="fadeOutStoryBoard">
<Storyboard>
<DoubleAnimation Duration="00:00:00" Storyboard.TargetName="errorBorder" Storyboard.TargetProperty="Opacity" To="0"/>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Window.Resources>
<Grid>
<TextBox x:Name="textbox" Width="200" Height="40" Validation.ErrorTemplate="{StaticResource validationTemplate }">
<TextBox.Text>
<Binding Path="Name" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<local:NumberValidationRule ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBox Width="100" Height="50" HorizontalAlignment="Right"></TextBox>
</Grid>
</Window>
5.效果
这个可以使用,不会出现白点,但是界面变化的时候,输入框不会跟着走。
第三种:
这是最完美的,也是最复杂的。
1.首先建立一个wpf程序
2.BindingBase类,用于属性通知
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace WpfApp2
{
public class BindingBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
//protected virtual void OnPropertyChanged(string propertyName)
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = "")//此处使用特性
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
3.IValidationExceptionHandler类,用于界面继承
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfApp2
{
public interface IValidationExceptionHandler
{
/// <summary>
/// 是否有效
/// </summary>
bool IsValid
{
get;
set;
}
}
}
4.VM,MainViewModel类,继承BindingBase和IValidationExceptionHandler
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace WpfApp2
{
public class MainViewModel : BindingBase , IValidationExceptionHandler
{
public MainViewModel()
{
}
private int name;
public int Name
{
get { return name; }
set
{
name = value; OnPropertyChanged();//OnPropertyChanged(nameof(name),使用特性,去掉括号的值
}
}
private string number;
public string Number
{
get { return number; }
set
{
number = value; OnPropertyChanged();//OnPropertyChanged(nameof(name),使用特性,去掉括号的值
}
}
private bool isValid = true;
public bool IsValid
{
get
{
return isValid;
}
set
{
if (value == isValid)
return;
isValid = value;
OnPropertyChanged();
}
}
}
}
5.NotifyAdorner类,用于提示
using System;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Media;
using System.Windows.Media.Imaging;
namespace WpfApp2
{
public class NotifyAdorner : Adorner
{
private VisualCollection _visuals;
private Canvas _canvas;
private Image _image;
private TextBlock _toolTip;
/// <summary>
/// 构造
/// </summary>
/// <param name="adornedElement"></param>
/// <param name="errorMessage"></param>
public NotifyAdorner(UIElement adornedElement, string errorMessage)
: base(adornedElement)
{
_visuals = new VisualCollection(this);
BuildNotifyStyle(errorMessage);
_canvas = new Canvas();
_canvas.Children.Add(_image);
_visuals.Add(_canvas);
}
private void BuildNotifyStyle(string errorMessage)
{
_image = new Image()
{
Width = 24,
Height = 24,
Source = new BitmapImage(new Uri(AppDomain.CurrentDomain.BaseDirectory + "/img/error.png", UriKind.Absolute))
};
_toolTip = new TextBlock()
{
FontSize = 14,
Text = errorMessage
};
_image.ToolTip = _toolTip;
}
protected override int VisualChildrenCount
{
get
{
return _visuals.Count;
}
}
protected override Visual GetVisualChild(int index)
{
return _visuals[index];
}
public void ChangeToolTip(string errorMessage)
{
_toolTip.Text = errorMessage;
}
protected override Size MeasureOverride(Size constraint)
{
return base.MeasureOverride(constraint);
}
protected override Size ArrangeOverride(Size finalSize)
{
_canvas.Arrange(new Rect(finalSize));
_image.Margin = new Thickness(finalSize.Width + 2, 0, 0, 0);
return base.ArrangeOverride(finalSize);
}
}
}
6.RequiredRule类,继承ValidationRule,此处可以写多个验证数据的类
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace WpfApp2
{
public class RequiredRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
if (value == null || string.IsNullOrEmpty(value.ToString()))
{
return new ValidationResult(false, "不能为空!");
}
return new ValidationResult(true, null);
}
}
}
7.ValidationExceptionBehavior类,验证行为类,可以获得附加到的对象
using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Interactivity;
namespace WpfApp2
{
/// <summary>
/// 验证行为类,可以获得附加到的对象
/// </summary>
public class ValidationExceptionBehavior : Behavior<FrameworkElement>
{
#region 字段
/// <summary>
/// 错误计数器
/// </summary>
private int _validationExceptionCount = 0;
private Dictionary<UIElement, NotifyAdorner> _adornerCache;
#endregion
#region 方法
#region 重写方法
/// <summary>
/// 附加对象时
/// </summary>
protected override void OnAttached()
{
_adornerCache = new Dictionary<UIElement, NotifyAdorner>();
//附加对象时,给对象增加一个监听验证错误事件的能力,注意该事件是冒泡的
this.AssociatedObject.AddHandler(Validation.ErrorEvent, new EventHandler<ValidationErrorEventArgs>(this.OnValidationError));
}
#endregion
#region 私有方法
#region 获取实现接口的对象
/// <summary>
/// 获取对象
/// </summary>
/// <returns></returns>
private IValidationExceptionHandler GetValidationExceptionHandler()
{
if (this.AssociatedObject.DataContext is IValidationExceptionHandler)
{
var handler = this.AssociatedObject.DataContext as IValidationExceptionHandler;
return handler;
}
return null;
}
#endregion
#region 显示Adorner
/// <summary>
/// 显示Adorner
/// </summary>
/// <param name="element"></param>
/// <param name="errorMessage"></param>
private void ShowAdorner(UIElement element, string errorMessage)
{
NotifyAdorner adorner = null;
//先去缓存找
if (_adornerCache.ContainsKey(element))
{
adorner = _adornerCache[element];
//找到了,修改提示信息
adorner.ChangeToolTip(errorMessage);
}
//没有找到,那就New一个,加入到缓存
else
{
adorner = new NotifyAdorner(element, errorMessage);
_adornerCache.Add(element, adorner);
}
//将Adorner加入到
if (adorner != null)
{
var adornerLayer = AdornerLayer.GetAdornerLayer(element);
adornerLayer.Add(adorner);
}
}
#endregion
#region 移除Adorner
/// <summary>
/// 移除Adorner
/// </summary>
/// <param name="element"></param>
private void HideAdorner(UIElement element)
{
//移除Adorner
if (_adornerCache.ContainsKey(element))
{
var adorner = _adornerCache[element];
var adornerLayer = AdornerLayer.GetAdornerLayer(element);
adornerLayer.Remove(adorner);
}
}
#endregion
#region 验证事件方法
/// <summary>
/// 验证事件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnValidationError(object sender, ValidationErrorEventArgs e)
{
try
{
var handler = GetValidationExceptionHandler();
var element = e.OriginalSource as UIElement;
if (handler == null || element == null)
return;
if (e.Action == ValidationErrorEventAction.Added)
{
_validationExceptionCount++;
ShowAdorner(element, e.Error.ErrorContent.ToString());
}
else if (e.Action == ValidationErrorEventAction.Removed)
{
_validationExceptionCount--;
HideAdorner(element);
}
handler.IsValid = _validationExceptionCount == 0;
}
catch (Exception ex)
{
throw ex;
}
}
#endregion
#endregion
#endregion
}
}
8.xaml界面
<Window x:Class="WpfApp2.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:local="clr-namespace:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<i:Interaction.Behaviors>
<local:ValidationExceptionBehavior/>
</i:Interaction.Behaviors>
<Grid>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition />
<RowDefinition />
</Grid.RowDefinitions>
<TextBox Height="25" Width="150">
<TextBox.Text>
<Binding Path="Number" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True">
<Binding.ValidationRules>
<local:RequiredRule ValidatesOnTargetUpdated="True"></local:RequiredRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBox Height="25" Width="150" Grid.Row="1">
<TextBox.Text>
<Binding Path="Name" UpdateSourceTrigger="PropertyChanged" NotifyOnValidationError="True">
<Binding.ValidationRules>
<local:RequiredRule ValidatesOnTargetUpdated="True"></local:RequiredRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<!--<Button Width="100" Height="30" Grid.Row="2" Content="确定" Command="{Binding OkCommand}"></Button>-->
</Grid>
</Window>
最后别忘记进行关联
this.DataContext = new MainViewModel();
9.效果
源码:
https://download.csdn.net/download/u012563853/86522132
扩展:
效果
输入66是正常的,空的话报错空,其他报错误字样
1.建立一个项目
2.RequireValidate.cs
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace WpfApp6
{
public class RequireValidate : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
var str = value as string;
//return ValidationResult.ValidResult;
if (str == "66")
{
return new ValidationResult(true, "1");
}
else if (string.IsNullOrEmpty(str))
{
return new ValidationResult(false, "不能为空");
}
else
{
return new ValidationResult(false, "错误");
}
}
}
}
3.MainWindow.xaml
<Window x:Class="WpfApp6.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WpfApp6"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<Window.Resources>
<local:RequireValidate x:Key="bb"/>
</Window.Resources>
<Grid>
<TextBox Width="50" Height="50">
<TextBox.Text>
<Binding Path="A" UpdateSourceTrigger="PropertyChanged" Mode="TwoWay" NotifyOnValidationError="True" >
<Binding.ValidationRules >
<local:RequireValidate ValidatesOnTargetUpdated="True" />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
<Validation.ErrorTemplate>
<ControlTemplate>
<StackPanel>
<Border BorderBrush="Red" BorderThickness="1">
<AdornedElementPlaceholder/>
</Border>
<TextBlock Foreground="Red" Text="{Binding [0].ErrorContent}"/>
</StackPanel>
</ControlTemplate>
</Validation.ErrorTemplate>
</TextBox>
</Grid>
</Window>
3.后台绑定A值即可
源码: