欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

[WPF] 使用自定义验证规则实现简单数据校验及绑定的项目

程序员文章站 2022-03-04 11:28:14
...

项目说明:将来自数据源的数据点画在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绑定的一些知识。

相关标签: WPF c# wpf