ホーム > コンピュータ > C# > WPF in C# > Prism

ベクトルドロー・レベルゼロ+

Prism Library 5.0 for WPFを使用してMVVMパターンを実装する 02[C# WPF Prism]

概要

MVVMパターン調べていて、Prismというツールキットを見つけました。MVVMパターンを使用するためのもっとも人気のあるツールキットのようです。

原文

訳文

Implementing the MVVM Pattern Using the Prism Library 5.0 for WPF(外部サイト)

パターンと実践 ディベロパー・センター

Microsoft Prism Library 5.0 for WPFの開発者のガイドから:

クラスの相互作用

Class Interactions

MVVMパターンは、あなたのアプリケーションのユーザー・インターフェイス、そのプレゼンテーション・ロジック、 そして、そのビジネス・ロジックとデータの間に、別々のクラスにそれぞれを分離することによって、完全な分離を提供します。 その結果、あなたが、MVVMを実装するとき、前の項目で説明されているように、 あなたのアプリケーションのコードを、適切なクラスに要素の1つとして含めることは、重要です。

うまく設計されたView、View ModelとModelクラスは、コードの適切な型と動作だけをカプセル化しないでしょう。; また、それらは、データ結合、コマンド、そして、データ検証インターフェイスを通して、 簡単に、互いに相互作用することができるように、設計されるでしょう。

ビューとそのビュー・モデルの間の相互作用は、おそらく、考慮することが、最も重要です。 しかし、また、モデル・クラスとビュー・モデルの間の相互作用は、重要です。 次の項目では、あなたのアプリケーションで、MVVMパターンを実装するとき、これらの相互作用のさまざまなパターンを説明し、 そして、それらを、どのように設計するか、説明します。

データ結合

Data Binding

データ結合は、MVVMパターンで極めて重要な役割を演じます。WPFは、強力なデータ結合機能を提供します。 あなたのView Modelと(理想的な)あなたのモデル・クラスは、これらの機能を利用することができるように、データ結合をサポートするように設計されている必要があります。 一般的に、これは、それらが、適切なインターフェースを実装する必要があることを示しています。

WPFのデータ結合は、複数のデータ結合モードをサポートしています。一方向のデータ結合では、UIコントロールは、View Modelに結合することができます。 そのため、表示が、レンダリングされるとき、それらは、基盤となるデータの値を反映します。 また、双方向のデータ結合は、ユーザーが、UIでそれを修正するとき、基盤となるデータを自動的に更新します。

View Modelで、データが、変更されるとき、適切な変更通知インターフェイスを実装する必要があるUIは、データを確実に維持します。 それが、データ結合するプロパティを定義する場合、それは、INotifyPropertyChangedインターフェイスを実装する必要があります。 View Modelが、コレクションを示す場合、INotifyCollectionChangedインターフェイスを実装する、 あるいは、このインターフェイスの実装を提供する、ObservableCollection<T>クラスから派生する必要があります。 これらのイベントを定義する両方のインターフェイスは、基盤となるデータが変わるたびに、いつでも、発生させます。 どんな、結合されたコントロールのデータも、これらのイベントが、発生する時、自動的に、更新されるでしょう。

多くの場合、View Modelは、オブジェクトを返すプロパティを定義します(そして、順番に、追加されたオブジェクトを返す、プロパティを定義するかもしれません)。 WPFのデータ結合は、Pathプロパティを通して、入れ子にされたプロパティへの結合をサポートします。 その結果、ビューのView Modelが、他のView ModelやModelクラスに参照を返すことは、極めて一般的です。 ビューにアクセスできる、すべてのビュー・モデルとモデル・クラスは、適切なように、INotifyPropertyChangedや INotifyCollectionChangedインターフェイスを実装している必要があります。

次のセクションでは、MVVMパターン内でデータ結合をサポートするために、必要とされるインターフェイスを、どのように実装するかを説明します。

INotifyPropertyChangedを実装する

Implementing INotifyPropertyChanged

あなたのView Modelで、INotifyPropertyChangedインターフェイスを実装する、あるいは、モデル・クラスは、 基盤となるプロパティの値が変わるとき、それらに、ビュー内のどんなデータ結合されたコントロールにでも変更通知を提供できます。 次のコードの例に示すように、このインタフェースを実装すると、簡単です。


public class Questionnaire : INotifyPropertyChanged
{
    private string favoriteColor;
    public event PropertyChangedEventHandler PropertyChanged;
    ...
    public string FavoriteColor
    {
        get { return this.favoriteColor; }
        set
        {
            if (value != this.favoriteColor)
            {
                this.favoriteColor = value;

                var handler = this.PropertyChanged;
                if (handler != null)
                {
                    handler(this,
                          new PropertyChangedEventArgs("FavoriteColor"));
                }
            }
        }
    }
}

多くのView Modelクラスで、INotifyPropertyChangedインターフェイスを実装することは、 イベントの引数に、プロパティ名を指定する必要があるため、反復的なので、エラーが発生しやすくなります。Prismライブラリは、ここに、示されるように、 あなたが、タイプ・セーフな方法で、INotifyPropertyChangedインターフェイスを実装する、 View Modelクラスを派生できる、BindableBase基底クラスを提供しています。:


public abstract class BindableBase : INotifyPropertyChanged
{
   public event PropertyChangedEventHandler PropertyChanged;
   ...
   protected virtual bool SetProperty<T>(ref T storage, T value, 
                          [CallerMemberName] string propertyName = null)
   {...}
   protected void OnPropertyChanged<T>(
                          Expression<Func<T>> propertyExpression)
   {...}
 
   protected void OnPropertyChanged(string propertyName)
   {...}
}

派生したView Modelクラスは、SetPropertyメソッドを呼び出すことによって、セッターで、プロパティ変更イベントを発生させることができます。 SetPropertyメソッドは、既に、設定されている値から、支援領域が異なるかどうか調べます。 異なる場合、支援領域は、更新され、そして、PropertyChangedイベントが呼び出されます。

次のコードの例は、OnPropertyChangedメソッドのラムダ式を使用して、プロパティを設定し、同時に、他のプロパティの変更を、 どのように通知するかを示しています。この例は、株トレーダーRIを参考にしています。TransactionInfoとTickerSymbolプロパティは、関連があります。 TransactionInfoプロパティを変更する場合、また、TickerSymbolは、おそらく、TickerSymbolプロパティのOnPropertyChangedを呼び出すことによって、 TransactionInfoプロパティのセッターで、更新されるでしょう。2つのPropertyChangedイベントは、 TransactionInfoのために1つ、TickerSymbolのために1つ、呼び出されるでしょう。


public TransactionInfo TransactionInfo
{
    get { return this.transactionInfo; } 
    set 
    { 
         SetProperty(ref this.transactionInfo, value); 
         this.OnPropertyChanged(() => this.TickerSymbol);
    }
}

備考

このように、ラムダ式を使用することは、小さな処理コストが含まれています。 なぜなら、ラムダ式は、それぞれの呼び出しのために、評価する必要があります。 利点は、この方法は、コンパイルの際、型の安全性を提供することです。 そして、あなたが、プロパティ名を変更する場合、リファクタリングは、サポートしています。 処理コストは小さく、通常、あなたのアプリケーションに影響を与えませんが、あなたが、多くの変更通知を持つ場合、コストが生じます。 この場合、あなたは、非ラムダ・メソッドの上書きの使用を考える必要があります。

多くの場合、あなたのModelやView Modelは、ModelやView Model内の他のプロパティから値が計算される、プロパティが含まれているでしょう。 また、プロパティの変更を扱う際には、どんな計算されたプロパティのためでも、通知イベントを必ず発生させます。

INotifyCollectionChangedを実現する

Implementing INotifyCollectionChanged

あなたのView ModelやModelクラスは、項目のコレクションを表すかもしれません。 あるいは、それは、項目のコレクションを返す、1つ以上のプロパティを定義するかもしれません。 いずれにせよ、あなたが、ItemsControlで、コレクションを表示したくなるかもしれません。 ListBoxやビュー内のDataGridコントロールのような、これらのコントロールは、コレクション示す、 あるいは、ItemSourceプロパティ経由で、コレクションを返す、プロパティをView Modelに結合することができます。


<DataGrid ItemsSource="{Binding Path=LineItems}" />

それが、コレクションを表し、INotifyCollectionChangedインターフェイスを実装する場合、 適切にView ModelやModelクラスが要求する変更通知をサポートするために、(INotifyPropertyChangedインターフェイスに加えて)。 View ModelやModelクラスがプロパティを定義する場合、それは、コレクションに参照を返します。 返されるコレクション・クラスは、INotifyCollectionChangedインターフェイスを実装する必要があります。

しかしながら、INotifyCollectionChangedインターフェイスを実装することが、困難な場合があります。 なぜなら、それは、コレクション内の項目が、追加される、削除される、変更されるとき、通知を提供する必要があります。 それは、多くの場合、インターフェイスを実装しているディレクトリの代わりに、使用する、 あるいは、既に、それを実装している、コレクション・クラスから派生することが、さらに、簡単です。 ObservableCollection<T>クラスは、このインターフェイスの実装を提供します。 そして、一般的に、基底クラスか項目のコレクションを表す実装プロパティのどちらかとして、使用されます。

あなたが、データ結合のためのビューに、コレクションを提供する必要がある場合、 そして、あなたは、ユーザーの選択を追跡する、あるいは、フィルタリング、並べ替えやコレクション内の項目のグループ化をサポートする必要は、ありません。 あなたは、単純に、ObservableCollectionインスタンスに参照を返す、View Modelでプロパティを定義することができます。


public class OrderViewModel : BindableBase
{
    public OrderViewModel( IOrderService orderService )
    {
        this.LineItems = new ObservableCollection(
                               orderService.GetLineItemList() );
    }

    public ObservableCollection LineItems { get; private set; }
}

あなたが、コレクション・クラスの参照を取得する場合、(例えば、他のコンポーネントやINotifyCollectionChangedを実装しないサービスから)、 あなたは、多くの場合、コンストラクタの1つを使用して、ObservableCollection<T>インスタンスで、そのコレクションを包むことができます。 IEnumerable<T>やList<T>パラメータを取得します。

備考

BindableBaseは、Microsoft.Practices.Prism.Mvvm名前空間で見つかります。 それは、Prism.Mvvm NuGetパッケージで配置されます。

ICollectionViewを実装する

Implementing ICollectionView

前述のコードの例は、ビューで、コントロールに結合されたデータを通して表示することができる項目のコレクションを返す、 単純なView Modelプロパティを、どのように実行するかを示します。ObservableCollection<T>クラスが、 INotifyCollectionChangedインターフェイスを実装するため、ビュー内のコントロールは、 自動的に、項目が追加、あるいは、削除されているか、コレクション内の項目の現在のリストを反映して、更新されます。

しかしながら、あなたは、多くの場合、ビュー内で表示される項目のコレクションを、さらに詳細に制御することが必要だと思います。 あるいは、その中のView Modelそれ自身で、表示された項目のコレクションで、ユーザーの相互作用を追跡します。 例えば、あなたは、View Model内で、プレゼンテーション・ロジックで実装された、 フィルターリングや並べ替えられている項目のコレクションを提供する必要があるかもしれません。 あるいは、あなたは、ビュー内で、現在選択された項目を追跡する必要があるかもしれません。 そのため、View Modelで実装されるコマンドは、現在選択された項目に基づいて行動することができます。

WPFは、ICollectionViewインターフェイスを実装する、さまざまなクラスを提供することによって、 これらの筋書きをサポートします。このインターフェイスは、フィルターをかける、並べ替える、 あるいは、グループ化するコレクションを提供するために、プロパティとメソッドを提供します。 そして、追跡や変更するために、現在選択された項目を提供します。 WPFは、ListCollectionViewクラスを使用して、このインターフェイスの実装を提供します。

コレクションのViewクラスは、項目の基盤となるコレクションを包むことによって動作します。 そのため、それらは、自動に選択するトラッキングと並べ替え、フィルタリングとページングを提供することができます。 これらのクラスのインスタンスは、CollectionViewSourceクラスを使用しているXAML内で、 プログラム上で、または、宣言的に作成することができます。

備考

WPFでは、既定のコレクション・ビューは、 実際に、コントロールがコレクションに結合されるたびに、自動的に作成されます。

コレクション・ビュー・クラスは、関係の明確な分離を維持している間、ビュー内のUIとモデル内の基盤となるデータの間で、 基盤となるコレクションのための重要な状態情報を追跡するために、View Modelで使用することができます。 実質的に、CollectionViewsは、View Modelです。それは、特に、コレクションをサポートするように設計されています。

その結果、あなたが、コレクション内の、あなたのView Modelで、項目のフィルタリング、並べ替え、 グループ化や選択トラッキングを実装する必要がある場合、あなたのView Modelは、ビューに公開されている、 それぞれのコレクションのために、コレクション・ビュー・クラスのインスタンスを作成する必要があります。 あなたは、続いて、あなたのView Model内から、コレクション・ビュー・クラスによって、 CurrentChangedイベント、あるいは、コントロールのフィルタリング、並べ替え、 提供されるメソッドを使用するグループ化のように、選択変更イベントに登録することができます。

View Modelは、ICollectionViewの参照を返す、読取専用プロパティを実装する必要があります。 そのため、ビューのコントロールは、コレクション・ビュー・オブジェクトにデータ結合、そして、相互作用することができます。 ItemsControl基底クラスから派生する、 すべてのWPFコントロールは、自動的に、ICollectionViewクラスと相互作用することができます。

次のコードの例は、WPFで、現在選択した利用者を追跡するために、ListCollectionViewの使用を示しています。


public class MyViewModel : BindableBase
{
    public ICollectionView Customers { get; private set; }

    public MyViewModel( ObservableCollection<Customer> customers )
    {
        // Initialize the CollectionView for the underlying model
        // and track the current selection.
        // 基盤となるモデルのためのCollectionViewと現在の選択内容の追跡を初期化する。
        Customers = new ListCollectionView( customers );
        
        Customers.CurrentChanged +=SelectedItemChanged;
    }

    private void SelectedItemChanged( object sender, EventArgs e )
    {
        Customer current = Customers.CurrentItem as Customer;
        ...
    }
    ...
}

ここに、示されるように、ビューで、あなたは、続いて、ListBoxのような、そのItemsSourceプロパティを通じて、 View ModelのCustomersプロパティに、ItemsControlを結合することができます。:


<ListBox ItemsSource="{Binding Path=Customers}">
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel>
                <TextBlock Text="{Binding Path=Name}"/>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

ユーザーが、UIで利用者を選択するとき、View Modelは、現在選択された利用者に関係するコマンドが、 適用することができることを知らされます。 次のコードの例に示すように、また、View Modelは、UIで、コレクション・ビュー・オブジェクトでメソッドを呼び出すことによって、 現在の選択した内容をプログラム上で変更することができます。


Customers.MoveCurrentToNext();

選択がコレクション・ビューで変更されると、UIは、自動的に、見えるように表示するために、項目の選択された状態を更新します。

コマンド

Commands

ビューで表示する、あるいは、編集するデータにアクセスを提供することに加えて、View Modelは、おそらく、ユーザーによって実行することができる、 1つ以上の動作や操作を定義します。WPFでは、ユーザーが、UIによって実行することができる動作や操作は、一般的に、コマンドとして定義されています。 コマンドは、動作や操作を表示するために、簡単に、UIで、コントロールに結合することができる、便利な方法を提供します。 それらは、動作や操作を実装する、実際のコードをカプセル化します。 そして、それを、ビュー内で、その実際の外観の表示から、分離し続けるのを助けます。

コマンドは、それらがビューと対話するように、ユーザーによって多くの異なる方法で、視覚的に表示する、そして、呼び出すことができます。 ほとんどの場合、それらは、マウスクリックの結果として呼び出されます。 しかし、また、それらは、ショートカット・キー押す、タッチ・ジェスチャー、あるいは、他の入力イベントの結果として呼び出すことができます。 ViewのControlは、View Modelのコマンドに結合されるデータです。 そのため、ユーザーは、どんな入力イベントやコントロールで定義するジェスチャーでも使用して、それらを呼び出すことができます。 ビューのUIコントロールの間の相互作用とコマンドは、双方向にすることができます。 この場合、コマンドは、ユーザーがUIと対話するように、呼び出すことができます。 そして、UIは、基盤となるコマンドが、有効や無効になるように、自動的に、有効や無効にすることができます。

View Modelは、どちらのCommandメソッドとして、あるいは、Commandオブジェクトとしてでも、コマンドを実装することができます。 (オブジェクトは、ICommandインターフェイスを実装する)。 いずれにせよ、コマンドに対するビューの相互作用は、宣言的に定義することができます。 ビューのコード・ビハインド・ファイルで、複雑なイベント処理コードを必要とすることなく、 例えば、WPFの特定のコントロールは、本質的に、コマンドをサポートして、View Modelで、 提供されるICommandオブジェクトにデータ結合することができるCommandプロパティを提供します。 他の場合には、コマンドの動作は、View Modelによって提供される、 コマンドのメソッドやコマンドのオブジェクトを、コントロールに関連付けるために使用することができます。

備考

ビヘイビアは、相互作用ロジックと動作をカプセル化するために使用することができる、 強力で柔軟な拡張の仕組みです。それは、続いて、ビューでコントロールに宣言的に関連付けることができます。 コマンドの動作は、コマンドと相互作用するように特に設計されていなかったコントロールで、 コマンド・オブジェクトやメソッドを、関連付けるために使用することができます。

次のセクションは、あなたのビューで、コマンドを、コマンド・メソッドとして、 または、コマンド・オブジェクトとして、どのように実装するか、そして、ビューでコントロールを、どのように関連付けるかを説明します。

作業に基づいたデリゲート・コマンドを実装する

Implementing a Task-Based Delegate Command

コマンドは、UIスレッドをブロックすることができない、実行時間の長いトランザクションで、 コードを呼び出す多くの筋書きがあります。これらの筋書きのために、 あなたは、非同期ハンドラ・メソッドから、 DelegateCommandの新しいインスタンスを作成する、DelegateCommandクラスの、 FromAsyncHandlerメソッドを使用する必要があります。


// DelegateCommand.cs
public static DelegateCommand FromAsyncHandler(Func<Task> executeMethod, Func<bool> canExecuteMethod)
{
    return new DelegateCommand(executeMethod, canExecuteMethod);
}

例えば、次のコードは、デリゲートを、SignInAsyncとCanSignInのビュー・モデルのメソッドに、 指定することによって構築される、コマンドの記号を表す、DelegateCommandが、どのようにインスタンスを作成するかを示します。 コマンドは、続いて、ICommandに参照を返す、読取専用プロパティを通してビューに公開されます。


// SignInFlyoutViewModel.cs
public DelegateCommand SignInCommand { get; private set;  }

...
SignInCommand = DelegateCommand.FromAsyncHandler(SignInAsync, CanSignIn);

コマンド・オブジェクトを実装する

Implementing Command Objects

コマンド・オブジェクトは、ICommandインターフェイスを実装するオブジェクトです。 このインターフェイスは、操作それ自身とコマンドが、特定の時間に呼び出されることができるかどうかを示す、 CanExecuteメソッドをカプセル化する、Executeメソッドを定義します。 これらのメソッドの両方は、コマンドのためのパラメータとして、一つの引数を取得します。 コマンド・オブジェクトの操作の実装ロジックのカプセル化は、さらに簡単に、ユニットをテストし、保守することができることを示します。

ICommandインターフェイスを実装するのは、簡単です。 しかしながら、あなたが、すぐに、アプリケーションの中で使用するための、このインターフェイスのいくつかの実装があります。 例えば、あなたは、Visual Studio SDKのために、Blendから、 ActionCommandクラスを使用することができます。 あるいは、DelegateCommandクラスは、Prismによって、提供されます。

備考

DelegateCommandは、Prism.Mvvm NuGetパッケージで配置される、Microsoft.Practices.Prism.Mvvm名前空間で見つかります。

PrismのDelegateCommandクラスは、メソッドのそれぞれの参照は、あなたのView Modelクラスの範囲内で、それを実装した、 2つのデリゲートをカプセル化します。それは、これらのデリゲートを呼び出すことによって、 ICommandインターフェイスのExecuteとCanExecuteメソッドを実装する、DelegateCommandBaseクラスから継承します。 あなたは、次のように定義されている、DelegateCommandクラス・コンストラクタ内の、View Modelメソッドにデリゲートを指定します。


// DelegateCommand.cs
public class DelegateCommand<T> : DelegateCommandBase
{
    public DelegateCommand(Action<T> executeMethod,Func<T,bool> canExecuteMethod ): base((o) => executeMethod((T)o), (o) => canExecuteMethod((T)o))
    {
        ...
    }
}

例えば、次のコードの例は、Submitコマンドを示すDelegateCommandインスタンスを、どのように、作成するかを示します。 デリゲートを、OnSubmitとCanSubmitのView Modelメソッドに指定することによって構築されます。 コマンドは、続いて、ICommandに参照を返す、読取専用プロパティを通してビューに公開されます。


public class QuestionnaireViewModel
{
    public QuestionnaireViewModel()
    {
       this.SubmitCommand = new DelegateCommand<object>(
                                        this.OnSubmit, this.CanSubmit );
    }
 
    public ICommand SubmitCommand { get; private set; }

    private void OnSubmit(object arg)   {...}
    private bool CanSubmit(object arg)  { return true; }
}

Executeメソッドが、DelegateCommandオブジェクトに呼び出されるとき、それは、単純に、あなたが、コンストラクタ内で指定するデリゲートを通して、 あなたのView Modelクラス内のメソッドに呼び出しを転送します。 同様に、CanExecuteメソッドが、呼び出されるとき、あなたのView Modelクラスの対応するメソッドは、呼び出されます。 コンストラクタ内のCanExecuteメソッドへのデリゲートは、選択可能です。 デリゲートが、指定されない場合、DelegateCommandは、常に、CanExecuteのためにtrueを返します。

DelegateCommandクラスは、ジェネリック型です。 型引数は、ExecuteとCanExecuteメソッドに渡されるコマンド・パラメータの型を指定します。前述の例では、コマンド・パラメータは、型オブジェクトです。 コマンド・パラメータが、必要とされていないとき、また、DelegateCommandクラスの非ジェネリックのバージョンは、使用するために、Prismによって提供されます。

View Modelは、DelegateCommandオブジェクトで、RaiseCanExecuteChangedメソッドを呼び出すことによって、 コマンドのCanExecuteステータスで変化を示すことができます。 これは、CanExecuteChangedイベントの発生をもたらします。コマンドに結合した、 UIのどんなコントロールでも、結合されたコマンドの利用を反映するために、それらの有効ステータスを更新するでしょう。

ICommandインターフェイスの他の実装は、利用できます。Expression Blend SDKで提供されるActionCommandクラスは、 先に説明した、PrismのDelegateCommandクラスに似ています。しかし、それは、一つのExecuteメソッド・デリゲートだけをサポートします。 また、Prismは、CompositeCommandクラスを提供します。それは、DelegateCommandsが、実行するために互いにグループ化できます。 CompositeCommandクラスを使用することに関する詳細は、 「高度なMVVMの筋書き」(原文リンク) で「複数の要素で構成されたコマンド」(原文リンク)を参照してください。

ビューからコマンド・オブジェクトを呼び出す

Invoking Command Objects from the View

ビュー内のコントロールのいくつかの方法は、View Modelで提供されるコマンド・オブジェクトに関連付けることができます。 ButtonやRadioButton、そして、HyperlinkやMenuItemから派生したコントロールのような、 特定のWPFコントロール、特にButtonBaseから派生したコントロール、簡単に、Commandプロパティを通して、コマンド・オブジェクトにデータ結合できます。 また、WPFは、View Model ICommandをKeyGestureに結合することをサポートします。


<Button Command="{Binding Path=SubmitCommand}" CommandParameter="SubmitOrder"/>

また、コマンド・パラメータは、必要に応じて、CommandParameterプロパティを使用して、定義することができます。 期待される引数の型は、ExecuteとCanExecuteの対象メソッドを指定します。 コントロールは、ユーザーがそのコントロールとコマンド・パラメータで相互作用するとき、対象とするコマンドを自動的に呼び出します。 提供される場合、コマンドのExecuteメソッドへの引数に渡されるでしょう。 前述の例では、ボタンは、自動的に、それがクリックされるSubmitCommandを呼び出します。 さらに、CanExecuteが、falseを返すと、CanExecuteハンドラが、指定される場合、ボタンは、自動的に無効になります。 そして、それが、trueを返す場合、それは、有効になるでしょう。

Visual Studio 2013のBlendを使用するための代わりの方法は、 相互作用を起動する、そして、InvokeCommandAction動作です。 イベントのInvokeCommandAction動作と関連するコマンドの詳細については、 「高度なMVVMの筋書き」(原文リンク)の中の「トリガとコマンドの相互作用」(原文リンク)参照してください。

データ検証とエラー報告

Data Validation and Error Reporting

あなたのView modelやModelは、多くの場合、データ検証を実行し、 ビューにどんなデータ検証エラーでも通知することを要求します。 そのため、ユーザーは、それらを修正するために作用させることができます。

WPFは、ビューのコントロールに結合する、それぞれのプロパティを変更するとき、 発生するデータ検証エラーを管理するためのサポートを提供します。 コントロールにデータ結合するための、一つのプロパティは、View modelやModelは、 プロパティ・セッターの範囲内でデータ検証エラーを知らせることができます。 入力される無効な値を拒否して、例外を投げることによって、データ結合のValidatesOnExceptionsプロパティが、 trueの場合、WPFのデータ結合エンジンは、例外を処理し、 そして、データ検証エラーがあることをユーザーに、視覚的な合図を表示します。

しかしながら、このように、プロパティで例外を投げることは、可能な限り、避ける必要があります。 代わりの方法は、あなたのView ModelやModelクラスで、IDataErrorInfoやINotifyDataErrorInfoインターフェイスを実装することです。 これらのインターフェイスは、データ検証を実行するために、 1つ以上のプロパティ値のために、そして、ビューにエラーメッセージを返すために、 ユーザーが、エラーを通知することができるように、あなたのView modelやModelを提供します。

IDataErrorInfoを実装する

Implementing IDataErrorInfo

IDataErrorInfoインターフェイスは、プロパティのデータ検証のための基本的なサポートとエラー報告を提供します。 それは、2つの読取専用プロパティを定義します。:indexerプロパティ、 indexer引数としてプロパティ名、そして、Errorプロパティ。両方のプロパティは、文字列の値を返します。

indexerプロパティは、名前を付けたプロパティに具体的なエラーメッセージを提供するために、View ModelやModelクラスを提供します。 空の文字列やnull戻り値は、変更されたプロパティの値が有効であることをビューに示します。Errorプロパティは、View ModelやModelクラスに、 全オブジェクトのためのエラーメッセージを提供することができます。 しかしながら、このプロパティは、WPFのデータ結合エンジンで、現在、それから、呼び出されないことに注意してください。

データ結合されたプロパティが、最初に表示されるとき、 そして、それが、その後、変更されるたびに、IDataErrorInfo indexerプロパティがアクセスされます。 indexerプロパティが、変更されるすべてのプロパティのために呼び出されるため、 あなたは、データ検証が、可能な限り速く、効率的であることを注意深く確認する必要があります。

あなたが、ビューのコントロールをプロパティに結合するとき、あなたは、データ結合のValidatesOnDataErrorsプロパティを、 trueに設定する、IDataErrorInfoインターフェイスを通して、検証することを望みます。 これは、データ結合エンジンが、データ結合されたプロパティのための、エラー情報を要求することを確認するでしょう。


<TextBox
Text="{Binding Path=CurrentEmployee.Name, Mode=TwoWay, ValidatesOnDataErrors=True, NotifyOnValidationError=True }"
/>

INotifyDataErrorInfoを実装する

Implementing INotifyDataErrorInfo

INotifyDataErrorInfoインターフェイスは、IDataErrorInfoインターフェイスに比べてより柔軟です。 それは、プロパティの複数のエラー、非同期のデータ検証、そして、エラー状態が、オブジェクトのために変更される場合、 ビューに通知する機能をサポートしています。

INotifyDataErrorInfoインターフェイスは、View Modelが、プロパティに存在するエラー(または複数のエラー)、 そして、View Modelが、特定のプロパティのために、エラーメッセージのリストを返すことができる、 GetErrorsメソッドのどちらかを示すことができる、HasErrorsプロパティを定義します。

また、INotifyDataErrorInfoインターフェイスは、ErrorsChangedイベントを定義します。 これは、ErrorsChangedイベントを通して、特定のプロパティのためのエラー状態の変更を知らせるために、 ViewやView Modelを提供することによって、非同期の妥当性検証の筋書きをサポートします。 プロパティ値は、データ結合を通してではない、いくつかの方法で変更することができます。 -例えば、webサービスコールやバックグラウンド計算の結果として、ErrorsChangedイベントは、 一旦、データ検証エラーが識別される場合、View Modelは、ビューにエラーを知らせることができます。

INotifyDataErrorInfoに対応するために、あなたは、それぞれのプロパティのためのエラー・リストを維持する必要があるでしょう。 Model-View-ViewModelの参考になる実装(MVVM RI)は、オブジェクト内の、すべての妥当性検証エラーを追跡する、 ErrorsContainerコレクション・クラスを使用して行うために、片方向を説明します。 また、それは、エラー・リストが変更される場合、通知イベントを発生させます。 次に示すコードの例は、DomainObject(ルート・モデル・オブジェクト)を示し、 そして、ErrorsContainerクラスを使用する、INotifyDataErrorInfoの実装の例を示します。


public abstract class DomainObject : INotifyPropertyChanged, 
                                     INotifyDataErrorInfo
{
    private ErrorsContainer<ValidationResult> errorsContainer =
                    new ErrorsContainer<ValidationResult>(
                       pn => this.RaiseErrorsChanged( pn ) );

    public event EventHandler<DataErrorsChangedEventArgs> ErrorsChanged;

    public bool HasErrors
    {
        get { return this.ErrorsContainer.HasErrors; }
    }
 
    public IEnumerable GetErrors( string propertyName )
    {
        return this.errorsContainer.GetErrors( propertyName );
    }

    protected void RaiseErrorsChanged( string propertyName )
    {
        var handler = this.ErrorsChanged;
        if (handler != null)
        {
            handler(this, new DataErrorsChangedEventArgs(propertyName) );
        }
    }
   ...
}

Copyright (C) 2011-2016 kukekko All Rights Reserved.
kukekko@gmail.com
ご連絡の際はアドレスの@は半角にしてください。 また、お問い合わせページのURLの明記をお願いします。
「掲載内容は私自身の見解であり、所属する組織を代表するものではありません 」。
inserted by FC2 system