かずきのBlog@hatena

すきな言語は C# + XAML の組み合わせ。Azure Functions も好き。最近は Go 言語勉強中。日本マイクロソフトで働いていますが、ここに書いていることは個人的なメモなので会社の公式見解ではありません。

IDataErrorInfoの汎用的な実装

INotifyDataErrorInfoの実装は、前の記事でやりました。
http://d.hatena.ne.jp/okazuki/20100418/1271594953

ただ、INotifyDataErrorInfoはSilverlight4にしかないので、WPF4だとIDataErrorInfoとかプロパティでの例外で入力値の検証をしないといけません。
とりあえずIDataErrorInfoを汎用的?に実装するとしたらというのをでっちあげてみました。Errorプロパティがちょっと悩みどころですが・・・

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;

namespace WpfApplication1
{
    public class ViewModelBase : INotifyPropertyChanged, IDataErrorInfo
    {
        public event PropertyChangedEventHandler PropertyChanged;
        protected virtual void OnPropertyChanged(string propertyName)
        {
            var h = PropertyChanged;
            if (h == null) return;
            h(this, new PropertyChangedEventArgs(propertyName));
        }

        public string Error
        {
            get 
            {
                // ここ何て実装していいのか悩みどころ、とりあえず全エラー連結してみた
                var results = new List<ValidationResult>();
                if (Validator.TryValidateObject(
                    this,
                    new ValidationContext(this, null, null),
                    results))
                {
                    return string.Empty;
                }
                return string.Join(Environment.NewLine, results.Select(r => r.ErrorMessage));
            }
        }

        public string this[string columnName]
        {
            get 
            {
                // TryValidatePropertyで、プロパティを検証
                var results = new List<ValidationResult>();
                if (Validator.TryValidateProperty(
                    GetType().GetProperty(columnName).GetValue(this, null),
                    new ValidationContext(this, null, null) { MemberName = columnName },
                    results))
                {
                    return null;
                }
                // エラーがあれあ最初のエラーを返す
                return results.First().ErrorMessage;
            }
        }
    }
}

こんな感じで使います

using System.ComponentModel.DataAnnotations;

namespace WpfApplication1
{
    public class PersonViewModel : ViewModelBase
    {
        private string _fullName;
        [Required(ErrorMessage = "名前を入力してね")]
        public string FullName
        {
            get { return _fullName; }
            set
            {
                _fullName = value;
                OnPropertyChanged("FullName");
            }
        }
    }
}

後はWindowにバインドしえ適当にエラーを表示するように細工すると

<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:WpfApplication1"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <l:PersonViewModel />
    </Window.DataContext>
    <Grid>
        <TextBox Height="24" HorizontalAlignment="Left" Margin="12,12,0,0" Name="textBox1" VerticalAlignment="Top" Width="120" Text="{Binding Path=FullName, ValidatesOnDataErrors=True}"
                 ToolTip="{Binding ElementName=textBox1, Path=(Validation.Errors)[0].ErrorContent}"/>
        <Button Content="Button" Height="23" HorizontalAlignment="Left" Margin="12,42,0,0" Name="button1" VerticalAlignment="Top" Width="75" />
        <TextBlock Height="23" HorizontalAlignment="Left" Margin="138,13,0,0" Name="textBlock1" Text="{Binding ElementName=textBox1, Path=(Validation.Errors)[0].ErrorContent}" VerticalAlignment="Top" />
    </Grid>
</Window>

こんな感じでエラーが表示されるようになります。