Wpf 通過數據註解(特性)校驗表單+Prism8+IDataErrorInfo WPF表單驗證

十年河東,十年河西,莫欺少年窮

學無止境,精益求精

參考:WPF表單驗證

摘要

WPF表單驗證是WPF重要基礎設施之一,依靠MVVM的數據綁定機制及微軟的有力封裝,使得我們在處理實體表單驗證等可以快捷高效的靈活處理。常見的表單驗證實現大概有Exception 、ValidationRule 、IDataErrorInfo ,而本文則是通過IDataErrorInfo來實現表單驗證功能

1、要現實的效果 (本文資源只寫了針對textBox的樣式,其他控件用法類似)

 2、封裝驗證模型

既然是直接對實體進行驗證,那首先肯定是從實體對象模型着手,爲了方便複用性,建議抽出一個公共的驗證模型

    public class ValidateModelBase : BindableBase, IDataErrorInfo
    {
        public Dictionary<string, string> dataErrors = new Dictionary<string, string>();  //錯誤信息集合

        public bool IsValidated
        {
            get
            {
                return !dataErrors.Any();
            }
        }

        public string this[string columnName]
        {
            get
            {
                ValidationContext vc = new ValidationContext(this, null, null)
                {
                    MemberName = columnName
                };
                var res = new List<ValidationResult>();
                var result = Validator.TryValidateProperty(this.GetType().GetProperty(columnName).GetValue(this, null), vc, res);
                if (res.Count > 0)
                {
                    string errorInfo = string.Join(Environment.NewLine, res.Select(r => r.ErrorMessage).ToArray());
                    AddError(dataErrors, columnName, errorInfo);
                    return errorInfo;
                }
                RemoveError(dataErrors, columnName);
                return null;
            }
        }

        /// <summary>
        /// 移除錯誤信息
        /// </summary>
        /// <param name="dataErrors"></param>
        /// <param name="columnName"></param>
        private void RemoveError(Dictionary<string, string> dataErrors, string columnName)
        {
            dataErrors.Remove(columnName);
        }

        /// <summary>
        /// 添加錯誤信息
        /// </summary>
        /// <param name="dataErrors"></param>
        /// <param name="columnName"></param>
        /// <param name="errorInfo"></param>
        private void AddError(Dictionary<string, string> dataErrors, string columnName, string errorInfo)
        {
            if (!dataErrors.ContainsKey(columnName))
            {
                dataErrors.Add(columnName, errorInfo);
            }
        }

        public string Error { get; set; }

        private bool isFormValid;
        /// <summary>
        /// 是否全局驗證
        /// </summary>
        public bool IsFormValid
        {
            get { return isFormValid; }
            set { isFormValid = value; RaisePropertyChanged(); }
        }
    }
View Code

原理就是使用索引器獲取頁面上的錯誤信息添加到一個字典集合中,公開一個是否驗證成功的屬性,方便外部驗證數據是否正確,這裏直接使用字典的一個擴展方法Enumerable.Any ,如果字典中包含元素則返回True,否則False

3、創建帶有數據註解的實體類

    public class StudentModel : ValidateModelBase
    {
        private string _StudentName;
        /// <summary>
        /// 學生姓名
        /// </summary>
        [Required(ErrorMessage = "學生姓名不允許爲空")]
        [MinLength(2, ErrorMessage = "學生姓名不能少於兩個字符")]
        public string StudentName
        {
            get { return _StudentName; }
            set { _StudentName = value; RaisePropertyChanged(); }
        }


        private int? _StudentAge;
        /// <summary>
        /// 學生年齡
        /// </summary>
        [Required(ErrorMessage = "學生年齡不允許爲空")]
        [Range(18, 40, ErrorMessage = "學生年齡範圍需在18-40歲之間")]
        [RegularExpression(@"[1-9]\d*", ErrorMessage = "請輸入數字")]
        public int? StudentAge
        {
            get { return _StudentAge; }
            set { _StudentAge = value; RaisePropertyChanged(); }
        }


        private string _StudentEmail;
        /// <summary>
        /// 學生郵箱
        /// </summary>
        [EmailAddress(ErrorMessage = "郵箱地址不合法")]
        public string StudentEmail
        {
            get { return _StudentEmail; }
            set { _StudentEmail = value; RaisePropertyChanged(); }
        }


        private string _StudentPhoneNumber;
        /// <summary>
        /// 學生手機號
        /// </summary>
        [Required(ErrorMessage = "學生手機號不允許爲空")]
        [RegularExpression(@"^1[3-9]\d{9}$", ErrorMessage = "手機號不正確")]
        public string StudentPhoneNumber
        {
            get { return _StudentPhoneNumber; }
            set { _StudentPhoneNumber = value; }
        }

    }
View Code

因其微軟早已對驗證進行其封裝爲註解(特性),相信做Web的童鞋並不陌生

4、使用Adorned裝飾器對文本框進行錯誤模板重寫

項目中新建 Resource 文件夾,並創建資源文件 DataValidation.Xaml ,代碼如下

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Style x:Key="VaildationTextBoxStyle" TargetType="{x:Type TextBox}">
        <Setter Property="BorderThickness" Value="1" />
        <Setter Property="Padding" Value="2,1,1,1" />
        <Setter Property="AllowDrop" Value="true" />
        <Setter Property="FocusVisualStyle" Value="{x:Null}" />
        <Setter Property="ScrollViewer.PanningMode" Value="VerticalFirst" />
        <Setter Property="Stylus.IsFlicksEnabled" Value="False" />
        <Setter Property="Validation.ErrorTemplate">
            <Setter.Value>
                <ControlTemplate>
                    <StackPanel Orientation="Horizontal">
                        <Border
                            x:Name="adornerborder"
                            VerticalAlignment="Top"
                            BorderThickness="1">
                            <Grid>
                                <AdornedElementPlaceholder x:Name="adorner" Margin="-1" />
                            </Grid>
                        </Border>
                        <Border
                            x:Name="errorBorder"
                            MinHeight="24"
                            Margin="8,0,0,0"
                            Background="#FFdc000c"
                            CornerRadius="0"
                            IsHitTestVisible="False"
                            Opacity="0">
                            <TextBlock
                                Margin="8,2,8,3"
                                VerticalAlignment="Center"
                                Foreground="White"
                                Text="{Binding ElementName=adorner, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"
                                TextWrapping="Wrap" />
                        </Border>
                    </StackPanel>

                    <ControlTemplate.Triggers>
                        <DataTrigger Value="True">
                            <DataTrigger.Binding>
                                <Binding ElementName="adorner" Path="AdornedElement.Tag" />
                            </DataTrigger.Binding>
                            <DataTrigger.EnterActions>
                                <BeginStoryboard x:Name="fadeInStoryboard1">
                                    <Storyboard>
                                        <DoubleAnimation
                                            Storyboard.TargetName="errorBorder"
                                            Storyboard.TargetProperty="Opacity"
                                            To="1"
                                            Duration="00:00:00.15" />
                                    </Storyboard>
                                </BeginStoryboard>
                            </DataTrigger.EnterActions>
                            <DataTrigger.Setters>
                                <Setter TargetName="adornerborder" Property="BorderBrush" Value="#FFdc000c" />
                            </DataTrigger.Setters>
                        </DataTrigger>

                        <DataTrigger Value="True">
                            <DataTrigger.Binding>
                                <Binding ElementName="adorner" Path="AdornedElement.IsKeyboardFocused" />
                            </DataTrigger.Binding>
                            <DataTrigger.EnterActions>
                                <BeginStoryboard x:Name="fadeInStoryboard">
                                    <Storyboard>
                                        <DoubleAnimation
                                            Storyboard.TargetName="errorBorder"
                                            Storyboard.TargetProperty="Opacity"
                                            To="1"
                                            Duration="00:00:00.15" />
                                    </Storyboard>
                                </BeginStoryboard>
                            </DataTrigger.EnterActions>
                            <!--  是否保留異常直到數據正常:如果不需要則放開下列驗證  -->
                            <DataTrigger.ExitActions>
                                <StopStoryboard BeginStoryboardName="fadeInStoryboard" />
                                <BeginStoryboard x:Name="fadeOutStoryBoard">
                                    <Storyboard>
                                        <DoubleAnimation
                                            Storyboard.TargetName="errorBorder"
                                            Storyboard.TargetProperty="Opacity"
                                            To="0"
                                            Duration="00:00:00" />
                                    </Storyboard>
                                </BeginStoryboard>
                            </DataTrigger.ExitActions>
                            <DataTrigger.Setters>
                                <Setter TargetName="adornerborder" Property="BorderBrush" Value="#FFdc000c" />
                            </DataTrigger.Setters>
                        </DataTrigger>
                    </ControlTemplate.Triggers>
                </ControlTemplate>
            </Setter.Value>
        </Setter>
    </Style>
    <Style BasedOn="{StaticResource VaildationTextBoxStyle}" TargetType="{x:Type TextBox}" />
</ResourceDictionary>
View Code

這裏直接定義爲一個資源字典了,在要引用的窗體或者自定義用戶控件中引用即可

Tips:如果要全局引用,建議定義一個無Key的Style,並放置在App.xaml

5、在頁面/用戶控件中引入資源文件,並創建如下佈局

 

<UserControl x:Class="WpfApp.UserControls.SetingView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
             xmlns:local="clr-namespace:WpfApp.UserControls"
             mc:Ignorable="d" 
             d:DesignHeight="450" d:DesignWidth="800">
    <UserControl.Resources>
        <ResourceDictionary>
            <ResourceDictionary.MergedDictionaries>

                <ResourceDictionary Source="/Resource/DataValidation.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </UserControl.Resources>
 
    <Grid> 

        <StackPanel Orientation="Vertical">
            <StackPanel Orientation="Horizontal" Height="40" VerticalAlignment="Center"
>
                <TextBlock Width="70" TextAlignment="Right" VerticalAlignment="Center">
                        <Run Foreground="Red" Text="*" />
                        <Run Text="學生姓名:" />
                </TextBlock>
                <TextBox  
                        Width="240" Height="30"  VerticalAlignment="Center" VerticalContentAlignment="Center"
                        Tag="{Binding  StudentInfo.IsFormValid, UpdateSourceTrigger=PropertyChanged}"
                        Text="{Binding   StudentInfo.StudentName, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
            </StackPanel>

            <StackPanel Orientation="Horizontal" Height="40" VerticalAlignment="Center"
>
                <TextBlock Width="70" TextAlignment="Right" VerticalAlignment="Center">
                        <Run Foreground="Red" Text="*" />
                        <Run Text="學生年齡:" />
                </TextBlock>
                <TextBox  
                        Width="240" Height="30"  VerticalAlignment="Center" VerticalContentAlignment="Center"
                        Tag="{Binding  StudentInfo.IsFormValid, UpdateSourceTrigger=PropertyChanged}"
                        Text="{Binding   StudentInfo.StudentAge, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
            </StackPanel>

            <StackPanel Orientation="Horizontal" Height="40" VerticalAlignment="Center"
>
                <TextBlock Width="70" TextAlignment="Right" VerticalAlignment="Center"> 
                        <Run Text="郵箱:" />
                </TextBlock>
                <TextBox  
                        Width="240" Height="30"  VerticalAlignment="Center"  VerticalContentAlignment="Center"
                        Tag="{Binding  StudentInfo.IsFormValid, UpdateSourceTrigger=PropertyChanged}"
                        Text="{Binding   StudentInfo.StudentEmail, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
            </StackPanel>

            <StackPanel Orientation="Horizontal" Height="40" VerticalAlignment="Center"
>
                <TextBlock Width="70" TextAlignment="Right" VerticalAlignment="Center">
                        <Run Foreground="Red" Text="*" />
                        <Run Text="聯繫方式:" />
                </TextBlock>
                <TextBox  
                        Width="240" Height="30" VerticalAlignment="Center" VerticalContentAlignment="Center"
                        Tag="{Binding  StudentInfo.IsFormValid, UpdateSourceTrigger=PropertyChanged}"
                        Text="{Binding   StudentInfo.StudentPhoneNumber, UpdateSourceTrigger=PropertyChanged, ValidatesOnDataErrors=True}" />
            </StackPanel>

            <Button
                    Width="150"
                    Height="26"
                    Margin="76,5"
                    IsEnabled="{Binding IsValidated}"
                    HorizontalAlignment="Left"
                    Command="{Binding SubmitCommand}"
                    Content="新生註冊" Cursor="Hand" />
        </StackPanel>
    </Grid>
</UserControl>
View Code

 6、ViewModel 如下:

using Prism.Commands;
using Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Windows;
using WpfApp.UserControlModels;

namespace WpfApp.ViewModels
{
    public class SetingViewModel : BindableBase
    {
        
        public DelegateCommand SubmitCommand { get; private set; }
        private StudentModel studentInfo;
        public StudentModel StudentInfo
        {
            get { return studentInfo; }
            set { studentInfo = value; RaisePropertyChanged(); }
        }

        public SetingViewModel()
        {
            StudentInfo = new StudentModel();
            SubmitCommand = new DelegateCommand(Submit);
        }

        private void Submit()
        {
            if (!StudentInfo.IsValidated)
            {
                StudentInfo.IsFormValid = true;
                MessageBox.Show("新生註冊失敗,請檢查錄入的新生信息是否正確!");
                return;
            }
            MessageBox.Show("新生註冊成功!");
        }
    }
     

}
View Code

7、總結

本文只針對TExtBox做了樣式封裝,其他諸如下拉框等需要自行構造樣式~在資源文件中增加即可

@陳大六

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章