[WPF] 使用自定义验证规则实现简单数据校验及绑定的项目
项目说明:将来自数据源的数据点画在Chart上,需要选择横轴数据源XAxis、纵轴数据源YAxis、以及选择起始时间和终止时间。
数据源来自于一个DataTable,有Date数据(横轴),以及其它各项Data1,Data2等(纵轴)
不同的纵轴数据源有不同的时间长度(按月份计数),所以在切换纵轴时,需要绑定不同的数据校验Max值,对输入的起始时间和终止时间做数据校验(由于所用验证规则相同,故将只用起始时间做说明)
先搭建一个简单的属性界面:
<!--X轴部分-->
<ComboBox Name="xAxis" Padding="5" Margin="1" ItemsSource="{Binding Path=XAxis}"/>
<!--Y轴部分-->
<ComboBox Name="yAxis" Padding="5" Margin="1" ItemsSource="{Binding Path=YAxis}"/>
<!--起始时间-->
<TextBox x:Name="startTime" Padding="5" Margin="1" Text="{Binding Path=StartTime}"/>
<!--终止时间-->
<TextBox x:Name="stopTime" Padding="5" Margin="1" Text="{Binding Path=StopTime}"/>
<!--信息提示框-->
<TextBlock x:Name="txtErrorInfo" FontSize="12" Foreground="Red" TextAlignment="Center" Padding="2"/>
首先,为了实现对起始时间和终止时间项的自定义验证规则,需要写一个继承ValidationRule的类:
//ValidationRule需要导入System.Windows.Controls包
public class ValidTimeRule : ValidationRule
{
private int min = 0;
private int max = int.MaxValue;
public int Min
{
get { return min; }
set { min = value; }
}
public int Max
{
get { return max; }
set { max = value; }
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
int time = 0;
try
{
//对非空内容尝试转化为int,否则返回false
if (((string)value).Length > 0)
time = int.Parse((string)value, NumberStyles.Any);
}
catch
{
return new ValidationResult(false, "字符输入非法,请输入数字.");
}
//对数据范围进行验证
if (time < Min || time > Max)
{
return new ValidationResult(false, string.Format("数字应在在{0}-{1}范围内", Min, Max));
}
return new ValidationResult(true, null);
}
}
在xaml中导入ValidTimeRule的命名空间(我的是在StoreDatebase工程的DataModel文件下,后续的导入不再给出说明):
<Window xmlns:valid ="clr-namespace:StoreDatebase.DataModel;assembly=StoreDatebase">
对起始时间加入数据验证,并在StartTime_Error中做出相应提示:
<TextBox x:Name="startTime" Padding="5" Margin="1" Validation.Error="StartTime_Error">
<TextBox.Text>
<Binding Path="StartTime" NotifyOnValidationError="True" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<valid:ValidTimeRule x:Name ="validStartTime" Max="2000" Min="0"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
StartTime_Error部分:
//数据错误提示
private void StartTime_Error(object sender, ValidationErrorEventArgs e)
{
if(e.Action == ValidationErrorEventAction.Added)
{
txtErrorInfo.Text = e.Error.ErrorContent.ToString();
}
}
这样一个简单的自定义验证规则的数据校验就算实现了,但是这还不能满足我们对Max值灵活更改的需求。这时有两种方式,一是在后台代码中更改Max值,另外一个就是尝试通过绑定的方式实现。(两者方法都可)
首先,在ViewModel中加入一个MouthCount字段绑定到Max属性:
public class ViewModel
{
//当前ViewModel已包含XAxis,YAxis,StartTime,StopTime字段
//实现INotifyPropertyChanged接口的部分略过不展示
private int monthCount;
public int MonthCount{get;set;}
}
但这样还不够,因为想要使用绑定,必须是注册了依赖项的属性。因为c#不支持多重继承,所以我们没办法直接在ValidTimeRule类中再继承DependencyObject,从而直接将Max属性注册为依赖项,所以先单独写一个可以注册依赖项属性的类:
public class MyDependencyObject: DependencyObject
{
public int BindingValue
{
get { return (int)GetValue(BindingValueProperty); }
set { SetValue(BindingValueProperty, value); }
}
public static readonly DependencyProperty BindingValueProperty =
DependencyProperty.Register("BindingValue", typeof(int), typeof(MyDependencyObject));
}
然后将ValidTimeRule类中的Max属性从int改为MyDependencyObject(以Max属性举例):
public class ValidTimeRule : ValidationRule
{
//在构造函数中将Max初始化为一个实例
public ValidTimeRule()
{
Max = new MyDependencyObject();
}
private int min;
private MyDependencyObject max;
public int Min
{
get { return min; }
set { min = value; }
}
public MyDependencyObject Max
{
get { return max; }
set { max = value; }
}
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
ValidState = true;
int time = 0;
try
{
//对非空内容尝试转化为int,否则返回false
if (((string)value).Length > 0)
time = int.Parse((string)value, NumberStyles.Any);
}
catch
{
return new ValidationResult(false, "字符输入非法,请输入数字.");
}
//对数据范围进行验证
if (time < Min || time > Max.BindingValue)
{
return new ValidationResult(false, string.Format("数字应在在{0}-{1}范围内", Min, Max.BindingValue));
}
return new ValidationResult(true, null);
}
}
这样应该可以使用绑定了吧,当我把xaml中的代码改为如下并运行之后发现了新的问题:
<TextBox x:Name="startTime" Padding="5" Margin="1" Validation.Error="StartTime_Error">
<TextBox.Text>
<Binding Path="StartTime" NotifyOnValidationError="True" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<valid:ValidTimeRule x:Name ="validStartTime">
<valid:ValidTimeRule.Max>
<dpc:MyDependencyObject BindingValue="{Binding Path=MonthCount}"/>
</valid:ValidTimeRule.Max>
</valid:ValidTimeRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
新的绑定并没有生效,查看输出窗口报错如下:
Cannot find governing FrameworkElement or FrameworkContentElement for target element.
查了一些资料之后解决了这个问题:详细情况见这里
再建一个类,继承Freezable
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy));
}
再将xaml中对应代码改为如下方式即可:
<!--导入BindingProxy-->
<Window.Resources>
<dpc:BindingProxy x:Key="proxy" Data="{Binding}"/>
</Window.Resources>
...
<TextBox x:Name="startTime" Padding="5" Margin="1" Validation.Error="StartTime_Error">
<TextBox.Text>
<Binding Path="StartTime" NotifyOnValidationError="True" UpdateSourceTrigger="PropertyChanged">
<Binding.ValidationRules>
<valid:ValidTimeRule x:Name ="validStartTime">
<valid:ValidTimeRule.Max>
<dpc:MyDependencyObject BindingValue="{
Binding Data.MonthCount,
Source={StaticResource proxy}}"/>
</valid:ValidTimeRule.Max>
</valid:ValidTimeRule>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
总结:后者用绑定的方式实现其实并不见得比后台直接更改更加方便有效,但也算是探索WPF的一种方式,在其中可以更好的理解WPF绑定的一些知识。
下一篇: element表格自定义无数据时的样式