WPFプログラミング備忘録

WPF アノテーションを使ってデータの妥当性検証をする

WPF

はじめに

この記事ではアノテーションを使用した入力データの妥当性検証を行う方法を確認します。

アノテーションは他の方法とは異なり、カスタム属性を使ってより宣言的な方法で妥当性検証を実装できます。

アノテーションを使用するには、参照設定にSystem.ComponentModel.DataAnnotationsを追加しておく必要があります。

プロジェクトのファイル構成は下図のとおりです。

開発環境は以下のとおりです。

オペレーティングシステムWindows10 x64
Visual StudioMicrosoft Visual Studio Community 2019 Version 16.11.2
.NET Framework4.7.2

サンプルプログラムの動作仕様

起動時のウィンドウは以下のとおりです。

3つのプロパティ全てが妥当性検証に成功した場合のウィンドウは以下のとおりです。

失敗した場合は以下のとおりです。

  • 赤い枠線を表示する
  • 背景を薄いピンク色にする
  • 右側にエラーメッセージを表示する
  • ツールチップにエラーメッセージを表示する

アノテーションの実装

アノテーションを実装したPersonクラスは以下のとおりです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.ComponentModel.DataAnnotations;

namespace WpfValidatingData3
{
    class Person
    {
        private string name;
        [Display(Name = "名前")]
        [Required(ErrorMessage = "{0}を入力してください")]
        [StringLength(30, MinimumLength = 2, ErrorMessage = "{0}は最小{2}文字、最大{1}文字です")]
        public string Name
        {
            get { return name; }
            set
            {
                ValidateProperty(value, "Name");
                name = value;
            }
        }

        private object age;
        [Display(Name = "年齢")]
        [Required(ErrorMessage = "{0}を入力してください")]
        [Range(0, 150, ErrorMessage = "{0}は{1}以上{2}以下の整数です")]
        public object Age
        {
            get { return age; }
            set
            {
                ValidateProperty(value, "Age");
                age = int.Parse(value.ToString());
            }
        }

        private string email;
        [Display(Name = "メールアドレス")]
        [Required(ErrorMessage = "{0}を入力してください")]
        [EmailAddress(ErrorMessage = "{0}の形式が不正です")]
        public string Email
        {
            get { return email; }
            set
            {
                ValidateProperty(value, "Email");
                email = value;
            }
        }
        private void ValidateProperty<T>(T value, string propertyName)
        {
            Validator.ValidateProperty(value, new ValidationContext(this, null, null) { MemberName = propertyName });
        }
    }
}

名前と年齢およびメールアドレスを表すプロパティを定義しています。

Display属性 Required属性 StringLength属性

Nameプロパティには以下の属性を指定しました。

  • Display属性
  • Required属性
  • StringLength属性

Display属性のNameプロパティは画面に表示される値を設定します。

Required属性は入力が必須であることを表します。これは空文字列、nullまたは空白記号のみからなる値がコントロールに入力されたときに妥当性検証に失敗します。

StringLength属性はコントロールに入力できる文字数の最小値と最大値を指定しています。この文字数の範囲外の長さの値が入力されたときに妥当性検証に失敗します。

各属性のErrorMessageプロパティにはプレースホルダーを指定できます。

Required属性のErrorMessageプロパティのプレースホルダーは以下のとおりです。

  • {0} Display属性のNameプロパティに指定した値(”名前”)となります。

StringLength属性のErrorMessageプロパティのプレースホルダーは以下のとおりです。

  • {0} Required属性の場合と同じ
  • {1} 最長文字数で、ここでは30に相当します。
  • {2} 最短文字数で、ここでは2に相当します。

Range属性

Ageプロパティには以下の属性を指定しました。

  • Display属性
  • Required属性
  • Range属性

Range属性はコントロールに入力できる値の範囲を設定します。範囲外の値が入力さたとき妥当性検証に失敗します。

Range属性のErrorMessageプロパティのプレースホルダーは以下のとおりです。

  • {0} Display属性のNameプロパティに指定した値(”年齢”)となります。
  • {1} 範囲の最小値を表し、ここでは0に相当します。
  • {2} 範囲の最大値を表し、ここでは150に相当します。

EmailAddress属性

Emailプロパティには以下の属性を指定します。

  • Display属性
  • Required属性
  • EmailAddress属性

Required属性とEmailAddress属性のErrorMessageプロパティのプレースホルダー{0}も他のプロパティと同様で、ここでは”メールアドレス”に相当します。

Validator.ValidatePropertyメソッドを使用する

Validator静的クラスには妥当性検証を行うための静的メソッドがいくつか定義されています。

Validator クラス (System.ComponentModel.DataAnnotations)
オブジェクト、プロパティ、およびメソッドに関連付けられている ValidationAttribute に含めることで、これらを検証するために使用できるヘルパー クラスを定義します。

今回はプロパティの妥当性検証を行うためValidator.ValidatePropertyメソッドを使用しています。

その他のファイルの実装

XAMLファイルは以下のとおりです。

<Window x:Class="WpfValidatingData3.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
        xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
        xmlns:local="clr-namespace:WpfValidatingData3"
        mc:Ignorable="d"
        Title="MainWindow" Height="250" Width="800"
        ContentRendered="Window_ContentRendered">
    <Window.Resources>
        <Style TargetType="{x:Type TextBox}">
            <Style.Triggers>
                <Trigger Property="Validation.HasError" Value="True">
                    <Setter Property="Background" Value="LightPink"/>
                    <Setter Property="ToolTip" 
                            Value="{Binding RelativeSource={x:Static RelativeSource.Self}, 
                                                        Path=(Validation.Errors)[0].ErrorContent}"/>
                </Trigger>
            </Style.Triggers>
        </Style>
        <ControlTemplate x:Key="_errorTemplate">
            <StackPanel Orientation="Horizontal">
                <Border BorderBrush="Red" BorderThickness="2" Margin="0,0,6,0">
                    <AdornedElementPlaceholder x:Name="_el"/>
                </Border>
                <TextBlock Text="{Binding AdornedElement.(Validation.Errors)[0].ErrorContent, ElementName=_el}"
                           Foreground="Red" HorizontalAlignment="Right"
                           VerticalAlignment="Center" Margin="0,0,10,0"/>
            </StackPanel>
        </ControlTemplate>
    </Window.Resources>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="30"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
            <RowDefinition Height="Auto"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="380"/>
        </Grid.ColumnDefinitions>

        <TextBlock Grid.Row="1" Text="名前 : " FontSize="20" TextAlignment="Right" Margin="10"/>
        <TextBox x:Name="txtBoxName" Grid.Row="1" Grid.Column="1" 
                 Width="240" FontSize="20" HorizontalAlignment="Left"
                 Text="{Binding Name, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}"
                 Validation.ErrorTemplate="{StaticResource _errorTemplate}"
                 Margin="10"/>
        
        <TextBlock Grid.Row="2" Text="年齢 : " FontSize="20" TextAlignment="Right" Margin="10"/>
        <TextBox x:Name="txtBoxAge" Grid.Row="2" Grid.Column="1" 
                 Width="80" HorizontalAlignment="Left"  FontSize="20"
                 Text="{Binding Age, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}"
                 Validation.ErrorTemplate="{StaticResource _errorTemplate}"
                 Margin="10"/>
        
        <TextBlock Grid.Row="3" Text="Eメール : " FontSize="20" TextAlignment="Right" Margin="10"/>
        <TextBox x:Name="txtBoxEmail" Grid.Row="3" Grid.Column="1" FontSize="20"
                 Text="{Binding Email, ValidatesOnExceptions=True, UpdateSourceTrigger=PropertyChanged}"
                 Validation.ErrorTemplate="{StaticResource _errorTemplate}"
                 Margin="10"/>
    </Grid>
</Window>

TextBoxではBindingマークアップ拡張のValidatesOnExceptionsをtrueに設定する必要があります。

Window.Resourcesで定義しているAdornedElementPlaceholderを含むカスタムテンプレート_errorTemplateはTextBoxの右側にエラーメッセージを表示するためのものです。

詳細については以下の記事をご確認ください。

>> WPF データの妥当性検証をしたい

コードビハインドは以下のとおりです。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
using System.ComponentModel.DataAnnotations;

namespace WpfValidatingData3
{
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();

            DataContext = new Person();

            txtBoxName.Focus();
        }

        private void Window_ContentRendered(object sender, EventArgs e)
        {
            // ウィンドウ起動直後は3つのTextBoxは未入力のため妥当性検証を失敗させたい
            // そのため1度ダミーで空白文字を入力する

            txtBoxName.Text = " ";
            txtBoxName.Text = string.Empty;

            txtBoxAge.Text = " ";
            txtBoxAge.Text = string.Empty;

            txtBoxEmail.Text = " ";
            txtBoxEmail.Text = string.Empty;
        }
    }
}

おわりに

今回はアノテーションを使用した入力データの妥当性検証を行う方法を確認しました。

本記事で取り上げたRequired属性、StringLength属性、Range属性、EmailAddress属性以外にも色々な属性があります。

詳細は以下をご確認ください。

System.ComponentModel.DataAnnotations 名前空間
ASP.NET MVC および ASP.NET データ コントロールのメタデータを定義するために使用される属性クラスが用意されています。

ValidationRuleクラスを使用した妥当性検証については以下の記事をご確認ください。

>> WPF ValidationRuleクラスを使ってデータの妥当性検証をする

以上です。

コメント