WPF_IDataErrorInfoインターフェース

IDataErrorInfoインターフェース

IDataErrorInfoインターフェースとは

IDataErrorInfoインターフェースは、バインドしているプロパティの検証エラーを表示する方法を提供します。


最初期のMVVMパターンでは、ViewModelに「INotifyPropertyChanged」と「IDataErrorInfo」の2つのインターフェイスを実装することが求められていました。

さらに、IDataErrorInfo に通知イベントを発生する仕組みを付与した「INotifyDataErrorInfo インターフェイス」がWPF4.5で追加されています。

しかしながら、どちらも使い勝手がいいとはいえなく、有名なエラー機能ライブラリを利用する場合が多いといえます。


System.ComponentModel.IDataErrorInfo


// ユーザー インターフェイスにバインドできるカスタム エラー情報を提供する機能を提供します。
public interface IDataErrorInfo
{
    // 指定した名前を持つプロパティのエラー メッセージを取得します。
    string this[string columnName] { get; }

    // このオブジェクトに関する問題を示すエラー メッセージを取得します。
    string Error { get; }
}

Modelで検証してはいけないのか?

MVVMにおいてUI操作(ユーザー入力など)の検証はViewModelで行い、ビジネスロジックの検証ならばModelで行うことを基本ルールとしています。

例えば、ログインフォームを例にすると、入力規則(文字列パターン)判定をViewModelで実施して、パスワード照会をModelで行う方法です。

しかし、セキュリティ観点からはViewModelの検証は脆弱性を生む可能性がありますので、Modelのみで検証処理を行うことは合理的といえます。


IDataErrorInfoインターフェース実装例

xaml

バインディング時に「ValidatesOnDataErrors」をtrueにします。


<Window x:Class="WpfDevelop.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:vm="clr-namespace:WpfDevelop"
        mc:Ignorable="d"
        Title="MainWindow" Height="240" Width="360">
    <Window.Resources>
        <!--エラーテンプレートの定義-->
        <ControlTemplate x:Key="ErrorTemplate">
            <DockPanel>
                <!--TextBoxの上側にエラーメッセージを表示-->
                <TextBlock DockPanel.Dock="Top" Foreground="Red" Margin="0"
                           Text="{Binding ElementName=adornedElement, Path=AdornedElement.(Validation.Errors)[0].ErrorContent}"/>
                <!--AdornedElementPlaceholderはErrorTemplateが適用される要素を指す-->
                <Border BorderBrush="Red" BorderThickness="1"
                        Width="{Binding ElementName=adornedElement, Path=ActualWidth}"
                        Height="{Binding ElementName=adornedElement, Path=ActualHeight}">
                    <AdornedElementPlaceholder Name="adornedElement"/>
                </Border>
            </DockPanel>
        </ControlTemplate>
    </Window.Resources>

    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="1*"/>
            <RowDefinition Height="auto"/>
            <RowDefinition Height="1*"/>
        </Grid.RowDefinitions>
        <Grid.ColumnDefinitions>
            <ColumnDefinition Width="Auto"/>
            <ColumnDefinition Width="200"/>
        </Grid.ColumnDefinitions>
        <TextBlock Grid.Column="0" Grid.Row="1" Margin="0,0,5,0" Text="TextBox(IDataErrorInfo):"/>
        <TextBox Grid.Column="1" Grid.Row="1"
                 Validation.ErrorTemplate="{StaticResource ErrorTemplate}"
                 Text="{Binding BindText, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}"/>
    </Grid>
</Window>

ViewModel

ViewModelにIDataErrorInfoを継承します。


namespace WpfDevelop
{
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;

    public class MainWindowViewModel : ViewModelBase, IDataErrorInfo
    {
        /// <summary> 
        /// プロパティに紐づいたエラーメッセージを格納します。 
        /// </summary> 
        private Dictionary<string, string> Errors = new Dictionary<string, string>();

        private int _bindText;

        public int BindText
        {
            get { return _bindText; }
            set
            {
                if (_bindText == value)
                {
                    return;
                }
                _bindText = value;

                // データ検証
                Errors["BindText"] = value < 0 ? "0以上の数値を入力してください。" : null;

                OnPropertyChanged();
            }
        }

        public string Error
        {
            get { throw new NotImplementedException(); }
        }

        /// <summary> 
        /// columnNameで指定したプロパティのエラーを返します。 
        /// </summary> 
        public string this[string columnName]
        {
            get
            {
                return Errors.ContainsKey(columnName) ? Errors[columnName] : null;
            }
        }
    }
}

備考

xaml.cs


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

            MainWindowViewModel vm = new MainWindowViewModel();
            this.DataContext = vm;
        }
    }
}

ViewModelBase


namespace WpfDevelop
{
    using System.ComponentModel;
    using System.Runtime.CompilerServices;

    /// <summary>
    /// ViewModel 基底クラス を表現します。
    /// </summary>
    public class ViewModelBase : INotifyPropertyChanged
    {
        /// <summary>
        /// プロパティ値が変更されたことをクライアントに通知します。
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        /// <summary>
        /// PropertyChanged イベント を発生させます。
        /// </summary>
        /// <param name="propertyName">変更されたプロパティの名前</param>
        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }
    }
}

関連ページ