天天看點

[WPF 基礎知識系列] —— 綁定中的資料校驗Vaildation

隻要是有表單存在,那麼就有可能有對資料的校驗需求。如:判斷是否為整數、判斷電子郵件格式等等。

WPF采用一種全新的方式 - Binding,來實作前台顯示與背景資料進行互動,當然資料校驗方式也不一樣了。

這專題全面介紹一下WPF中4種Validate方法,幫助你了解如何在WPF中對binding的資料進行校驗,并處理錯誤顯示。

前言:

本專題全面介紹一下WPF中4種Validate方法,幫助你了解如何在WPF中對binding的資料進行校驗,并處理錯誤顯示。

一、簡介

正常情況下,隻要是綁定過程中出現異常或者在converter中出現異常,都會造成綁定失敗。

但是WPF不會出現任何異常,隻會顯示一片空白(當然有些Converter中的異常會造成程式崩潰)。

這是因為預設情況下,Binding.ValidatesOnException為false,是以WPF忽視了這些綁定錯誤。

但是如果我們把Binding.ValidatesOnException為true,那麼WPF會對錯誤做出以下反應:

  1. 設定綁定元素的附加屬性 Validation.HasError為true(如TextBox,如果Text被綁定,并出現錯誤)。
  2. 建立一個包含錯誤詳細資訊(如抛出的Exception對象)的ValidationError對象。
  3. 将上面産生的對象添加到綁定對象的Validation.Errors附加屬性當中。
  4. 如果Binding.NotifyOnValidationError是true,那麼綁定元素的附加屬性中的Validation.Error附加事件将被觸發。(這是一個冒泡事件)

我們的Binding對象,維護着一個ValidationRule的集合,當設定ValidatesOnException為true時,

預設會添加一個ExceptionValidationRule到這個集合當中。

PS:對于綁定的校驗隻在Binding.Mode 為TwoWay和OneWayToSource才有效,

即當需要從target控件将值傳到source屬性時,很容易了解,當你的值不需要被别人使用時,就很可能校驗也沒必要。

二、四種實作方法

1、在Setter方法中進行判斷

直接在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,首次加載時不會對預設資料進行檢驗。

2、繼承IDataErrorInfo接口

使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,但是相對很麻煩,既需要繼承接口,又需要添加一個索引,如果遺留代碼,那麼這種方式就不太好。

3、自定義校驗規則

一個資料對象或許不能包含一個應用要求的所有不同驗證規則,但是通過自定義驗證規則就可以解決這個問題。

在需要的地方,添加我們建立的規則,并進行檢測。

通過繼承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,暫時還不知道如何解決,但是這個能夠靈活的實作校驗,并且能傳參數。

效果圖:

[WPF 基礎知識系列] —— 綁定中的資料校驗Vaildation

4、使用資料注解(特性方式)

在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中,是以我們可以取出錯誤詳細資訊,并顯示出來。

1、為控件建立ErrorTemplate

下面就是一個簡單的例子,每次都把錯誤資訊以紅色展示在空間上面。這裡的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}" >      

使用方式非常簡單,将上面的模闆作為邏輯資源加入項目中,然後像上面一樣引用即可。

[WPF 基礎知識系列] —— 綁定中的資料校驗Vaildation

對知識梳理總結,希望對大家有幫助!

作者:

ColdJokeLife

出處:http://www.cnblogs.com/ColdJokeLife/

本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,如有問題,請聯系我,非常感謝。

繼續閱讀