Mvvm-quick-guide
MVVM –はじめに
コードを整理するための、よく整理された、おそらく最も再利用可能な方法は、「MVVM」パターンを使用することです。 * Model、View、ViewModel(MVVMパターン)*は、メンテナンス可能、テスト可能、および拡張可能なアプリケーションを作成するためのコードの整理と構造化の方法をガイドするものです。
モデル-データを保持するだけで、ビジネスロジックとは関係ありません。
*ViewModel* -モデルとビューの間のリンク/接続として機能し、見た目を美しくします。
表示-フォーマットされたデータを保持し、基本的にすべてをモデルに委任します。
分離プレゼンテーション
アプリケーションロジックを分離コードまたはXAMLに配置することによって生じる問題を回避するには、分離プレゼンテーションと呼ばれる手法を使用するのが最善です。 ユーザーインターフェイスオブジェクトを直接操作するために最低限必要なXAMLとコードビハインドがある場合、これを回避しようとしています。 ユーザーインターフェイスクラスには、次の左側の図に示すように、複雑な対話動作、アプリケーションロジック、およびその他すべてのコードも含まれています。
- 分離されたプレゼンテーションでは、ユーザーインターフェイスクラスがはるかに簡単です。 もちろんXAMLがありますが、コードビハインドは実用的ではありません。
- アプリケーションロジックは、多くの場合モデルと呼ばれる別のクラスに属します。
- ただし、これはすべてではありません。 ここでやめた場合、データバインディングの狂気の道をたどる非常に一般的な間違いを繰り返す可能性があります。
- 多くの開発者は、データバインディングを使用してXAMLの要素をモデルのプロパティに直接接続しようとします。
- 今ではこれで問題ない場合もありますが、そうでない場合もあります。 問題は、モデルがユーザーのアプリケーションとのやり取りではなく、アプリケーションの動作に完全に関係していることです。
- データを提示する方法は、多くの場合、内部的に構造化されている方法とは多少異なります。
- さらに、ほとんどのユーザーインターフェイスには、アプリケーションモデルに属さない状態があります。
- たとえば、ユーザーインターフェイスでドラッグアンドドロップを使用している場合、ドラッグ中のアイテムが現在どこにあるか、可能なドロップターゲット上を移動するときに外観がどのように変化するか、ドロップターゲットがどのようになり得るかなどを追跡する必要がありますアイテムがそれらの上にドラッグされると変化します。
- この種の状態は驚くほど複雑になる可能性があるため、徹底的にテストする必要があります。
- 実際には、通常、ユーザーインターフェイスとモデルの間に他のクラスを配置する必要があります。 これには2つの重要な役割があります。
- まず、アプリケーションモデルを特定のユーザーインターフェイスビューに適合させます。
- 第二に、重要な相互作用ロジックが存在する場所であり、それによって、ユーザーインターフェイスを希望どおりに動作させるために必要なコードを意味します。
MVVM –利点
MVVMパターンは、最終的にはMVCパターンの最新の構造であるため、ドメインロジックとプレゼンテーションレイヤーを明確に分離するという主な目標は同じです。 MVVMパターンの利点と欠点のいくつかを以下に示します。
主な利点は、ビューとモデルを真に分離できることであり、分離とそれを利用することで得られる効率を達成することです。 つまり、モデルを変更する必要がある場合、ビューを必要とせずにモデルを簡単に変更でき、その逆も同様です。
MVVMの適用から流出する3つの重要な重要事項は次のとおりです。
保守性
- さまざまな種類のコードを完全に分離することで、よりきめ細かく焦点を絞った部分の1つまたは複数に簡単にアクセスし、心配することなく変更を加えることができます。
- つまり、機敏性を維持し、新しいリリースにすばやく移行できます。
テスト容易性
- MVVMを使用すると、各コードはよりきめ細かく、適切に実装された場合、外部および内部の依存関係は、テストするコアロジックを持つパーツとは別のコードになります。
- これにより、コアロジックに対する単体テストを簡単に作成できます。
- 書かれたときに正しく機能し、メンテナンスで状況が変わっても機能し続けることを確認してください。
拡張性
- 明確な分離境界と、よりきめ細かいコード片のため、保守性と重なる場合があります。
- これらのパーツを再利用可能にする可能性が高くなります。
- また、アーキテクチャ内の適切な場所に同様のことを行う新しいコードを置換または追加する機能も備えています。
MVVMパターンの明白な目的は、ビューの抽象化であり、分離コードのビジネスロジックの量を減らします。 しかし、以下はいくつかの他の堅実な利点です-
- ViewModelは、コードビハインドコードまたはイベント駆動型コードよりも単体テストが簡単です。
- 厄介なUIの自動化と対話なしでテストできます。
- プレゼンテーション層とロジックは疎結合です。
デメリット
- 一部の人々は、単純なUIの場合、MVVMが過剰になる可能性があると考えています。
- 同様に、より大きなケースでは、ViewModelの設計が難しい場合があります。
- 複雑なデータバインディングがある場合、デバッグは少し難しくなります。
MVVM –責任
MVVMパターンは、Model、View、ViewModelの3つの部分で構成されています。 最初の開発者のほとんどは、Model、View、およびViewModelに含めるべきまたは含めるべきではないもの、および各部分の責任について混乱していません。
この章では、MVVMパターンの各部分の責任を学び、どのようなコードがどこに行くのかを明確に理解できるようにします。 次の図に示すように、MVVMは実際にはクライアント側の階層化アーキテクチャです。
- プレゼンテーション層はビューで構成されています。
- 論理層はビューモデルです。
- プレゼンテーションレイヤーは、モデルオブジェクトの組み合わせです。
- クライアントサービスを生成および保持するクライアントサービスは、2層アプリケーションでのアクセスを指示するか、サービス呼び出しを介してアプリケーションにアクセスします。
- クライアントサービスは正式にはMVVMパターンの一部ではありませんが、MVVMでよく使用され、さらなる分離を実現し、コードの重複を防ぎます。
モデルの責任
一般的に、モデルは理解するのが最も簡単なものです。 アプリケーションのビューをサポートするのは、クライアント側のデータモデルです。
- これは、メモリにデータを格納するためのプロパティといくつかの変数を持つオブジェクトで構成されています。
- これらのプロパティの一部は、他のモデルオブジェクトを参照し、全体としてモデルオブジェクトであるオブジェクトグラフを作成します。
- モデルオブジェクトは、WPFでデータバインディングを意味するプロパティ変更通知を発生させる必要があります。
- 最後の責任はオプションの検証ですが、INotifyDataErrorInfo/IDataErrorInfoなどのインターフェイスを介してWPFデータバインディング検証機能を使用して、モデルオブジェクトに検証情報を埋め込むことができます
責任を表示
ビューの主な目的と責任は、ユーザーが画面に表示するものの構造を定義することです。 構造には、静的部分と動的部分を含めることができます。
- 静的パーツは、ビューを構成するコントロールとコントロールのレイアウトを定義するXAML階層です。
- 動的な部分は、ビューの一部として定義されるアニメーションまたは状態の変化に似ています。
- MVVMの主な目標は、ビューにコードビハインドがないことです。
- 背後にコードが存在しないことは不可能です。 ビューでは、少なくともコンストラクターとコンポーネントを初期化するための呼び出しが必要です。
- その考え方は、イベント処理、アクション、およびデータ操作のロジックコードをViewのコードビハインドに含めるべきではないということです。
- UI要素への参照が本質的にビューコードである必要があるコードの背後にあるコードに入れなければならない他の種類のコードもあります。
ViewModelの責任
- ViewModelはMVVMアプリケーションの主要なポイントです。 ViewModelの主な役割は、ビューにデータを提供して、ビューがそのデータを画面に表示できるようにすることです。
- また、ユーザーはデータを操作してデータを変更できます。
- ViewModelのもう1つの重要な役割は、ビューの対話ロジックをカプセル化することですが、それは、アプリケーションのすべてのロジックがViewModelに入ることを意味するものではありません。
- ユーザーまたはビューの変更に基づいて正しいことを行うために、適切な呼び出しシーケンスを処理できる必要があります。
- ViewModelは、別のビューに移動するタイミングを決定するなど、ナビゲーションロジックも管理する必要があります。
MVVM –最初のアプリケーション
この章では、単純な入力画面にMVVMパターンを使用する方法と、すでに慣れているWPFアプリケーションを学習します。
MVVMアプローチを使用する簡単な例を見てみましょう。
- ステップ1 *-新しいWPFアプリケーションプロジェクトMVVMDemoを作成します。
- ステップ2 *-3つのフォルダー(Model、ViewModel、およびViews)をプロジェクトに追加します。
- ステップ3 *-モデルフォルダーにStudentModelクラスを追加し、そのクラスに以下のコードを貼り付けます
using System.ComponentModel;
namespace MVVMDemo.Model {
public class StudentModel {}
public class Student : INotifyPropertyChanged {
private string firstName;
private string lastName;
public string FirstName {
get {
return firstName;
}
set {
if (firstName != value) {
firstName = value;
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
}
}
public string LastName {
get {return lastName; }
set {
if (lastName != value) {
lastName = value;
RaisePropertyChanged("LastName");
RaisePropertyChanged("FullName");
}
}
}
public string FullName {
get {
return firstName + " " + lastName;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
- ステップ4 *-別のStudentViewModelクラスをViewModelフォルダーに追加し、次のコードを貼り付けます。
using MVVMDemo.Model;
using System.Collections.ObjectModel;
namespace MVVMDemo.ViewModel {
public class StudentViewModel {
public ObservableCollection<Student> Students {
get;
set;
}
public void LoadStudents() {
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { FirstName = "Mark", LastName = "Allain" });
students.Add(new Student { FirstName = "Allen", LastName = "Brown" });
students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
Students = students;
}
}
}
- ステップ5 *-[ビュー]フォルダを右クリックし、[追加]> [新しいアイテム]を選択して、新しいユーザーコントロール(WPF)を追加します。
- ステップ6 *-[追加]ボタンをクリックします。 これで、XAMLファイルが表示されます。 さまざまなUI要素を含むStudentView.xamlファイルに次のコードを追加します。
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
- ステップ7 *-次のコードを使用して、StudentViewをMainPage.xamlファイルに追加します。
<Window x:Class = "MVVMDemo.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:local = "clr-namespace:MVVMDemo"
xmlns:views = "clr-namespace:MVVMDemo.Views"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Grid>
<views:StudentView x:Name = "StudentViewControl" Loaded = "StudentViewControl_Loaded"/>
</Grid>
</Window>
- ステップ8 *-次に、MainModel.xaml.csファイルのLoadedイベントの実装を示します。これにより、ViewModelからViewが更新されます。
using System.Windows;
namespace MVVMDemo {
///<summary>
///Interaction logic for MainWindow.xaml
///</summary>
public partial class MainWindow : Window {
public MainWindow() {
InitializeComponent();
}
private void StudentViewControl_Loaded(object sender, RoutedEventArgs e) {
MVVMDemo.ViewModel.StudentViewModel studentViewModelObject =
new MVVMDemo.ViewModel.StudentViewModel();
studentViewModelObject.LoadStudents();
StudentViewControl.DataContext = studentViewModelObject;
}
}
}
- ステップ9 *-上記のコードをコンパイルして実行すると、メインウィンドウに次の出力が表示されます。
理解を深めるために、上記の例を段階的に実行することをお勧めします。
MVVM –ビューの接続
この章では、ViewModelにビューを接続するさまざまな方法について説明します。 まず、XAMLで宣言できるView first構造を見てみましょう。 メインウィンドウからビューを接続した最後の章の例を見てきたように。 次に、ビューを接続する他の方法を見ていきます。
この章でも同じ例を使用します。 以下は、同じModelクラスの実装です。
using System.ComponentModel;
namespace MVVMDemo.Model {
public class StudentModel {}
public class Student : INotifyPropertyChanged {
private string firstName;
private string lastName;
public string FirstName {
get { return firstName; }
set {
if (firstName != value) {
firstName = value;
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
}
}
public string LastName {
get { return lastName; }
set {
if (lastName != value) {
lastName = value;
RaisePropertyChanged("LastName");
RaisePropertyChanged("FullName");
}
}
}
public string FullName {
get {
return firstName + " " + lastName;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
ViewModelクラスの実装は次のとおりです。 今回は、LoadStudentsメソッドがデフォルトのコンストラクターで呼び出されます。
using MVVMDemo.Model;
using System.Collections.ObjectModel;
namespace MVVMDemo.ViewModel{
public class StudentViewModel {
public StudentViewModel() {
LoadStudents();
}
public ObservableCollection<Student> Students {
get;
set;
}
public void LoadStudents() {
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { FirstName = "Mark", LastName = "Allain" });
students.Add(new Student { FirstName = "Allen", LastName = "Brown" });
students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
Students = students;
}
}
}
ビューがウィンドウ、ユーザーコントロール、またはページのいずれであっても、パーサーは通常、上から下、左から右に機能します。 各要素に遭遇すると、デフォルトのコンストラクタを呼び出します。 ビューを作成するには2つの方法があります。 どれでも使用できます。
- XAMLで最初の構築を表示
- コードビハインドで最初の構造を表示
XAMLで最初の構築を表示
1つの方法は、次のコードに示すように、DataContextプロパティのセッターにネストされた要素としてViewModelを追加するだけです。
<UserControl.DataContext>
<viewModel:StudentViewModel/>
</UserControl.DataContext>
完全なView XAMLファイルを次に示します。
<UserControl x:Class="MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<UserControl.DataContext>
<viewModel:StudentViewModel/>
</UserControl.DataContext>
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
コードビハインドで最初の構造を表示
もう1つの方法は、Viewを最初に構築できることです。インスタンスでDataContextプロパティを設定して、Viewのコードビハインドでビューモデルを構築するだけです。
通常、DataContextプロパティはビューのコンストラクターメソッドで設定されますが、ビューのLoadイベントが発生するまで構築を延期することもできます。
using System.Windows.Controls;
namespace MVVMDemo.Views {
///<summary>
///Interaction logic for StudentView.xaml
///</summary>
public partial class StudentView : UserControl {
public StudentView() {
InitializeComponent();
this.DataContext = new MVVMDemo.ViewModel.StudentViewModel();
}
}
}
XAMLの代わりに分離コードでビューモデルを構築する理由の1つは、Viewモデルコンストラクターがパラメーターを取ることですが、XAML解析では既定のコンストラクターで定義されている場合にのみ要素を構築できることです。
この場合、ViewのXAMLファイルは次のコードのようになります。
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
mc:Ignorable = "d"
d:DesignHeight = "300"
d:DesignWidth = "300">
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal"<
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
MainWindow.XAMLファイルに示されているように、このビューをMainWindowで宣言できます。
<Window x:Class = "MVVMDemo.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:local = "clr-namespace:MVVMDemo"
xmlns:views = "clr-namespace:MVVMDemo.Views"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Grid>
<views:StudentView x:Name = "StudentViewControl"/>
</Grid>
</Window>
上記のコードをコンパイルして実行すると、メインウィンドウに次の出力が表示されます。
理解を深めるために、上記の例を段階的に実行することをお勧めします。
MVVM – ViewModelの接続
この章では、ViewModelを接続する方法について説明します。 ビューの最初の構成について説明した最後の章の続きです。 さて、最初の構築の次の形式は、 ViewModelLocator として知られる*メタパターン*です。 これは疑似パターンであり、MVVMパターンの上に階層化されています。
- MVVMでは、各ViewをViewModelに接続する必要があります。
- ViewModelLocatorは、コードを集中化し、ビューをさらに分離するためのシンプルなアプローチです。
- これは、ViewModelタイプとその構築方法を明示的に知る必要がないことを意味します。
- ViewModelLocatorを使用するにはさまざまなアプローチがありますが、ここでは、PRISMフレームワークの一部であるアプローチと最も類似したアプローチを使用します。
ViewModelLocatorは、ViewModelをViewに接続するプロセスを自動化する、ビューの最初の構築を行うための標準的で一貫性のある宣言的で疎結合の方法を提供します。 次の図は、ViewModelLocatorの高レベルプロセスを表しています。
- ステップ1 *-構築されているビュータイプを把握します。
- ステップ2 *-特定のビュータイプのViewModelを特定します。
- ステップ3 *-そのViewModelを構築します。
- ステップ4 *-Views DataContextをViewModelに設定します。
基本概念を理解するために、前の章の同じ例を続けて、ViewModelLocatorの簡単な例を見てみましょう。 StudentView.xamlファイルを見ると、ViewModelが静的に接続されていることがわかります。
次に、次のプログラムに示すように、これらのXAMLコードをコメント化して、コードビハインドからコードを削除します。
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
<!--<UserControl.DataContext>
<viewModel:StudentViewModel/>
</UserControl.DataContext>-->
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
次のコードに示すように、新しいフォルダーVMLを作成し、単一の添付プロパティ(依存プロパティ)AutoHookedUpViewModelを含む新しいパブリッククラスViewModelLocatorを追加します。
public static bool GetAutoHookedUpViewModel(DependencyObject obj) {
return (bool)obj.GetValue(AutoHookedUpViewModelProperty);
}
public static void SetAutoHookedUpViewModel(DependencyObject obj, bool value) {
obj.SetValue(AutoHookedUpViewModelProperty, value);
}
//Using a DependencyProperty as the backing store for AutoHookedUpViewModel.
//This enables animation, styling, binding, etc...
public static readonly DependencyProperty AutoHookedUpViewModelProperty =
DependencyProperty.RegisterAttached("AutoHookedUpViewModel",
typeof(bool), typeof(ViewModelLocator), new PropertyMetadata(false,
AutoHookedUpViewModelChanged));
これで、基本的なアタッチプロパティの定義を確認できます。 プロパティに動作を追加するには、ViewModelをViewに接続する自動プロセスを含むこのプロパティの変更イベントハンドラーを追加する必要があります。 これを行うためのコードは次のとおりです-
private static void AutoHookedUpViewModelChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e) {
if (DesignerProperties.GetIsInDesignMode(d)) return;
var viewType = d.GetType();
string str = viewType.FullName;
str = str.Replace(".Views.", ".ViewModel.");
var viewTypeName = str;
var viewModelTypeName = viewTypeName + "Model";
var viewModelType = Type.GetType(viewModelTypeName);
var viewModel = Activator.CreateInstance(viewModelType);
((FrameworkElement)d).DataContext = viewModel;
}
以下は、ViewModelLocatorクラスの完全な実装です。
using System;
using System.ComponentModel;
using System.Windows;
namespace MVVMDemo.VML {
public static class ViewModelLocator {
public static bool GetAutoHookedUpViewModel(DependencyObject obj) {
return (bool)obj.GetValue(AutoHookedUpViewModelProperty);
}
public static void SetAutoHookedUpViewModel(DependencyObject obj, bool value) {
obj.SetValue(AutoHookedUpViewModelProperty, value);
}
//Using a DependencyProperty as the backing store for AutoHookedUpViewModel.
//This enables animation, styling, binding, etc...
public static readonly DependencyProperty AutoHookedUpViewModelProperty =
DependencyProperty.RegisterAttached("AutoHookedUpViewModel",
typeof(bool), typeof(ViewModelLocator), new
PropertyMetadata(false, AutoHookedUpViewModelChanged));
private static void AutoHookedUpViewModelChanged(DependencyObject d,
DependencyPropertyChangedEventArgs e) {
if (DesignerProperties.GetIsInDesignMode(d)) return;
var viewType = d.GetType();
string str = viewType.FullName;
str = str.Replace(".Views.", ".ViewModel.");
var viewTypeName = str;
var viewModelTypeName = viewTypeName + "Model";
var viewModelType = Type.GetType(viewModelTypeName);
var viewModel = Activator.CreateInstance(viewModelType);
((FrameworkElement)d).DataContext = viewModel;
}
}
}
最初に行うことは、プロジェクトのルートにあるViewModelLocatorタイプにアクセスできるように、名前空間を追加することです。 次に、ビュータイプであるルート要素にAutoHookedUpViewModelプロパティを追加し、trueに設定します。
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
これがStudentView.xamlファイルの完全な実装です。
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
<!--<UserControl.DataContext>
<viewModel:StudentViewModel/>
</UserControl.DataContext>-->
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
上記のコードをコンパイルして実行すると、ViewModelLocatorがその特定のビューのViewModelをフックしていることがわかります。
これについて注意すべき重要な点は、ビューがそのViewModelのタイプや構築方法に結合されていないことです。 これらはすべて、ViewModelLocator内の中央の場所に移動されました。
MVVM – WPFデータバインディング
この章では、データバインディングがMVVMパターンをどのようにサポートするかを学習します。 データバインディングは、MVVMとMVCやMVPなどの他のUI分離パターンとを区別する重要な機能です。
- データバインディングの場合は、ビューまたはUI要素のセットを構築する必要があり、その後、バインディングが指す他のオブジェクトが必要です。
- ビューのUI要素は、ViewModelによって公開されるプロパティにバインドされます。
- 最初にビューを説明したように、ViewとViewModelが構築される順序は状況に依存します。
- ViewとViewModelが構築され、ViewのDataContextがViewModelに設定されます。
- バインディングは、ViewとViewModelの間でデータをやり取りするOneWayまたはTwoWayデータバインディングのいずれかです。
同じ例でデータバインディングを見てみましょう。 以下は、StudentViewのXAMLコードです。
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
<!--<UserControl.DataContext>
<viewModel:StudentViewModel/>
</UserControl.DataContext>-->
<Grid>
<StackPanel HorizontalAlignment = "Left">
<ItemsControl ItemsSource = "{Binding Path = Students}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
</Grid>
</UserControl>
- 上記のXAMLコードを見ると、ItemsControlがViewModelによって公開されている学生コレクションにバインドされていることがわかります。
- また、Studentモデルのプロパティにも独自の個別のバインディングがあり、これらはTextboxesとTextBlockにバインドされていることもわかります。
- ビューの全体的なDataContextがViewModelに設定されているため、ItemsControlのItemSourceはStudentsプロパティにバインドできます。
- ここでのプロパティの個々のバインディングもDataContextバインディングですが、ItemSourceの動作方法のため、ViewModel自体に対するバインディングではありません。
- アイテムソースがコレクションにバインドされると、レンダリング時に各アイテムのコンテナがレンダリングされ、そのコンテナのDataContextがアイテムに設定されます。 そのため、行内の各テキストボックスとテキストブロックの全体的なDataContextは、コレクション内の個々の生徒になります。 また、これらのTextBoxのバインディングはTwoWayデータバインディングであり、TextBlockの場合はTextBlockを編集できないため、OneWayデータバインディングであることがわかります。
このアプリケーションを再度実行すると、次の出力が表示されます。
最初の行の2番目のテキストボックスのテキストをAllainからUpstonに変更し、Tabキーを押してフォーカスを失います。 TextBlockテキストも更新されていることがわかります。
これは、TextBoxesのバインディングがTwoWayに設定され、Modelも更新され、モデルからTextBlockが再び更新されるためです。
MVVM – WPFデータテンプレート
テンプレートは、コントロールの全体的な外観と外観を記述します。 各コントロールには、そのコントロールに外観を与えるデフォルトのテンプレートが関連付けられています。 WPFアプリケーションでは、コントロールの視覚的な動作と視覚的な外観をカスタマイズするときに、独自のテンプレートを簡単に作成できます。 ロジックとテンプレート間の接続は、データバインディングによって実現できます。
MVVMには、ViewModelの最初の構築として知られる別の主要な形式があります。
- ViewModelの最初の構築アプローチは、WPFの暗黙的なデータテンプレートの機能を活用します。
- 暗黙的なデータテンプレートは、データバインディングを使用する要素の現在のリソースディクショナリから適切なテンプレートを自動的に選択できます。 これは、データバインディングによってレンダリングされるデータオブジェクトのタイプに基づいて行われます。 まず、データオブジェクトにバインドする要素が必要です。
データテンプレート、具体的には暗黙的なデータテンプレートを活用して最初にモデルを表示する方法を理解する簡単な例をもう一度見てみましょう。 これがStudentViewModelクラスの実装です。
using MVVMDemo.Model;
using System.Collections.ObjectModel;
namespace MVVMDemo.ViewModel {
public class StudentViewModel {
public StudentViewModel() {
LoadStudents();
}
public ObservableCollection<Student> Students {
get;
set;
}
public void LoadStudents() {
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { FirstName = "Mark", LastName = "Allain" });
students.Add(new Student { FirstName = "Allen", LastName = "Brown" });
students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
Students = students;
}
}
}
上記のViewModelが変更されていないことがわかります。 前の章と同じ例を続けます。 このViewModelクラスは、Studentsコレクションプロパティを公開し、構築時に設定します。 StudentView.xamlファイルに移動して、既存の実装を削除し、リソースセクションでデータテンプレートを定義しましょう。
<UserControl.Resources>
<DataTemplate x:Key = "studentsTemplate">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
次のコードに示すように、リストボックスを追加し、そのリストボックスを学生プロパティにバインドします。
<ListBox ItemsSource = "{Binding Students}" ItemTemplate = "{StaticResource studentsTemplate}"/>
Resourceセクションでは、DataTemplateのキーはstudentTemplateであり、そのテンプレートを実際に使用するには、ListBoxのItemTemplateプロパティを使用する必要があります。 これで、リストボックスにその特定のテンプレートを使用してそれらの生徒をレンダリングするよう指示することがわかります。 以下は、StudentView.xamlファイルの完全な実装です。
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
<UserControl.Resources>
<DataTemplate x:Key = "studentsTemplate">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ListBox
ItemsSource = "{Binding Students}"
ItemTemplate = "{StaticResource studentsTemplate}"/>
</Grid>
</UserControl>
上記のコードをコンパイルして実行すると、1つのListBoxを含む次のウィンドウが表示されます。 各ListBoxItemには、TextBlockおよびTextボックスに表示されるStudentクラスのオブジェクトデータが含まれています。
これを暗黙的なテンプレートにするには、次のコードに示すように、リストボックスからItemTemplateプロパティを削除し、テンプレート定義にDataTypeプロパティを追加する必要があります。
<UserControl.Resources>
<DataTemplate DataType = "{x:Type data:Student}">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource = "{Binding Students}"/>
</Grid>
DataTemplateでは、x:Typeマークアップ拡張は非常に重要です。これは、XAMLの一種の演算子のようなものです。 したがって、基本的には、MVVMDemo.Model名前空間にあるStudentデータ型を指す必要があります。 以下は、更新された完全なXAMLファイルです。
<UserControl x:Class="MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:data = "clr-namespace:MVVMDemo.Model"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d" d:DesignHeight = "300" d:DesignWidth = "300">
<UserControl.Resources>
<DataTemplate DataType = "{x:Type data:Student}">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<ListBox ItemsSource = "{Binding Students}"/>
</Grid>
</UserControl>
このアプリケーションを再度実行すると、適切なDataTemplateを見つけることでレンダリングされるオブジェクトのタイプが自動的にマッピングされるため、データテンプレートを使用した生徒の同じレンダリングが得られます。
理解を深めるために、上記の例を段階的な方法で実行することをお勧めします。
MVVM –ビュー/ViewModel通信
この章では、MVVMアプリケーションに対話性を追加する方法と、ロジックをきれいに呼び出す方法を学習します。 また、MVVMパターンの中心である疎結合と適切な構造化を維持することにより、これらすべてが行われることもわかります。 これをすべて理解するために、まずコマンドについて学習しましょう。
コマンドを介したView/ViewModel通信
コマンドパターンは十分に文書化されており、数十年にわたって頻繁にデザインパターンを使用しています。 このパターンには、呼び出し側と受信側の2つの主なアクターがあります。
View and ViewModel Communication
呼び出し側
- 呼び出し側は、いくつかの命令型ロジックを実行できるコードです。
- 通常、ユーザーがUIフレームワークのコンテキストで操作するのはUI要素です。
- アプリケーション内の別の場所にあるロジックコードの別のチャンクである可能性があります。
受信機
- レシーバーは、呼び出し側が起動したときに実行するためのロジックです。
- MVVMのコンテキストでは、受信者は通常、呼び出す必要があるViewModelのメソッドです。
これら2つの間に障害層があります。これは、呼び出し側と受信側が互いについて明示的に知る必要がないことを意味します。 これは通常、呼び出し側に公開されるインターフェース抽象化として表され、そのインターフェースの具体的な実装はレシーバーを呼び出すことができます。
コマンドと、それらを使用してViewとViewModelの間で通信する方法を学習する簡単な例を見てみましょう。 この章では、前の章と同じ例を続けます。
StudentView.xamlファイルには、ViewModelからの学生データを接続するListBoxがあります。 次に、リストボックスから生徒を削除するためのボタンを追加しましょう。
重要なことは、ICommandに接続するためのコマンドプロパティがあるため、ボタン上のコマンドの操作が非常に簡単であることです。
そのため、次のコードに示すように、ICommandを持ち、ボタンのコマンドプロパティからバインドするViewModelのプロパティを公開できます。
<Button Content = "Delete"
Command = "{Binding DeleteCommand}"
HorizontalAlignment = "Left"
VerticalAlignment = "Top"
Width = "75"/>
プロジェクトに新しいクラスを追加して、ICommandインターフェイスを実装します。 以下は、ICommandインターフェイスの実装です。
using System;
using System.Windows.Input;
namespace MVVMDemo {
public class MyICommand : ICommand {
Action _TargetExecuteMethod;
Func<bool> _TargetCanExecuteMethod;
public MyICommand(Action executeMethod) {
_TargetExecuteMethod = executeMethod;
}
public MyICommand(Action executeMethod, Func<bool> canExecuteMethod){
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}
public void RaiseCanExecuteChanged() {
CanExecuteChanged(this, EventArgs.Empty);
}
bool ICommand.CanExecute(object parameter) {
if (_TargetCanExecuteMethod != null) {
return _TargetCanExecuteMethod();
}
if (_TargetExecuteMethod != null) {
return true;
}
return false;
}
//Beware - should use weak references if command instance lifetime
is longer than lifetime of UI objects that get hooked up to command
//Prism commands solve this in their implementation
public event EventHandler CanExecuteChanged = delegate { };
void ICommand.Execute(object parameter) {
if (_TargetExecuteMethod != null) {
_TargetExecuteMethod();
}
}
}
}
ご覧のとおり、これはICommandの単純な委任実装であり、1つはexecuteMethodに、もう1つは作成時に渡すことができるcanExecuteMethodに2つのデリゲートを持っています。
上記の実装では、2つのオーバーロードされたコンストラクターがあります。1つはexecuteMethod専用で、もう1つはexecuteMethodとI canExecuteMethodの両方です。
StudentView ModelクラスにMyICommandタイプのプロパティを追加しましょう。 次に、StudentViewModelにインスタンスを構築する必要があります。 2つのパラメーターを取るMyICommandのオーバーロードされたコンストラクターを使用します。
public MyICommand DeleteCommand { get; set;}
public StudentViewModel() {
LoadStudents();
DeleteCommand = new MyICommand(OnDelete, CanDelete);
}
次に、OnDeleteメソッドとCanDeleteメソッドの実装を追加します。
private void OnDelete() {
Students.Remove(SelectedStudent);
}
private bool CanDelete() {
return SelectedStudent != null;
}
また、ユーザーがListBoxからSelected Itemを削除できるように、新しいSelectedStudentを追加する必要があります。
private Student _selectedStudent;
public Student SelectedStudent {
get {
return _selectedStudent;
}
set {
_selectedStudent = value;
DeleteCommand.RaiseCanExecuteChanged();
}
}
以下は、ViewModelクラスの完全な実装です。
using MVVMDemo.Model;
using System.Collections.ObjectModel;
using System.Windows.Input;
using System;
namespace MVVMDemo.ViewModel {
public class StudentViewModel {
public MyICommand DeleteCommand { get; set;}
public StudentViewModel() {
LoadStudents();
DeleteCommand = new MyICommand(OnDelete, CanDelete);
}
public ObservableCollection<Student> Students {
get;
set;
}
public void LoadStudents() {
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { FirstName = "Mark", LastName = "Allain" });
students.Add(new Student { FirstName = "Allen", LastName = "Brown" });
students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
Students = students;
}
private Student _selectedStudent;
public Student SelectedStudent {
get {
return _selectedStudent;
}
set {
_selectedStudent = value;
DeleteCommand.RaiseCanExecuteChanged();
}
}
private void OnDelete() {
Students.Remove(SelectedStudent);
}
private bool CanDelete() {
return SelectedStudent != null;
}
}
}
StudentView.xamlでは、SelectStudentプロパティにバインドするListBoxにSelectedItemプロパティを追加する必要があります。
<ListBox ItemsSource = "{Binding Students}" SelectedItem = "{Binding SelectedStudent}"/>
完全なxamlファイルを次に示します。
<UserControl x:Class = "MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:data = "clr-namespace:MVVMDemo.Model"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<UserControl.Resources>
<DataTemplate DataType = "{x:Type data:Student}">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<StackPanel Orientation = "Horizontal">
<ListBox ItemsSource = "{Binding Students}"
SelectedItem = "{Binding SelectedStudent}"/>
<Button Content = "Delete"
Command = "{Binding DeleteCommand}"
HorizontalAlignment = "Left"
VerticalAlignment = "Top"
Width = "75"/>
</StackPanel>
</Grid>
</UserControl>
上記のコードをコンパイルして実行すると、次のウィンドウが表示されます。
View and ViewModel Communication MainWindow1
削除ボタンが無効になっていることがわかります。 アイテムを選択すると有効になります。
View and ViewModel Communication MainWindow2
いずれかの項目を選択して、削除を押すと。 選択した項目リストが削除され、削除ボタンが再び無効になることがわかります。
View and ViewModel Communication MainWindow3
理解を深めるために、上記の例を段階的に実行することをお勧めします。
MVVM –階層とナビゲーション
MVVMアプリケーションを構築する場合、通常、複雑な情報画面を親ビューと子ビューのセットに分解します。子ビューは、パネルまたはコンテナコントロールの親ビュー内に含まれ、使用の階層を形成します。
- 複雑なビューを分解した後、独自のXAMLファイルに分離するすべての子コンテンツが必ずしもMVVMビューである必要があるわけではありません。
- コンテンツのチャンクは、画面に何かを表示するための構造を提供するだけであり、そのコンテンツに対するユーザーによる入力や操作はサポートしていません。
- 別のViewModelを必要としない場合もありますが、親ViewModelによって公開されたプロパティに基づいてレンダリングするXAMLチャンクだけにすることもできます。
- 最後に、ViewsとViewModelsの階層がある場合、親ViewModelが通信のハブになり、各子ViewModelが他の子ViewModelsとその親から可能な限り分離されたままになることができます。
異なるビュー間の単純な階層を定義する例を見てみましょう。 新しいWPFアプリケーションプロジェクト MVVMHierarchiesDemo を作成します
- ステップ1 *-3つのフォルダー(Model、ViewModel、およびViews)をプロジェクトに追加します。
- ステップ2 *-次の図に示すように、ModelフォルダーにCustomerクラスとOrderクラス、ViewsフォルダーにCustomerListViewとOrderView、ViewModelフォルダーにCustomerListViewModelとOrderViewModelを追加します。
- ステップ3 *-CustomerListViewとOrderViewの両方にテキストブロックを追加します。 これがCustomerListView.xamlファイルです。
<UserControl x:Class="MVVMHierarchiesDemo.Views.CustomerListView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<TextBlock Text = "Customer List View"/>
</Grid>
</UserControl>
次に、OrderView.xamlファイルを示します。
<UserControl x:Class = "MVVMHierarchiesDemo.Views.OrderView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x ="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc ="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d ="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views" mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<TextBlock Text = "Order View"/>
</Grid>
</UserControl>
ここで、これらのビューをホストするための何かが必要になります。これは単純なアプリケーションであるため、MainWindow内の適切な場所です。 ビューを配置してナビゲーション形式で切り替えることができるコンテナコントロールが必要です。 この目的のために、MainWindow.xamlファイルにContentControlを追加する必要があり、そのコンテンツプロパティを使用して、それをViewModel参照にバインドします。
次に、リソースディクショナリの各ビューのデータテンプレートを定義します。 以下はMainWindow.xamlファイルです。 各データテンプレートがデータ型(ViewModel型)を対応するビューにマップする方法に注意してください。
<Window x:Class = "MVVMHierarchiesDemo.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:local = "clr-namespace:MVVMHierarchiesDemo"
xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views"
xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
<views:CustomerListView/>
</DataTemplate>
<DataTemplate DataType = "{x:Type viewModels:OrderViewModel}">
<views:OrderView/>
</DataTemplate>
</Window.Resources>
<Grid>
<ContentControl Content = "{Binding CurrentView}"/>
</Grid>
</Window>
現在のビューモデルがCustomerListViewModelのインスタンスに設定されると、ViewModelが接続されたCustomerListViewがレンダリングされます。 これは注文ViewModelであり、OrderViewなどをレンダリングします。
現在、CurrentViewModelプロパティと、プロパティ内のViewModelの現在の参照を切り替えることができるいくつかのロジックとコマンドを持つViewModelが必要です。
MainWindowViewModelと呼ばれるこのMainWindowのViewModelを作成しましょう。 XAMLからViewModelのインスタンスを作成し、それを使用してウィンドウのDataContextプロパティを設定するだけです。 このために、ViewModelsのINotifyPropertyChangedの実装をカプセル化する基本クラスを作成する必要があります。
このクラスの背後にある主なアイデアは、適切な通知を簡単にトリガーできるように、INotifyPropertyChanged実装をカプセル化し、派生クラスにヘルパーメソッドを提供することです。 以下は、BindableBaseクラスの実装です。
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo {
class BindableBase : INotifyPropertyChanged {
protected virtual void SetProperty<T>(ref T member, T val,
[CallerMemberName] string propertyName = null) {
if (object.Equals(member, val)) return;
member = val;
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
protected virtual void OnPropertyChanged(string propertyName) {
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public event PropertyChangedEventHandler PropertyChanged = delegate { };
}
}
CurrentViewModelプロパティを使用してビューの切り替えを実際に開始します。 このプロパティの設定を駆動する方法が必要です。 そして、エンドユーザーが顧客リストまたは注文ビューに移動するようにコマンドを実行できるようにします。 最初に、ICommandインターフェイスを実装するプロジェクトに新しいクラスを追加します。 以下は、ICommandインターフェイスの実装です。
using System;
using System.Windows.Input;
namespace MVVMHierarchiesDemo {
public class MyICommand<T> : ICommand {
Action<T> _TargetExecuteMethod;
Func<T, bool> _TargetCanExecuteMethod;
public MyICommand(Action<T> executeMethod) {
_TargetExecuteMethod = executeMethod;
}
public MyICommand(Action<T> executeMethod, Func<T, bool> canExecuteMethod) {
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}
public void RaiseCanExecuteChanged() {
CanExecuteChanged(this, EventArgs.Empty);
}
#region ICommand Members
bool ICommand.CanExecute(object parameter) {
if (_TargetCanExecuteMethod != null) {
T tparm = (T)parameter;
return _TargetCanExecuteMethod(tparm);
}
if (_TargetExecuteMethod != null) {
return true;
}
return false;
}
//Beware - should use weak references if command instance lifetime is
longer than lifetime of UI objects that get hooked up to command
//Prism commands solve this in their implementation
public event EventHandler CanExecuteChanged = delegate { };
void ICommand.Execute(object parameter) {
if (_TargetExecuteMethod != null) {
_TargetExecuteMethod((T)parameter);
}
}
#endregion
}
}
次に、これらへのいくつかのトップレベルナビゲーションをViewModelsに設定する必要があり、その切り替えのロジックはMainWindowViewModel内に属する必要があります。 このために、文字列の宛先を取得してCurrentViewModelプロパティを返す、navigateと呼ばれるメソッドを使用します。
private void OnNav(string destination) {
switch (destination) {
case "orders":
CurrentViewModel = orderViewModelModel;
break;
case "customers":
default:
CurrentViewModel = custListViewModel;
break;
}
}
これらの異なるビューのナビゲーションのために、MainWindow.xamlファイルに2つのボタンを追加する必要があります。 以下は、完全なXAMLファイルの実装です。
<Window x:Class = "MVVMHierarchiesDemo.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:local = "clr-namespace:MVVMHierarchiesDemo"
xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views"
xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
<views:CustomerListView/>
</DataTemplate>
<DataTemplate DataType = "{x:Type viewModels:OrderViewModel}">
<views:OrderView/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height = "Auto"/>
<RowDefinition Height = "*"/>
</Grid.RowDefinitions>
<Grid x:Name = "NavBar">
<Grid.ColumnDefinitions>
<ColumnDefinition Width = "*"/>
<ColumnDefinition Width = "*"/>
<ColumnDefinition Width = "*"/>
</Grid.ColumnDefinitions>
<Button Content = "Customers"
Command = "{Binding NavCommand}"
CommandParameter = "customers"
Grid.Column = "0"/>
<Button Content = "Order"
Command = "{Binding NavCommand}"
CommandParameter = "orders"
Grid.Column = "2"/>
</Grid>
<Grid x:Name = "MainContent" Grid.Row = "1">
<ContentControl Content = "{Binding CurrentViewModel}"/>
</Grid>
</Grid>
</Window>
以下は、MainWindowViewModelの完全な実装です。
using MVVMHierarchiesDemo.ViewModel;
using MVVMHierarchiesDemo.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo {
class MainWindowViewModel : BindableBase {
public MainWindowViewModel() {
NavCommand = new MyICommand<string>(OnNav);
}
private CustomerListViewModel custListViewModel = new CustomerListViewModel();
private OrderViewModel orderViewModelModel = new OrderViewModel();
private BindableBase _CurrentViewModel;
public BindableBase CurrentViewModel {
get {return _CurrentViewModel;}
set {SetProperty(ref _CurrentViewModel, value);}
}
public MyICommand<string> NavCommand { get; private set; }
private void OnNav(string destination) {
switch (destination) {
case "orders":
CurrentViewModel = orderViewModelModel;
break;
case "customers":
default:
CurrentViewModel = custListViewModel;
break;
}
}
}
}
BindableBaseクラスからすべてのViewModelを派生します。 上記のコードをコンパイルして実行すると、次の出力が表示されます。
ご覧のとおり、MainWindowには2つのボタンとCurrentViewModelのみが追加されています。 ボタンをクリックすると、その特定のビューに移動します。 [顧客]ボタンをクリックすると、CustomerListViewが表示されます。
理解を深めるために、上記の例を段階的に実行することをお勧めします。
MVVM –検証
この章では、検証について学習します。 また、WPFバインディングが既にサポートしているがMVVMコンポーネントに結び付けているもので検証を行うためのクリーンな方法も検討します。
MVVMでの検証
- アプリケーションがエンドユーザーからのデータ入力の受け入れを開始したら、その入力の検証を検討する必要があります。
- 全体的な要件に準拠していることを確認してください。
- WPFのバインディングシステムには、入力を検証するための優れたビルドと機能がいくつかありますが、MVVMを実行するときにこれらの機能をすべて活用できます。
- 検証をサポートし、どのプロパティに存在するルールを定義するロジックは、ビュー自体ではなく、モデルまたはViewModelの一部であることに注意してください。
あなたはまだWPFデータバインディングによってサポートされている検証を表現するすべての方法を使用することができます-
- プロパティでの例外のスローが設定されます。
- IDataErrorInfoインターフェイスを実装します。
- INotifyDataErrorInfoを実装します。
- WPF検証ルールを使用します。
一般に、INotifyDataErrorInfoが推奨され、WPF .net 4.5に導入され、プロパティに関連付けられたエラーのオブジェクトのクエリをサポートし、他のすべてのオプションのいくつかの欠陥も修正します。 具体的には、非同期検証を許可します。 プロパティに複数のエラーを関連付けることができます。
検証の追加
入力ビューに検証サポートを追加する例を見てみましょう。大規模なアプリケーションでは、おそらくこれをアプリケーションのいくつかの場所に必要とします。 ビュー、ViewModel、およびこれらのヘルパーオブジェクトには、モデルオブジェクトの周りにラッパーが存在する場合があります。
検証サポートを共通の基本クラスに配置して、さまざまなシナリオから継承できるようにすることをお勧めします。
基本クラスはINotifyDataErrorInfoをサポートするため、プロパティが変更されたときに検証がトリガーされます。
ValidatableBindableBaseという新しいクラスを作成します。 プロパティ変更処理の基本クラスが既にあるので、それから基本クラスを派生させ、INotifyDataErrorInfoインターフェイスも実装しましょう。
以下は、ValidateableBindableBaseクラスの実装です。
using System;
using System.Collections.Generic;
using System.ComponentModel;
//using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Controls;
namespace MVVMHierarchiesDemo {
public class ValidatableBindableBase : BindableBase, INotifyDataErrorInfo {
private Dictionary<string, List<string>> _errors = new Dictionary<string, List<string>>();
public event EventHandler<DataErrorsChangedEventArgs>
ErrorsChanged = delegate { };
public System.Collections.IEnumerable GetErrors(string propertyName) {
if (_errors.ContainsKey(propertyName))
return _errors[propertyName];
else
return null;
}
public bool HasErrors {
get { return _errors.Count > 0; }
}
protected override void SetProperty<T>(ref T member, T val,
[CallerMemberName] string propertyName = null) {
base.SetProperty<T>(ref member, val, propertyName);
ValidateProperty(propertyName, val);
}
private void ValidateProperty<T>(string propertyName, T value) {
var results = new List<ValidationResult>();
//ValidationContext context = new ValidationContext(this);
//context.MemberName = propertyName;
//Validator.TryValidateProperty(value, context, results);
if (results.Any()) {
//_errors[propertyName] = results.Select(c => c.ErrorMessage).ToList();
} else {
_errors.Remove(propertyName);
}
ErrorsChanged(this, new DataErrorsChangedEventArgs(propertyName));
}
}
}
次に、それぞれのフォルダーにAddEditCustomerViewとAddEditCustomerViewModelを追加します。 以下は、AddEditCustomerView.xamlのコードです。
<UserControl x:Class = "MVVMHierarchiesDemo.Views.AddEditCustomerView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMHierarchiesDemo.Views"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height = "Auto"/>
<RowDefinition Height = "Auto"/>
</Grid.RowDefinitions>
<Grid x:Name = "grid1"
HorizontalAlignment = "Left"
DataContext = "{Binding Customer}"
Margin = "10,10,0,0"
VerticalAlignment = "Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width = "Auto"/>
<ColumnDefinition Width = "Auto"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height = "Auto"/>
<RowDefinition Height = "Auto"/>
<RowDefinition Height = "Auto"/>
<RowDefinition Height = "Auto"/>
</Grid.RowDefinitions>
<Label Content = "First Name:"
Grid.Column = "0"
HorizontalAlignment = "Left"
Margin = "3"
Grid.Row = "0"
VerticalAlignment = "Center"/>
<TextBox x:Name = "firstNameTextBox"
Grid.Column = "1"
HorizontalAlignment = "Left"
Height = "23"
Margin = "3"
Grid.Row = "0"
Text = "{Binding FirstName, ValidatesOnNotifyDataErrors = True}"
VerticalAlignment = "Center"
Width = "120"/>
<Label Content = "Last Name:"
Grid.Column = "0"
HorizontalAlignment = "Left"
Margin = "3"
Grid.Row = "1"
VerticalAlignment = "Center"/>
<TextBox x:Name = "lastNameTextBox"
Grid.Column = "1"
HorizontalAlignment = "Left"
Height = "23"
Margin = "3"
Grid.Row = "1"
Text = "{Binding LastName, ValidatesOnNotifyDataErrors = True}"
VerticalAlignment = "Center"
Width = "120"/>
<Label Content = "Email:"
Grid.Column = "0"
HorizontalAlignment = "Left"
Margin = "3"
Grid.Row = "2"
VerticalAlignment = "Center"/>
<TextBox x:Name = "emailTextBox"
Grid.Column = "1"
HorizontalAlignment = "Left"
Height = "23"
Margin = "3"
Grid.Row = "2"
Text = "{Binding Email, ValidatesOnNotifyDataErrors = True}"
VerticalAlignment = "Center"
Width = "120"/>
<Label Content = "Phone:"
Grid.Column = "0"
HorizontalAlignment = "Left"
Margin = "3"
Grid.Row = "3"
VerticalAlignment = "Center"/>
<TextBox x:Name = "phoneTextBox"
Grid.Column = "1"
HorizontalAlignment = "Left"
Height = "23"
Margin = "3"
Grid.Row = "3"
Text = "{Binding Phone, ValidatesOnNotifyDataErrors = True}"
VerticalAlignment = "Center"
Width = "120"/>
</Grid>
<Grid Grid.Row = "1">
<Button Content = "Save"
Command = "{Binding SaveCommand}"
HorizontalAlignment = "Left"
Margin = "25,5,0,0"
VerticalAlignment = "Top"
Width = "75"/>
<Button Content = "Add"
Command = "{Binding SaveCommand}"
HorizontalAlignment = "Left"
Margin = "25,5,0,0"
VerticalAlignment = "Top"
Width = "75"/>
<Button Content = "Cancel"
Command = "{Binding CancelCommand}"
HorizontalAlignment = "Left"
Margin = "150,5,0,0"
VerticalAlignment = "Top"
Width = "75"/>
</Grid>
</Grid>
</UserControl>
以下は、AddEditCustomerViewModelの実装です。
using MVVMHierarchiesDemo.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo.ViewModel {
class AddEditCustomerViewModel : BindableBase {
public AddEditCustomerViewModel() {
CancelCommand = new MyIcommand(OnCancel);
SaveCommand = new MyIcommand(OnSave, CanSave);
}
private bool _EditMode;
public bool EditMode {
get { return _EditMode; }
set { SetProperty(ref _EditMode, value);}
}
private SimpleEditableCustomer _Customer;
public SimpleEditableCustomer Customer {
get { return _Customer; }
set { SetProperty(ref _Customer, value);}
}
private Customer _editingCustomer = null;
public void SetCustomer(Customer cust) {
_editingCustomer = cust;
if (Customer != null) Customer.ErrorsChanged -= RaiseCanExecuteChanged;
Customer = new SimpleEditableCustomer();
Customer.ErrorsChanged += RaiseCanExecuteChanged;
CopyCustomer(cust, Customer);
}
private void RaiseCanExecuteChanged(object sender, EventArgs e) {
SaveCommand.RaiseCanExecuteChanged();
}
public MyIcommand CancelCommand { get; private set; }
public MyIcommand SaveCommand { get; private set; }
public event Action Done = delegate { };
private void OnCancel() {
Done();
}
private async void OnSave() {
Done();
}
private bool CanSave() {
return !Customer.HasErrors;
}
}
}
次に、SimpleEditableCustomerクラスの実装を示します。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo.Model {
public class SimpleEditableCustomer : ValidatableBindableBase {
private Guid _id;
public Guid Id {
get { return _id; }
set { SetProperty(ref _id, value); }
}
private string _firstName;
[Required]
public string FirstName {
get { return _firstName; }
set { SetProperty(ref _firstName, value); }
}
private string _lastName;
[Required]
public string LastName {
get { return _lastName; }
set { SetProperty(ref _lastName, value); }
}
private string _email;
[EmailAddress]
public string Email {
get { return _email; }
set { SetProperty(ref _email, value); }
}
private string _phone;
[Phone]
public string Phone {
get { return _phone; }
set { SetProperty(ref _phone, value); }
}
}
}
上記のコードをコンパイルして実行すると、次のウィンドウが表示されます。
[顧客の追加]ボタンを押すと、次のビューが表示されます。 ユーザーがフィールドを空のままにすると、そのフィールドが強調表示され、保存ボタンが無効になります。
MVVM –依存性注入
この章では、依存性注入について簡単に説明します。 通信のもう一方の端で何が起こっているかを明確に知らずに通信できるように、ビューとViewModelを互いに分離するデータバインディングについては既に説明しました。
ここで、ViewModelをクライアントサービスから分離するのと同様のものが必要です。
オブジェクト指向プログラミングの初期の頃、開発者はアプリケーション内のクラスのインスタンスを作成および取得する問題に直面していました。 この問題に対してさまざまな解決策が提案されています。
過去数年間、依存性注入と制御の反転(IoC)は開発者の間で人気を博し、シングルトンパターンなどの古いソリューションよりも優先されてきました。
依存性注入/IoCコンテナー
IoCと依存性注入は密接に関連する2つの設計パターンであり、コンテナは基本的にこれらの両方のパターンを実行するインフラストラクチャコードのチャンクです。
- IoCパターンは、構築の責任を委任することに関するものであり、依存性注入パターンは、既に構築されているオブジェクトに依存関係を提供することです。
- どちらも、構築する2段階のアプローチとして扱うことができます。 あなたがコンテナを使用するとき、コンテナは次のようないくつかの責任を負います-
- 要求されたときにオブジェクトを構築します。
- コンテナは、そのオブジェクトが依存するものを決定します。
- これらの依存関係を構築します。
- 構築中のオブジェクトにそれらを注入します。
- プロセスを再帰的に実行します。
依存性注入を使用して、ViewModelとクライアントサービス間の分離を解除する方法を見てみましょう。 それに関連する依存関係注入を使用して、保存処理AddEditCustomerViewModelフォームを結び付けます。
最初に、プロジェクト内のServicesフォルダーに新しいインターフェイスを作成する必要があります。 プロジェクトにサービスフォルダーがない場合は、まず作成し、サービスフォルダーに次のインターフェイスを追加します。
using MVVMHierarchiesDemo.Model;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo.Services {
public interface ICustomersRepository {
Task<List<Customer>> GetCustomersAsync();
Task<Customer> GetCustomerAsync(Guid id);
Task<Customer> AddCustomerAsync(Customer customer);
Task<Customer> UpdateCustomerAsync(Customer customer);
Task DeleteCustomerAsync(Guid customerId);
}
}
以下は、ICustomersRepositoryの実装です。
using MVVMHierarchiesDemo.Model;
using System;
using System.Collections.Generic;
using System.Linq; using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo.Services {
public class CustomersRepository : ICustomersRepository {
ZzaDbContext _context = new ZzaDbContext();
public Task<List<Customer>> GetCustomersAsync() {
return _context.Customers.ToListAsync();
}
public Task<Customer> GetCustomerAsync(Guid id) {
return _context.Customers.FirstOrDefaultAsync(c => c.Id == id);
}
public async Task<Customer> AddCustomerAsync(Customer customer){
_context.Customers.Add(customer);
await _context.SaveChangesAsync();
return customer;
}
public async Task<Customer> UpdateCustomerAsync(Customer customer) {
if (!_context.Customers.Local.Any(c => c.Id == customer.Id)) {
_context.Customers.Attach(customer);
}
_context.Entry(customer).State = EntityState.Modified;
await _context.SaveChangesAsync();
return customer;
}
public async Task DeleteCustomerAsync(Guid customerId) {
var customer = _context.Customers.FirstOrDefault(c => c.Id == customerId);
if (customer != null) {
_context.Customers.Remove(customer);
}
await _context.SaveChangesAsync();
}
}
}
保存処理を行う簡単な方法は、AddEditCustomerViewModelにICustomersRepositoryの新しいインスタンスを追加し、AddEditCustomerViewModelおよびCustomerListViewModelコンストラクターをオーバーロードすることです。
private ICustomersRepository _repo;
public AddEditCustomerViewModel(ICustomersRepository repo) {
_repo = repo;
CancelCommand = new MyIcommand(OnCancel);
SaveCommand = new MyIcommand(OnSave, CanSave);
}
次のコードに示すように、OnSaveメソッドを更新します。
private async void OnSave() {
UpdateCustomer(Customer, _editingCustomer);
if (EditMode)
await _repo.UpdateCustomerAsync(_editingCustomer);
else
await _repo.AddCustomerAsync(_editingCustomer);
Done();
}
private void UpdateCustomer(SimpleEditableCustomer source, Customer target) {
target.FirstName = source.FirstName;
target.LastName = source.LastName;
target.Phone = source.Phone;
target.Email = source.Email;
}
完全なAddEditCustomerViewModelは次のとおりです。
using MVVMHierarchiesDemo.Model;
using MVVMHierarchiesDemo.Services;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo.ViewModel {
class AddEditCustomerViewModel : BindableBase {
private ICustomersRepository _repo;
public AddEditCustomerViewModel(ICustomersRepository repo) {
_repo = repo;
CancelCommand = new MyIcommand(OnCancel);
SaveCommand = new MyIcommand(OnSave, CanSave);
}
private bool _EditMode;
public bool EditMode {
get { return _EditMode; }
set { SetProperty(ref _EditMode, value); }
}
private SimpleEditableCustomer _Customer;
public SimpleEditableCustomer Customer {
get { return _Customer; }
set { SetProperty(ref _Customer, value); }
}
private Customer _editingCustomer = null;
public void SetCustomer(Customer cust) {
_editingCustomer = cust;
if (Customer != null) Customer.ErrorsChanged -= RaiseCanExecuteChanged;
Customer = new SimpleEditableCustomer();
Customer.ErrorsChanged += RaiseCanExecuteChanged;
CopyCustomer(cust, Customer);
}
private void RaiseCanExecuteChanged(object sender, EventArgs e) {
SaveCommand.RaiseCanExecuteChanged();
}
public MyIcommand CancelCommand { get; private set; }
public MyIcommand SaveCommand { get; private set; }
public event Action Done = delegate { };
private void OnCancel() {
Done();
}
private async void OnSave() {
UpdateCustomer(Customer, _editingCustomer);
if (EditMode)
await _repo.UpdateCustomerAsync(_editingCustomer);
else
await _repo.AddCustomerAsync(_editingCustomer);
Done();
}
private void UpdateCustomer(SimpleEditableCustomer source, Customer target) {
target.FirstName = source.FirstName;
target.LastName = source.LastName;
target.Phone = source.Phone;
target.Email = source.Email;
}
private bool CanSave() {
return !Customer.HasErrors;
}
private void CopyCustomer(Customer source, SimpleEditableCustomer target) {
target.Id = source.Id;
if (EditMode) {
target.FirstName = source.FirstName;
target.LastName = source.LastName;
target.Phone = source.Phone;
target.Email = source.Email;
}
}
}
}
上記のコードをコンパイルして実行すると、同じ出力が表示されますが、ViewModelsはより緩やかに分離されます。
MVVM Dependency Injection MainWindow1
[顧客の追加]ボタンを押すと、次のビューが表示されます。 ユーザーがフィールドを空のままにすると、そのフィールドが強調表示され、保存ボタンが無効になります。
MVVM Dependency Injection MainWindow2
MVVM –イベント
イベントは、状態の変化に反応して、通知用に登録されたエンドポイントに通知するプログラミング構成です。 主に、イベントはマウスとキーボードを介してユーザー入力を通知するために使用されますが、その有用性はそれに限定されません。 状態の変化が検出されたときはいつでも、おそらくオブジェクトがロードまたは初期化されたときに、関心のあるサードパーティに警告するイベントを起動できます。
- MVVM(Model-View-ViewModel)デザインパターンを使用するWPFアプリケーションでは、ビューモデルはアプリケーションのプレゼンテーションロジックと状態を処理するコンポーネントです。
- ビューの分離コードファイルには、ボタンやComboBoxなどのユーザーインターフェイス(UI)要素から発生するイベントを処理するコードを含めることはできません。
- 理想的には、ビューのコードビハインドには、InitializeComponentメソッドを呼び出すコンストラクターと、XAMLで表現することが困難または非効率的なビューレイヤーを制御または対話するための追加コードのみが含まれていることが理想的です。 複雑なアニメーション。
アプリケーションのボタンクリックイベントの簡単な例を見てみましょう。 以下は、2つのボタンが表示されるMainWindow.xamlファイルのXAMLコードです。
<Window x:Class = "MVVMHierarchiesDemo.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:local = "clr-namespace:MVVMHierarchiesDemo"
xmlns:views = "clr-namespace:MVVMHierarchiesDemo.Views"
xmlns:viewModels = "clr-namespace:MVVMHierarchiesDemo.ViewModel"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Window.DataContext>
<local:MainWindowViewModel/>
</Window.DataContext>
<Window.Resources>
<DataTemplate DataType = "{x:Type viewModels:CustomerListViewModel}">
<views:CustomerListView/>
</DataTemplate>
<DataTemplate DataType = "{x:Type viewModels:OrderViewModel}">
<views:OrderView/>
</DataTemplate>
</Window.Resources>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height = "Auto"/>
<RowDefinition Height = "*"/>
</Grid.RowDefinitions>
<Grid x:Name = "NavBar">
<Grid.ColumnDefinitions>
<ColumnDefinition Width = "*"/>
<ColumnDefinition Width = "*"/>
<ColumnDefinition Width = "*"/>
</Grid.ColumnDefinitions>
<Button Content = "Customers"
Command = "{Binding NavCommand}"
CommandParameter = "customers"
Grid.Column = "0"/>
<Button Content = "Order"
Command = "{Binding NavCommand}"
CommandParameter = "orders"
Grid.Column = "2"/>
</Grid>
<Grid x:Name = "MainContent" Grid.Row = "1">
<ContentControl Content = "{Binding CurrentViewModel}"/>
</Grid>
</Grid>
</Window>
上記のXAMLファイルでは、ボタンのClickプロパティが使用されていないことがわかりますが、CommandおよびCommandParameterプロパティは、ボタンが押されたときに異なるビューをロードするために使用されます。 次に、MainWindowViewModel.csファイルでコマンド実装を定義する必要がありますが、Viewファイルでは定義する必要がありません。 以下は、MainWindowViewModelの完全な実装です。
using MVVMHierarchiesDemo.ViewModel;
using MVVMHierarchiesDemo.Views;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MVVMHierarchiesDemo {
class MainWindowViewModel : BindableBase {
public MainWindowViewModel() {
NavCommand = new MyICommand<string>(OnNav);
}
private CustomerListViewModel custListViewModel = new CustomerListViewModel();
private OrderViewModel orderViewModelModel = new OrderViewModel();
private BindableBase _CurrentViewModel;
public BindableBase CurrentViewModel {
get { return _CurrentViewModel; }
set { SetProperty(ref _CurrentViewModel, value); }
}
public MyICommand<string> NavCommand { get; private set; }
private void OnNav(string destination) {
switch (destination) {
case "orders":
CurrentViewModel = orderViewModelModel;
break;
case "customers":
default:
CurrentViewModel = custListViewModel;
break;
}
}
}
}
BindableBaseクラスからすべてのViewModelを派生します。 上記のコードをコンパイルして実行すると、次の出力が表示されます。
ご覧のとおり、MainWindowには2つのボタンとCurrentViewModelのみが追加されています。 ここでanyボタンをクリックすると、その特定のビューに移動します。 [顧客]ボタンをクリックすると、CustomerListViewが表示されます。
理解を深めるために、上記の例を段階的な方法で実行することをお勧めします。
MVVM –ユニットテスト
単体テストの背後にある考え方は、個別のコードチャンク(ユニット)を取得し、予想される方法でコードを使用するテストメソッドを記述し、予想される結果が得られるかどうかをテストすることです。
- コード自体であるため、単体テストはプロジェクトの他の部分と同様にコンパイルされます。
- また、テスト実行ソフトウェアによって実行されます。これにより、各テストが高速化され、テストが成功したか失敗したかを効果的に示すことができます。
先ほど作成した例を見てみましょう。 以下は、学生モデルの実装です。
using System.ComponentModel;
namespace MVVMDemo.Model {
public class StudentModel {}
public class Student : INotifyPropertyChanged {
private string firstName;
private string lastName;
public string FirstName {
get { return firstName; }
set {
if (firstName != value) {
firstName = value;
RaisePropertyChanged("FirstName");
RaisePropertyChanged("FullName");
}
}
}
public string LastName {
get { return lastName; }
set {
if (lastName != value) {
lastName = value;
RaisePropertyChanged("LastName");
RaisePropertyChanged("FullName");
}
}
}
public string FullName {
get {
return firstName + " " + lastName;
}
}
public event PropertyChangedEventHandler PropertyChanged;
private void RaisePropertyChanged(string property) {
if (PropertyChanged != null) {
PropertyChanged(this, new PropertyChangedEventArgs(property));
}
}
}
}
以下は、StudentViewの実装です。
<UserControl x:Class="MVVMDemo.Views.StudentView"
xmlns = "http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x = "http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc = "http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d = "http://schemas.microsoft.com/expression/blend/2008"
xmlns:local = "clr-namespace:MVVMDemo.Views"
xmlns:viewModel = "clr-namespace:MVVMDemo.ViewModel"
xmlns:data = "clr-namespace:MVVMDemo.Model"
xmlns:vml = "clr-namespace:MVVMDemo.VML"
vml:ViewModelLocator.AutoHookedUpViewModel = "True"
mc:Ignorable = "d"
d:DesignHeight = "300" d:DesignWidth = "300">
<UserControl.Resources>
<DataTemplate DataType = "{x:Type data:Student}">
<StackPanel Orientation = "Horizontal">
<TextBox Text = "{Binding Path = FirstName, Mode = TwoWay}"
Width = "100" Margin = "3 5 3 5"/>
<TextBox Text = "{Binding Path = LastName, Mode = TwoWay}"
Width = "100" Margin = "0 5 3 5"/>
<TextBlock Text = "{Binding Path = FullName, Mode = OneWay}"
Margin = "0 5 3 5"/>
</StackPanel>
</DataTemplate>
</UserControl.Resources>
<Grid>
<StackPanel Orientation = "Horizontal">
<ListBox ItemsSource = "{Binding Students}"
SelectedItem = "{Binding SelectedStudent}"/>
<Button Content = "Delete"
Command = "{Binding DeleteCommand}"
HorizontalAlignment = "Left"
VerticalAlignment = "Top"
Width = "75"/>
</StackPanel>
</Grid>
</UserControl>
以下は、StudentViewModelの実装です。
using MVVMDemo.Model;
using System.Collections.ObjectModel;
using System.Windows.Input;
using System;
namespace MVVMDemo.ViewModel {
public class StudentViewModel {
public MyICommand DeleteCommand { get; set;}
public StudentViewModel() {
LoadStudents();
DeleteCommand = new MyICommand(OnDelete, CanDelete);
}
public ObservableCollection<Student> Students {
get;
set;
}
public void LoadStudents() {
ObservableCollection<Student> students = new ObservableCollection<Student>();
students.Add(new Student { FirstName = "Mark", LastName = "Allain" });
students.Add(new Student { FirstName = "Allen", LastName = "Brown" });
students.Add(new Student { FirstName = "Linda", LastName = "Hamerski" });
Students = students;
}
private Student _selectedStudent;
public Student SelectedStudent {
get {
return _selectedStudent;
}
set {
_selectedStudent = value;
DeleteCommand.RaiseCanExecuteChanged();
}
}
private void OnDelete() {
Students.Remove(SelectedStudent);
}
private bool CanDelete() {
return SelectedStudent != null;
}
public int GetStudentCount() {
return Students.Count;
}
}
}
以下はMainWindow.xamlファイルです。
<Window x:Class = "MVVMDemo.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:local = "clr-namespace:MVVMDemo"
xmlns:views = "clr-namespace:MVVMDemo.Views"
mc:Ignorable = "d"
Title = "MainWindow" Height = "350" Width = "525">
<Grid>
<views:StudentView x:Name = "StudentViewControl"/>
</Grid>
</Window>
以下は、ICommandインターフェイスを実装するMyICommandの実装です。
using System;
using System.Windows.Input;
namespace MVVMDemo {
public class MyICommand : ICommand {
Action _TargetExecuteMethod;
Func<bool> _TargetCanExecuteMethod;
public MyICommand(Action executeMethod) {
_TargetExecuteMethod = executeMethod;
}
public MyICommand(Action executeMethod, Func<bool> canExecuteMethod) {
_TargetExecuteMethod = executeMethod;
_TargetCanExecuteMethod = canExecuteMethod;
}
public void RaiseCanExecuteChanged() {
CanExecuteChanged(this, EventArgs.Empty);
}
bool ICommand.CanExecute(object parameter) {
if (_TargetCanExecuteMethod != null) {
return _TargetCanExecuteMethod();
}
if (_TargetExecuteMethod != null) {
return true;
}
return false;
}
//Beware - should use weak references if command instance lifetime
is longer than lifetime of UI objects that get hooked up to command
//Prism commands solve this in their implementation
public event EventHandler CanExecuteChanged = delegate { };
void ICommand.Execute(object parameter) {
if (_TargetExecuteMethod != null) {
_TargetExecuteMethod();
}
}
}
}
上記のコードをコンパイルして実行すると、メインウィンドウに次の出力が表示されます。
上記の例の単体テストを作成するには、ソリューションに新しいテストプロジェクトを追加しましょう。
「参照」を右クリックして、プロジェクトへの参照を追加します。
既存のプロジェクトを選択し、[OK]をクリックします。
次のコードに示すように、学生数をチェックする簡単なテストを追加しましょう。
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MVVMDemo.ViewModel;
namespace MVVMTest {
[TestClass]
public class UnitTest1 {
[TestMethod]
public void TestMethod1() {
StudentViewModel sViewModel = new StudentViewModel();
int count = sViewModel.GetStudentCount();
Assert.IsTrue(count == 3);
}
}
}
このテストを実行するには、テスト→実行→すべてのテストメニューオプションを選択します。
Student Explorerには3人の生徒が追加されているため、テストエクスプローラーでテストに合格したことがわかります。 次のコードに示すように、カウント条件を3から4に変更します。
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using MVVMDemo.ViewModel;
namespace MVVMTest {
[TestClass]
public class UnitTest1 {
[TestMethod] public void TestMethod1() {
StudentViewModel sViewModel = new StudentViewModel();
int count = sViewModel.GetStudentCount();
Assert.IsTrue(count == 4);
}
}
}
テスト計画を再度実行すると、学生数が4でないためにテストが失敗したことがわかります。
理解を深めるために、上記の例を段階的な方法で実行することをお勧めします。
MVVM –フレームワーク
この章では、利用可能なMVVMツールキットまたはフレームワークについて説明します。 また、これらのフレームワークを使用すると、MVVMパターンを自分で実装するために繰り返しコードを大量に記述する必要がなくなります。 最も人気のあるフレームワークの一部を次に示します-
- プリズム
- MVVMライト
- カリバーンマイクロ
プリズム
Prismは、豊富で柔軟性があり、メンテナンスが容易なWindows Presentation Foundation(WPF)デスクトップアプリケーションを簡単に設計および構築するのに役立つサンプルおよびドキュメントの形でガイダンスを提供します。 Microsoft SilverlightブラウザープラグインとWindowsアプリケーションで構築されたリッチインターネットアプリケーション(RIA)。
- Prismは、関心の分離や疎結合などの重要なアーキテクチャ設計原則を具体化する設計パターンを使用します。
- Prismは、独立して進化できますが、アプリケーション全体に簡単かつシームレスに統合できる疎結合コンポーネントを使用して、アプリケーションの設計と構築を支援します。
- これらのタイプのアプリケーションは、複合アプリケーションとして知られています。
Prismには、すぐに使用できる多くの機能があります。 以下は、Prismの重要な機能の一部です。
MVVMパターン
PrismはMVVMパターンをサポートしています。 これには、前の章で実装されたものと同様のBindablebaseクラスがあります。
柔軟なViewModelLocatorには規則がありますが、それらの規則をオーバーライドし、疎結合の方法でビューとViewModelを宣言的に接続できます。
モジュール性
コードを部分的に完全に疎結合されたクラスライブラリに分割し、実行時にそれらをまとめてエンドユーザーのためにまとまりのある全体にすることができますが、コードは完全に分離されたままです。
UIの構成/領域
プラグを行うのは、UIコンテナ自体への明示的な参照を必要とするビューなしで、ビューをコンテナにプラグインする機能です。
ナビゲーション
Prismには、前方ナビゲーションと後方ナビゲーション、ビューモデルをナビゲーションプロセスに直接参加させるナビゲーションスタックなど、リージョンの最上位に位置するナビゲーション機能があります。
コマンド
Prismにはコマンドがあるため、前の章で使用したMyICommandと非常によく似たデリゲートコマンドがありますが、メモリリークから保護するための特別な堅牢性があります。
パブ/サブイベント
PrismはPub/Subイベントもサポートしています。 これらは、パブリッシャーとサブスクライバーが異なるライフタイムを持つことができる疎結合イベントであり、イベントを介して通信するために相互に明示的な参照を持つ必要はありません。
MVVMライト
MVVM LightはLaurent Bugnionによって作成され、ビューをモデルから分離するのに役立ちます。これにより、よりクリーンで保守および拡張が容易なアプリケーションが作成されます。
- また、テスト可能なアプリケーションを作成し、はるかに薄いユーザーインターフェイスレイヤーを使用できるようにします(自動的にテストするのはより困難です)。
- このツールキットは、Blendユーザーがデータコントロールを使用して「何かを見る」ことができるようにするデザイン時データの作成など、Blendのユーザーインターフェイスを開いて編集することを特に重視しています。
カリバーンマイクロ
これは、MVVMパターンの実装に役立つ小さなオープンソースフレームワークであり、すぐに使用できるものも多数サポートしています。
- Caliburn Microは、すべてのXAMLプラットフォームでアプリケーションを構築するために設計された、小さいながらも強力なフレームワークです。
- MVVMおよびその他の実績のあるUIパターンを強力にサポートすることにより、Caliburn Microはコードの品質やテスト容易性を犠牲にすることなく、ソリューションを迅速に構築できるようにします。