【WPF】数据验证
引言
數據驗證在任何用戶界面程序中都是不可缺少的一部分.在WPF中,數據驗證更是和綁定緊緊聯系在一起,下面簡單介紹MVVM模式下常用的幾種驗證方式.
錯誤信息顯示
在介紹數據驗證之前,有必要介紹一下如何顯示錯誤信息.方式很簡單,定義一個樣式觸發器,將錯誤信息和ToolTip綁定,如下:
           <Style TargetType="TextBox">
                <Style.Triggers>
                    <Trigger Property="Validation.HasError" Value="true">
                        <Setter Property="ToolTip"
                            Value="{Binding RelativeSource={RelativeSource Self}, Path=(Validation.Errors)[0].ErrorContent}" />
                    </Trigger>
                </Style.Triggers>
            </Style>
ValidatesOnExceptions驗證規則
ValidatesOnExceptions是WPF預定義的驗證規則,它會捕捉任何位置上的異常,包括類型轉換異常,屬性設置器異常,值轉換器異常等.捕捉到異常的時候,輸入的邊框會變成紅色,當然也可以自定義錯誤的模板(Validation.ErrorTemplate).想要ValidatesOnExceptions生效,將綁定屬性中的ValidatesOnExceptions設置為true即可.
PS:無論設置為true或false,類型轉換異常總會發生的,也就是總會有紅色框.
數據對象中驗證
直接在數據對象中編寫驗證規則是最簡單粗暴的方式,如下
        public int Price
        {
            get { return _price; }
            set
            {
                if (Equals(value, _price)) return;
                if (value < 0)
                {
                    throw new ArgumentException("數值不能小于0");
                }
                else
                {
                    _price = value;
                    RaisePropertyChanged(() => Price);
                }
            }
        }
如果小于0,程序不會拋錯,文字提示也會顯示在ToolTip上,前提是ValidatesOnExceptions=true.
PS:這種方式能如期實現,是因為WPF的Binding 捕捉屬性設置中的所有異常.但是,如果是代碼設置負數的話,程序直接掛掉.
自定義驗證規則
除了WPF預定義的驗證規則外,我們還可以自定義驗證規則,要繼承ValidationRule,編寫驗證不能大于99的數值,代碼如下:
    public class NumberRule : ValidationRule
    {
        public override System.Windows.Controls.ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            int i;
            //  int.TryParse(value.ToString(), out i);
            if (!int.TryParse(value.ToString(), out i))
            {
                return new System.Windows.Controls.ValidationResult(false,
                       "字符串格式不對!");
            }
            if (i > 99)
            {
                return new System.Windows.Controls.ValidationResult(false,
                        "數值不能大于99!");
            }
            else
            {
                return new System.Windows.Controls.ValidationResult(true, null);
            }
        }
    }
       <TextBox  Height="25" Width="100" Margin="208,142,0,0"  VerticalAlignment="Top" HorizontalAlignment="Left" >
            <TextBox.Text>
                <Binding Path="Price" Mode="TwoWay" ValidatesOnDataErrors="True">  
                    <Binding.ValidationRules>
                        <ExceptionValidationRule></ExceptionValidationRule>
                        <local:NumberRule></local:NumberRule>
                    </Binding.ValidationRules>
                </Binding>
            </TextBox.Text>           
        </TextBox>
PS:自定義驗證規則總是在ExceptionValidationRule之前進行,所以要在NumberRule加上轉換類型的異常處理,不然會有拋錯的可能性,當然上面的代碼中如果有類型錯誤變量i總會返回0.
PS:驗證的執行順序:自定義驗證規則->值轉換器->ExceptionValidationRule->數據對象驗證.
ValidatesOnDataErrors驗證規則
正常情況下,上面的幾種方式都可以工作得很好,但是屬性多達幾十個的時候,寫起來就不是那么的舒服了.這個時候我們可以通過繼承接口IDataErrorInfo來將我們的驗證規則統一起來,代碼如下:
        public string Error
        { get { return ""; } }
        public string this[string propertyname]
        {
            get
            {
                string result = null;
                if (propertyname == "Price")
                {
                    if (Price >99)
                    {
                        result = "數值不能大于99!!";
                    }
                }
                return result;
            }
        }
Error在WPF中沒作用,返回任意都可以.
PS:記得把ValidatesOnDataErrors=true
PS:驗證的執行順序:自定義驗證規則->值轉換器->ExceptionValidationRule->數據對象驗證->ValidatesOnDataErrors.
進階ValidatesOnDataErrors驗證規則
ValidatesOnDataErrors雖然能統一起來到一個地方,但是還免不了每一個屬性單獨寫一個規則.所以我們需要一個更簡便的方式,那就是DataAnnotations+IDataErrorInfo的方式,代碼如下:
        private int _price;
        [Range(0, 99, ErrorMessage = "數值要在0到99之間")]
        public int Price
        {
            get { return _price; }
            set
            {
                if (Equals(value, _price)) return;
                if (value < 0)
                {
                    throw new ArgumentException("數值不能小于0");
                }
                else
                {
                    _price = value;
                    RaisePropertyChanged(() => Price);
                }
            }
        }
        public string this[string propertyname]
        {
            get
            {
                var vc = new ValidationContext(this, null, null);
                vc.MemberName = propertyname;
                var res = new List<System.ComponentModel.DataAnnotations.ValidationResult>();
                var result = Validator.TryValidateProperty(this.GetType().GetProperty(propertyname).GetValue(this, null), vc, res);
                if (res.Count > 0)
                {
                    return string.Join(Environment.NewLine, res.Select(r => r.ErrorMessage).ToArray());
                }
                return string.Empty;
            }
        }
采用這種方式,開發的時候只需要簡單的設置一下特性,就能如期望的顯示我們的驗證提示了,關于這種方式的詳細用法,網上有一篇更好的文章:傳送門.
注意事項
到這里,如果沒有什么意外,相信大家都會采用DataAnnotations+IDataErrorInfo的方式,這種方式實現最簡單,而且發生在viewmodel上,我們很容易地在保存環節得到所有異常信息,從而阻止保存數據的進行.但是WPF的數據驗證中都有個通病,就是發生數據異常的時候,屬性實際的值還是上次合法的值,和界面上顯示的值有所不同.這個時候如果用戶強行保存,我們就發現DataAnnotations+IDataErrorInfo的驗證方式竟然通過了!這不符合我們的期望.這種情況我沒發現有什么優雅的解決方案,暫時想到的只有在按鈕的點擊事件中遍歷LogicalTreeHelper的輸入控件,檢查Validation.HasError屬性,組合異常信息傳給viewmodel,讓viewmodel作出處理.其實最為徹底的方式是,封裝數字輸入控件等各類特定的控件,提高用戶體驗的同時,也讓異常處理更簡單.
小結
本文簡單介紹了WPF數據驗證的各種方式,而我們基本上都會采用DataAnnotations+IDataErrorInfo的方式,如果您有更好的方式,請不吝指教,感激不盡!
總結
 
                            
                        - 上一篇: ERP failure: Error w
- 下一篇: Why AG3 client 815 t
