Проверка данных в WPF

wpf controls bindings

Эта статья демонстрирует, как можно создать правило для проверки данных, заданных через Binding. В статье так же рассматривается проблема передачи дополнительных параметров в метод проверки.

Предположим, что мы хотим создать простое приложение, которое позволяет нам выбрать некоторое число с помощью Slider, затем ввести некоторое другое число в TextBox.

Приложение не должно допускать ввод числа не кратного тому, что выбрано с помощью Slider’а.

Пользовательский интерфейс может быть похож на этот:

Пример UI

Описание проблемы

2 числа, введенные пользователем, будут храниться в классе DivisionNumbers, который реализует следующий интерфейс:

  1. interface IDivisionNumbers : INotifyPropertyChanged
  2. {
  3.   int Dividend { get; set; }
  4.   int Divisor { get; set; }
  5. }

В конструкторе класса MainWindow сконфигурируем объект данного класса и установим свойство DataContext для того, чтобы можно было использовать Binding для элементов UI.

  1. public MainWindow()
  2. {
  3.   InitializeComponent();
  4.   var nums = new DivisionNumbers();
  5.   nums.Divisor = 1;
  6.   nums.Dividend = 1;
  7.   this.DataContext = nums;
  8. }

Логику проверки данных, введенных в TextBox, вынесем в подкласс ValidationRule. Объект данного класса добавим в свойство ValidationRules ассоциированного с TextBox’ом Binding’а. Когда пользователь вводит некоторое число в текстовом поле, наше правило будет его проверять на кратность заданному с помощью Slider’а числу.
Вот XAML описывающий вышесказанное:

  1. <TextBox>
  2.   <TextBox.Text>
  3.     <Binding Path="Dividend" UpdateSourceTrigger="PropertyChanged">
  4.       <Binding.ValidationRules>
  5.         <local:IsMultipleOfValidationRule />
  6.       </Binding.ValidationRules>
  7.     </Binding>
  8.   </TextBox.Text>
  9. </TextBox>

Незавершенная реализация нашего правила может выглядеть так:

  1. public class IsMultipleOfValidationRule : ValidationRule
  2. {
  3.   public override ValidationResult Validate(object value, CultureInfo cultureInfo)
  4.   {
  5.     try
  6.     {
  7.       int dividend = (int)Convert.ChangeType(value, typeof(int));
  8.       int divisor = // Каким-то образом получаем делитель...
  9.       if (dividend % divisor == 0)
  10.         return ValidationResult.ValidResult;
  11.       return new ValidationResult(
  12.         false,
  13.         "The number is not a multiple of " + divisor);
  14.     }
  15.     catch
  16.     {
  17.       return new ValidationResult( false, "Not a number." );
  18.     }
  19.   }
  20. }

И тут проявляется следующая проблема. Как получить значение делителя, которое выбрал пользователь, в методе Validate?

Мы не можем добавить свойство Divisor в класс IsMultipleOfValidationRule и использовать для него Binding, так как класс ValidationRule не является подклассом класса DependencyObject.

Каким образом мы можем преодолеть эту, казалось бы, невозможную из-за технического барьера, цель наиболее элегантно?

Решение

Добавим следующий фрагмент XAML кода в описание нашего окна:

  1. <Window.Resources>
  2.   <FrameworkElement x:Key="DataContextBridge" />
  3. </Window.Resources>
  4. <Window.DataContext>
  5.   <Binding Mode="OneWayToSource" Path="DataContext" Source="{StaticResource DataContextBridge}" />
  6. </Window.DataContext>

Таким образом, мы создали некоторый виртуальный элемент DataContextBridge, к которому будет привязан DataContext нашего окна.

Создадим новый класс, который будет хранить некоторое число:

  1. public class IntegerContainer : FrameworkElement
  2. {
  3.   public int Value
  4.   {
  5.     get { return (int)GetValue( ValueProperty ); }
  6.     set { SetValue( ValueProperty, value ); }
  7.   }
  8.  
  9.   public static readonly DependencyProperty ValueProperty =
  10.     DependencyProperty.Register(
  11.     "Value",
  12.     typeof( int ),
  13.     typeof( IntegerContainer ),
  14.     new UIPropertyMetadata( 0 ) );
  15. }

Теперь обновим наше правило для проверки данных с использованием IntegerContainer:

  1. public class IsMultipleOfValidationRule : ValidationRule
  2. {
  3.   public IntegerContainer DivisorContainer { get; set; }
  4.  
  5.   public override ValidationResult Validate(
  6.     object value, CultureInfo cultureInfo )
  7.   {
  8.     try
  9.     {
  10.       int dividend = (int)Convert.ChangeType( value, typeof( int ) );
  11.       int divisor = this.DivisorContainer.Value;
  12.       if( dividend % divisor == 0 )
  13.         return ValidationResult.ValidResult;
  14.       return new ValidationResult(
  15.         false,
  16.         "The number is not a multiple of " + divisor );
  17.     }
  18.     catch
  19.     {
  20.       return new ValidationResult( false, "Not a number." );
  21.     }
  22.   }
  23. }

Теперь осталось только настроить наше правило в XAML представлении.

  1. <TextBox>
  2.   <TextBox.Text>
  3.     <Binding Path="Dividend" UpdateSourceTrigger="PropertyChanged">
  4.       <Binding.ValidationRules>
  5.         <local:IsMultipleOfValidationRule>
  6.           <local:IsMultipleOfValidationRule.DivisorContainer>
  7.             <!-- This IntegerContainer is the "child node" of
  8.                  the DataContextBridge element, in the virtual
  9.                  branch attached to the Window's logical tree. -->
  10.             <local:IntegerContainer
  11.               DataContext="{Binding Source={StaticResource DataContextBridge}, Path=DataContext}"
  12.               Value="{Binding Divisor}" />
  13.           </local:IsMultipleOfValidationRule.DivisorContainer>
  14.         </local:IsMultipleOfValidationRule>
  15.       </Binding.ValidationRules>
  16.     </Binding>
  17.   </TextBox.Text>
  18. </TextBox>

Готово!

Оставить комментарий могут только зарегистрированные пользователи.

Войдите на сайт или зарегистрируйтесь, чтобы оставить комментарий.