かずきのBlog@hatena

日本マイクロソフトに勤めています。XAML + C#の組み合わせをメインに、たまにASP.NETやJavaなどの.NET系以外のことも書いています。掲載内容は個人の見解であり、所属する企業を代表するものではありません。

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>

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