只要是有表單存在,那麼就有可能有對數據的校驗需求。如:判斷是否爲整數、判斷電子郵件格式等等。ide
WPF採用一種全新的方式 - Binding,來實現前臺顯示與後臺數據進行交互,固然數據校驗方式也不同了。ui
本專題全面介紹一下WPF中4種Validate方法,幫助你瞭解如何在WPF中對binding的數據進行校驗,並處理錯誤顯示。this
正常狀況下,只要是綁定過程當中出現異常或者在converter中出現異常,都會形成綁定失敗。spa
可是WPF不會出現任何異常,只會顯示一片空白(固然有些Converter中的異常會形成程序崩潰)。3d
這是由於默認狀況下,Binding.ValidatesOnException爲false,因此WPF忽視了這些綁定錯誤。code
可是若是咱們把Binding.ValidatesOnException爲true,那麼WPF會對錯誤作出如下反應:對象
咱們的Binding對象,維護着一個ValidationRule的集合,當設置ValidatesOnException爲true時,blog
默認會添加一個ExceptionValidationRule到這個集合當中。繼承
PS:對於綁定的校驗只在Binding.Mode 爲TwoWay和OneWayToSource纔有效,索引
即當須要從target控件將值傳到source屬性時,很容易理解,當你的值不須要被別人使用時,就極可能校驗也不必。
直接在Setter方法中,對value進行校驗,若是不符合規則,那麼就拋出異常。而後修改XAML不忽視異常。
public class PersonValidateInSetter : ObservableObject { private string name; private int age; public string Name { get { return this.name; } set { if (string.IsNullOrWhiteSpace(value)) { throw new ArgumentException("Name cannot be empty!"); } if (value.Length < 4) { throw new ArgumentException("Name must have more than 4 char!"); } this.name = value; this.OnPropertyChanged(() => this.Name); } } public int Age { get { return this.age; } set { if (value < 18) { throw new ArgumentException("You must be an adult!"); } this.age = value; this.OnPropertyChanged(() => this.Age); } } }
<Grid DataContext="{Binding PersonValidateInSetter}"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBlock Text="Name:" /> <TextBox Grid.Column="1" Margin="1" Text="{Binding Name, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}" /> <TextBlock Grid.Row="1" Text="Age:" /> <TextBox Grid.Row="1" Grid.Column="1" Margin="1" Text="{Binding Age, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}" /> </Grid>
當輸入的值,在setter方法中校驗時出現錯誤,就會出現一個紅色的錯誤框。
關鍵代碼:ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged。
PS:這種方式有一個BUG,首次加載時不會對默認數據進行檢驗。
使Model對象繼承IDataErrorInfo接口,並實現一個索引進行校驗。若是索引返回空表示沒有錯誤,若是返回不爲空,
表示有錯誤。另一個Erro屬性,可是在WPF中沒有被用到。
public class PersonDerivedFromIDataErrorInfo : ObservableObject, IDataErrorInfo { private string name; private int age; public string Name { get { return this.name; } set { this.name = value; this.OnPropertyChanged(() => this.Name); } } public int Age { get { return this.age; } set { this.age = value; this.OnPropertyChanged(() => this.Age); } } // never called by WPF public string Error { get { return null; } } public string this[string propertyName] { get { switch (propertyName) { case "Name": if (string.IsNullOrWhiteSpace(this.Name)) { return "Name cannot be empty!"; } if (this.Name.Length < 4) { return "Name must have more than 4 char!"; } break; case "Age": if (this.Age < 18) { return "You must be an adult!"; } break; } return null; } } }
<Grid DataContext="{Binding PersonDerivedFromIDataErrorInfo}"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBlock Text="Name:" /> <TextBox Grid.Column="1" Margin="1" Text="{Binding Name, NotifyOnValidationError=True, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" /> <TextBlock Grid.Row="1" Text="Age:" /> <TextBox Grid.Row="1" Grid.Column="1" Margin="1" Text="{Binding Age, NotifyOnValidationError=True, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />
PS:這種方式,沒有了第一種方法的BUG,可是相對很麻煩,既須要繼承接口,又須要添加一個索引,若是遺留代碼,那麼這種方式就不太好。
一個數據對象或許不能包含一個應用要求的全部不一樣驗證規則,可是經過自定義驗證規則就能夠解決這個問題。
在須要的地方,添加咱們建立的規則,並進行檢測。
經過繼承ValidationRule抽象類,並實現Validate方法,並添加到綁定元素的Binding.ValidationRules中。
public class MinAgeValidation : ValidationRule { public int MinAge { get; set; } public override ValidationResult Validate(object value, CultureInfo cultureInfo) { ValidationResult result = null; if (value != null) { int age; if (int.TryParse(value.ToString(), out age)) { if (age < this.MinAge) { result = new ValidationResult(false, "Age must large than " + this.MinAge.ToString(CultureInfo.InvariantCulture)); } } else { result = new ValidationResult(false, "Age must be a number!"); } } else { result = new ValidationResult(false, "Age must not be null!"); } return new ValidationResult(true, null); } }
<Grid> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBlock Text="Name:" /> <TextBox Grid.Column="1" Margin="1" Text="{Binding Name}"> </TextBox> <TextBlock Grid.Row="1" Text="Age:" /> <TextBox Grid.Row="1" Grid.Column="1" Margin="1"> <TextBox.Text> <Binding Path="Age" UpdateSourceTrigger="PropertyChanged" ValidatesOnDataErrors="True"> <Binding.ValidationRules> <validations:MinAgeValidation MinAge="18" /> </Binding.ValidationRules> </Binding> </TextBox.Text> </TextBox> </Grid>
這種方式,也會有第一種方法的BUG,暫時還不知道如何解決,可是這個可以靈活的實現校驗,而且能傳參數。
效果圖:
在System.ComponentModel.DataAnnotaions命名空間中定義了不少特性,
它們能夠被放置在屬性前面,顯示驗證的具體須要。放置了這些特性以後,
屬性中的Setter方法就可使用Validator靜態類了,來用於驗證數據。
public class PersonUseDataAnnotation : ObservableObject { private int age; private string name; [Range(18, 120, ErrorMessage = "Age must be a positive integer")] public int Age { get { return this.age; } set { this.ValidateProperty(value, "Age"); this.SetProperty(ref this.age, value, () => this.Age); } } [Required(ErrorMessage = "A name is required")] [StringLength(100, MinimumLength = 3, ErrorMessage = "Name must have at least 3 characters")] public string Name { get { return this.name; } set { this.ValidateProperty(value, "Name"); this.SetProperty(ref this.name, value, () => this.Name); } } protected void ValidateProperty<T>(T value, string propertyName) { Validator.ValidateProperty(value,
new ValidationContext(this, null, null) { MemberName = propertyName });
}
}
<Grid> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Grid.ColumnDefinitions> <ColumnDefinition Width="Auto" /> <ColumnDefinition /> </Grid.ColumnDefinitions> <TextBlock Text="Name:" /> <TextBox Grid.Column="1" Margin="1" Text="{Binding Name, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}" /> <TextBlock Grid.Row="1" Text="Age:" /> <TextBox Grid.Row="1" Grid.Column="1" Margin="1" Text="{Binding Age, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}" /> </Grid>
使用特性的方式,可以很自由的使用自定義的規則,並且在.Net4.5中新增了不少特性,能夠很方便的對數據進行校驗。
例如:EmailAddress, Phone, and Url等。
在上面的例子中,咱們能夠看到當出現驗證不正確時,綁定控件會被一圈紅色錯誤線包裹住。
這種方式通常不可以正確的展現出,錯誤的緣由等信息,因此有可能須要本身的錯誤顯示方式。
前面,咱們已經講過了。當在檢測過程當中,出現錯誤時,WPF會把錯誤信息封裝爲一個ValidationError對象,
並添加到Validation.Errors中,因此咱們能夠取出錯誤詳細信息,並顯示出來。
下面就是一個簡單的例子,每次都把錯誤信息以紅色展現在空間上面。這裏的AdornedElementPlaceholder至關於
控件的佔位符,表示控件的真實位置。這個例子是在書上直接拿過來的,只能作基本展現用。
<ControlTemplate x:Key="ErrorTemplate"> <Border BorderBrush="Red" BorderThickness="2"> <Grid> <AdornedElementPlaceholder x:Name="_el" /> <TextBlock Margin="0,0,6,0" HorizontalAlignment="Right" VerticalAlignment="Center" Foreground="Red" Text="{Binding [0].ErrorContent}" /> </Grid> </Border> </ControlTemplate>
<TextBox x:Name="AgeTextBox" Grid.Row="1" Grid.Column="1" Margin="1" Validation.ErrorTemplate="{StaticResource ErrorTemplate}" >
使用方式很是簡單,將上面的模板做爲邏輯資源加入項目中,而後像上面同樣引用便可。
效果圖:
對知識梳理總結,但願對你們有幫助!