SourceChord

C#とXAML好きなプログラマの備忘録。最近はWPF系の話題が中心です。

Prism.Mvvmを使ってみる・その2 ~ErrorsContainerの使い方~

↓の続きです。
Prism.Mvvmを使ってみる - SourceChord

Prism.Mvvmには、ErrorsContainerというINotifyDataErrorInfo実装のためのヘルパークラスがあります。
今回はこれを使ってみます。

f:id:minami_SC:20140611071555p:plain

INotifyDataErrorInfo実装時に、発生中のエラー情報を保持するためにDictionary型のメンバを用意したりします。これを簡単に扱えるようにするためのクラスがErrorsContainerです。

使い方

1. ErrorsContainerのフィールド作成

こんな風に、保持したいエラー情報の型を指定して、ErrorsContainerのフィールドを作ります。

private ErrorsContainer<string> errorsContainer;
2. コンストラクタで、ErrorsContainerのインスタンス作成

引数として、INotifyDataErrorInfoのErrorsChangedイベントを発行するためのメソッドを渡します。
こうすると、エラー内容に変化があったときに、ErrorsChangedイベントを通知してくれるようになります。

        public MainWindowViewModel()
        {
            errorsContainer = new ErrorsContainer<string>(OnErrorsChanged);
        }
3. INotifyDataErrorInfoの実装

INotifyDataErrorInfoを実装します。
ErrorsContainerを使って、以下のようにスッキリと実装します。

        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        public System.Collections.IEnumerable GetErrors(string propertyName)
        {
            return this.errorsContainer.GetErrors(propertyName);
        }

        public bool HasErrors
        {
            get { return this.errorsContainer.HasErrors; }
        }
4. 入力値検証用のメソッド作成

DataAnnotationsの機能を使って以下のような入力値の検証用メソッドを作ります。
(System.ComponentModel.DataAnnotationsの参照が必要になります。)

        protected void ValidateProperty(object value, [CallerMemberName]string propertyName = null)
        {
            var context = new ValidationContext(this) { MemberName = propertyName };
            var validationErrors = new List<ValidationResult>();
            if (!Validator.TryValidateProperty(value, context, validationErrors))
            {
                var errors = validationErrors.Select(error => error.ErrorMessage);
                this.errorsContainer.SetErrors(propertyName, errors);
            }
            else
            {
                this.errorsContainer.ClearErrors(propertyName);
            }
        }
5. プロパティに入力値検証のための実装を行う

DataAnnotationsのRequired属性を使って、プロパティが空の時に警告を出すようにしてます。
また、setterでは4.で作ったValidatePropertyメソッドを呼んで入力値の検証をするようにします。

        private string name;
        [Required(ErrorMessage = "何か入力してください")]
        public string Name
        {
            get { return name; }
            set { this.SetProperty(ref this.name, value); this.ValidateProperty(value); }
        }

サンプル・コード

MainWindow.xaml
<Window x:Class="PrismTest.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow"
        Width="300"
        Height="200">
    <Window.Resources>
        <ControlTemplate x:Key="ValidationTemplate">
            <StackPanel>
                <TextBlock Foreground="Red" Text="{Binding AdornedElement.(Validation.Errors)[0].ErrorContent, ElementName=adornedelem}" />
                <AdornedElementPlaceholder x:Name="adornedelem" />
            </StackPanel>
        </ControlTemplate>
    </Window.Resources>
    <StackPanel>
        <TextBlock Margin="20,10"
                   HorizontalAlignment="Left"
                   Text="{Binding Message}"
                   TextWrapping="Wrap" />
        <TextBox Width="120"
                 Height="23"
                 Margin="20,10"
                 HorizontalAlignment="Left"
                 Text="{Binding Name,
                                UpdateSourceTrigger=PropertyChanged}"
                 TextWrapping="Wrap"
                 Validation.ErrorTemplate="{StaticResource ValidationTemplate}" />
        <Button Width="75"
                Margin="20,10"
                HorizontalAlignment="Left"
                Command="{Binding ShowMessageCommand}"
                Content="Button" />
    </StackPanel>
</Window>
MainWindow.xaml.cs
    /// <summary>
    /// MainWindow.xaml の相互作用ロジック
    /// </summary>
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
            this.DataContext = new MainWindowViewModel();
        }
    }
MainWindowViewModel.cs
    class MainWindowViewModel : BindableBase, INotifyDataErrorInfo
    {
        private string name;
        [Required(ErrorMessage = "何か入力してください")]
        public string Name
        {
            get { return name; }
            set { this.SetProperty(ref this.name, value); this.ValidateProperty(value); }
        }

        private string message;
        public string Message
        {
            get { return message; }
            set { this.SetProperty(ref this.message, value); }
        }

        private DelegateCommand showMessageCommand;
        public DelegateCommand ShowMessageCommand
        {
            get { return showMessageCommand = showMessageCommand ?? new DelegateCommand(ShowMessage); }
        }

        private void ShowMessage()
        {
            this.Message = string.Format("こんにちは、 {0} さん", this.Name);
        }

        public MainWindowViewModel()
        {
            errorsContainer = new ErrorsContainer<string>(OnErrorsChanged);
            this.Name = string.Empty;
        }

        protected void ValidateProperty(object value, [CallerMemberName]string propertyName = null)
        {
            var context = new ValidationContext(this) { MemberName = propertyName };
            var validationErrors = new List<ValidationResult>();
            if (!Validator.TryValidateProperty(value, context, validationErrors))
            {
                var errors = validationErrors.Select(error => error.ErrorMessage);
                this.errorsContainer.SetErrors(propertyName, errors);
            }
            else
            {
                this.errorsContainer.ClearErrors(propertyName);
            }
        }


        #region INotifyDataErrorInfoの実装
        private ErrorsContainer<string> errorsContainer;
        protected void OnErrorsChanged([CallerMemberName]string propertyName = null)
        {
            var handler = this.ErrorsChanged;
            if (handler != null)
            {
                handler(this, new DataErrorsChangedEventArgs(propertyName));
            }
        }

        public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

        public System.Collections.IEnumerable GetErrors(string propertyName)
        {
            return this.errorsContainer.GetErrors(propertyName);
        }

        public bool HasErrors
        {
            get { return this.errorsContainer.HasErrors; }
        }
        #endregion
    }