Sunday 10 October 2010

Провірка даних в WPF (Simple Wpf Validation)

Сьогодні я покажу вам як легко можна валідувати дані введені користувачем. Для цього ми створимо простенький клас і поексперементуємо з ним. Першим буде розглянуто метод валідаціі на основі ексепшинів, після чого ми перейдемо до валідаціі яка появилась починаючи з фреймворка 3,5, валідаціі на основі IDataErrorInfo. Я пропущу створення проекту так як це вже було описано в попередніх постах і перейду безпосередньо до опису методу та реалізації.

Валідація на основі Exceptions.

Цей метод передбачає, що дані для користувацького інтерфейсу беруться з властивостей певного обєкту і відповідно коли вони змінюються на формі, вони автоматично записуюються в властивість обєкта, а так як записати в властивість типу int можна лише значення int то після того як користувач ввів щось типу “Десять” виникає помилка що дані не можуть бути записані і це відобразиться на нашому інтерфейсі. Такий же метод валідації існує в ASP NET. маю надію розглянути його роботу і там. Описав ніби правильно але в такому разі легше показати на прикладі:

Window1.xaml:

<Window x:Class="WpfValidation.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="400">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="35" ></RowDefinition>
            <RowDefinition Height="35" ></RowDefinition>
            <RowDefinition Height="35" ></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="50"></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        
        <TextBlock Grid.Row="0" Grid.Column="0" Margin="5">Name:</TextBlock>
        <TextBlock Grid.Row="1" Grid.Column="0" Margin="5">Age:</TextBlock>
        <TextBlock Grid.Row="2" Grid.Column="0" Margin="5">Sex:</TextBlock>
        
        <TextBox Name="name" Grid.Row="0" Grid.Column="1" Width="200" Margin="5" Text="{Binding Path=Name, ValidatesOnExceptions=true, NotifyOnValidationError=true}"></TextBox>
        <TextBox Name="age" Grid.Row="1" Grid.Column="1" Width="200" Margin="5" Text="{Binding Path=Age, ValidatesOnExceptions=true, NotifyOnValidationError=true}"></TextBox>
        <TextBox Name="sex" Grid.Row="2" Grid.Column="1" Width="200" Margin="5" Text="{Binding Path=Sex, ValidatesOnExceptions=true, NotifyOnValidationError=true}"></TextBox>
                
    </Grid>
</Window>

В редакторі це виглядатиме наступним чином:

01

Window1.xaml.cs:

using System.Windows;
namespace WpfValidation
{
    /// <summary>
    /// Interaction logic for Window1.xaml
    /// </summary>
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();
            loadData();
        }
        private void loadData()
        {
            UserItem item = new UserItem{
                              Name = "Ihor",
                              Age = 24,
                              Sex = UserItem.SexEnum.Male
                          };
            name.DataContext = item;
            age.DataContext = item;
            sex.DataContext = item;
        }
    }
    internal class UserItem
    {
        internal enum SexEnum
        {
            Male,
            Famale
        }
        public string Name { get; set; }
        public int Age { get; set; }
        public SexEnum Sex { get; set; }
    }
}
 image

Підсвітка спрацьовує автоматично при зміні фокусу, але якщо ми добавимо до стрічки з біндінгом наступний фрагмент “UpdateSourceTrigger=PropertyChanged“ то дані валідуватимуться зразу при введенні. Остаточний код xaml файлу наступний:

<Window x:Class="WpfValidation.Window1"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="Window1" Height="300" Width="400">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="35" ></RowDefinition>
            <RowDefinition Height="35" ></RowDefinition>
            <RowDefinition Height="35" ></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="50"></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        
        <TextBlock Grid.Row="0" Grid.Column="0" Margin="5">Name:</TextBlock>
        <TextBlock Grid.Row="1" Grid.Column="0" Margin="5">Age:</TextBlock>
        <TextBlock Grid.Row="2" Grid.Column="0" Margin="5">Sex:</TextBlock>
        
        <TextBox Name="name" Grid.Row="0" Grid.Column="1" Width="200" Margin="5" Text="{Binding Path=Name, ValidatesOnExceptions=true, NotifyOnValidationError=true, UpdateSourceTrigger=PropertyChanged}"></TextBox>
        <TextBox Name="age" Grid.Row="1" Grid.Column="1" Width="200" Margin="5" Text="{Binding Path=Age, ValidatesOnExceptions=true, NotifyOnValidationError=true, UpdateSourceTrigger=PropertyChanged}"></TextBox>
        <TextBox Name="sex" Grid.Row="2" Grid.Column="1" Width="200" Margin="5" Text="{Binding Path=Sex, ValidatesOnExceptions=true, NotifyOnValidationError=true, UpdateSourceTrigger=PropertyChanged}"></TextBox>
                
    </Grid>
</Window>

Тепер про використані властивості з біндінгом

UpdateSourceTrigger=PropertyChanged Вказує коли саме проводити валідацію. Детальніше тут http://msdn.microsoft.com/ru-ru/library/system.windows.data.binding.updatesourcetrigger.aspx
ValidatesOnExceptions В даному випадку є основним і вказує чи робити валідацію данних при ексепшинах.
NotifyOnValidationError Вказує на необхідність інформування при виникненні помилок при валідацї.

На цьому перший крок у валідації даних я вважаю завершеним, тепер ми переходимо до розгляду методу на основі IDataErrorInfo.

Валидация з використанням інтерфейсу IDataErrorInfo

Для реалізації даного методу нам необхідно реалізувати цей інтерфейс в нашому класі, він складається з індексатора і властивості. Властивість повертає стрічку з помилкою для всього класу а індексатор вказує на конкретну властивість в якій виникла помилка валідації, якщо помилок немає то відповідно повертається null. Виглядає цей інтерфейс наступним чином :

  public interface IDataErrorInfo 
  {
     string Error { get; }
     string this[string propertyName] { get; }
  }

Давайте попробуємо реалізувати цей інтерфейс на нашому прикладі:

public class UserItem : IDataErrorInfo
    {
        public enum SexEnum
        {
            Male,
            Famale
        }
        public string Name { get; set; }
        public int Age { get; set; }
        public SexEnum Sex { get; set; }
        public string this[string columnName]
        {
            get
            {
                string msg = null;
                switch (columnName)
                {
                    case "Name":
                        if (string.IsNullOrEmpty(this.Name))
                            msg = "Name can't be empty.";
                        break;
                    case "Age":
                        if (this.Age <= 0)
                            msg = "Age can't be less then 0.";
                        break;
                    case "Sex":
                        if (this.Sex != SexEnum.Famale && this.Sex != SexEnum.Male)
                        {
                            msg = "Incorect Sex.";
                        }
                        break;
                    default:
                        throw new ArgumentException(
                          "Unrecognized property: " + columnName);
                }
                return msg;
            }
        }
        public string Error
        {
            get
            {
                return null;
            }
        }
    }

В даному випадку ми використали індексатор, але нічого не мішає нам використати властивість Error. Після запуску проекту ви побачите що ніякої валідації не відбувається і це не дивно тому що наш хамл поки що не знає про можливість валідації даних, тому добавимо до нього властивості які дозволять використовувати валідацію:

<Window x:Class="ValidationApp.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="35" ></RowDefinition>
            <RowDefinition Height="35" ></RowDefinition>
            <RowDefinition Height="35" ></RowDefinition>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="50"></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Row="0" Grid.Column="0" Margin="5">Name:</TextBlock>
        <TextBlock Grid.Row="1" Grid.Column="0" Margin="5">Age:</TextBlock>
        <TextBlock Grid.Row="2" Grid.Column="0" Margin="5">Sex:</TextBlock>
        <TextBox Name="name" Grid.Row="0" Grid.Column="1" Width="200" Margin="5" Text="{Binding Path=Name, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"></TextBox>
        <TextBox Name="age" Grid.Row="1" Grid.Column="1" Width="200" Margin="5" Text="{Binding Path=Age, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"></TextBox>
        <TextBox Name="sex" Grid.Row="2" Grid.Column="1" Width="200" Margin="5" Text="{Binding Path=Sex, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"></TextBox>
    </Grid>
</Window>

Я добавив наступні властивості :

  • ValidatesOnDataErrors=True
  • UpdateSourceTrigger=PropertyChanged

Перша для того щоб використати нашу валідацію, а друга для визначення часу її спрацьовування, в даному випадку після зміни тексту. Детальніше про ці та інші параметри на сайті майкрософта: http://msdn.microsoft.com/en-us/library/system.windows.data.binding.updatesourcetrigger.aspx

На остаток хотів би показати як можна програмно встановити контрол в стан помилки валідації, це може допомогти вам коли ви будете розробляти свою систему валідації (хоча я рекомендую використовувати стандартні методи так як в більшості випадків їх можливостей вистачає з головою). Для цього нам будуть потрібні два методи

  • Validation.MarkInvalid
  • Validation.ClearInvalid

Для включення стану помилки валідації:

Validation.MarkInvalid(
   name.GetBindingExpression(TextBox.TextProperty),
   new ValidationError(new ExceptionValidationRule(),
   name.GetBindingExpression(TextBox.TextProperty)));

Для очищення:

Validation.ClearInvalid(name.GetBindingExpression(TextBox.TextProperty));

Ці методи дозволять вам підсвітити валідацію на контролах відповідно до встановленого вами шаблону або шаблону заданого по замовчуванню.

Всім поменше помилок.

Source Code