- 追加された行はこの色です。
- 削除された行はこの色です。
&tag(WPFのためのMVVMデザイン パターン);
&tag(WPF,MVVM,WPFのためのMVVMデザイン パターン);
*目次 [#ka793b28]
#contents
*参考情報 [#i940a73b]
-[[WPF のための MODEL-VIEW-VIEWMODEL (MVVM) デザイン パターン:http://msdn.microsoft.com/ja-jp/magazine/dd419663.aspx]]
*MVVM基本 [#lbcc1f7f]
-ViewとModelの間にViewModelをはさむ。
-ViewModelがModelをラップする。View独自の状態なども管理するらしい。
-ViewModelをいちいち作るのがうざいらしい。
-実装技術が不完全でVisualStudio2010をインストールしただけでは十分ではない?ビヘイビア・トリガー・トリガーアクションの実装を可能にするにはBlend SDKの導入が必要らしい。
*上記msdnサイトのサンプルアプリ [#yd8fcd31]
**概要 [#sf66f6d2]
*概要 [#sf66f6d2]
-顧客情報を一覧表示するビューと顧客情報を新規追加するビューが存在。どちらもタブで表示。
**RelayCommandの役割 [#uc595673]
*RelayCommandの役割 [#uc595673]
-ViewにViewModelがバインドされ、ButtonなどにViewModelのICommandインターフェイスがバインドされる(「保存」などの機能を実行する)。
-ICommandからViewModelの内部構造に触りたい。しかしICommandをViewModelのインナークラスにすると複雑になるので、処理をリレーするためにRelayCommandを導入。
-例えばCutomerViewModelでは次のように定義されている。
#pre{{
public ICommand SaveCommand
{
get
{
if (_saveCommand == null)
{
_saveCommand = new RelayCommand(
param => this.Save(),
param => this.CanSave
);
}
return _saveCommand;
}
}
}}
-CustomerView.xamlでは次のようにバインドされている。
#pre{{
<!-- SAVE BUTTON -->
<Button
Grid.Row="8" Grid.Column="2"
Command="{Binding Path=SaveCommand}"
Content="_Save"
HorizontalAlignment="Right"
Margin="4,2"
MinWidth="60"
/>
}}
**ViewModelの構造 [#ve640666]
*ViewModelの構造 [#ve640666]
-ViewModelは継承構造となっている。
***ViewModelBase [#i61fb092]
**ViewModelBase [#i61fb092]
-ViewModelの階層のルート。
-INotifyPropertyChangedインターフェイスを実装。ViewModelのプロパティが変更されるたびにPropertyChangedイベントを起動する(結果バインドされたUIが更新される)。
-サブクラスのために、OnPropertyChanged()メソッドを公開している。サブクラスから文字列ベースで変更されたプロパティ名が指定されるため、実際にそのプロパティが存在するかどうか、VerifyPropertyName()メソッドを使って確認している。
#pre{{
// In ViewModelBase.cs
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
this.VerifyPropertyName(propertyName);
PropertyChangedEventHandler handler = this.PropertyChanged;
if (handler != null)
{
var e = new PropertyChangedEventArgs(propertyName);
handler(this, e);
}
}
[Conditional("DEBUG")]
[DebuggerStepThrough]
public void VerifyPropertyName(string propertyName)
{
// Verify that the property name matches a real,
// public, instance property on this object.
if (TypeDescriptor.GetProperties(this)[propertyName] == null)
{
string msg = "Invalid property name: " + propertyName;
if (this.ThrowOnInvalidPropertyName)
throw new Exception(msg);
else
Debug.Fail(msg);
}
}
}}
-CustomewViewModelサブクラスからはつぎのように呼び出されている。
#pre{{
public string Email
{
get { return _customer.Email; }
set
{
if (value == _customer.Email)
return;
_customer.Email = value;
base.OnPropertyChanged("Email");
}
}
}}
***CommandViewModel [#r790134d]
**CommandViewModel [#r790134d]
-メインウィンドウに表示される。"View all customers", "Create new customer"などのリンクに対するViewモデル。
-コマンドをセットして実行するだけの役割。
-MainWindowViewModelで次のように生成されている。
#pre{{
public ReadOnlyCollection<CommandViewModel> Commands
{
get
{
if (_commands == null)
{
List<CommandViewModel> cmds = this.CreateCommands();
_commands = new ReadOnlyCollection<CommandViewModel>(cmds);
}
return _commands;
}
}
List<CommandViewModel> CreateCommands()
{
return new List<CommandViewModel>
{
new CommandViewModel(
Strings.MainWindowViewModel_Command_ViewAllCustomers,
new RelayCommand(param => this.ShowAllCustomers())),
new CommandViewModel(
Strings.MainWindowViewModel_Command_CreateNewCustomer,
new RelayCommand(param => this.CreateNewCustomer()))
};
}
}}
-MainWindowResource.xamlでDateTemplateとして定義され、MainWindow.xamlに埋め込まれている。
#pre{{
<DataTemplate x:Key="CommandsTemplate">
<ItemsControl IsTabStop="False" ItemsSource="{Binding}" Margin="6,2">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Margin="2,6">
<Hyperlink Command="{Binding Path=Command}">
<TextBlock Text="{Binding Path=DisplayName}" />
</Hyperlink>
</TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</DataTemplate>
}}
***WorkspaceViewModel [#i4e571b0]
**WorkspaceViewModel [#i4e571b0]
-ViewModelBaseを直接継承し、「閉じる」機能を実装。
***MainWindowViewModel [#pd103d5d]
**MainWindowViewModel [#pd103d5d]
-メニューから呼び出される「閉じる」機能は、AppクラスのOnStartupで設定される。
#pre{{
EventHandler handler = null;
handler = delegate
{
viewModel.RequestClose -= handler;
window.Close();
};
viewModel.RequestClose += handler;
}}
-顧客一覧ビュー、顧客登録ビューの各タブにある×ボタンがおされたときビューを非表示にする処理を設定するのも、MainWindowViewModelの役割。
--ObservableCollection<WorkspaceViewModel> _workspacesでタブごとのViewModelを管理。
--タブ生成時にViewModelに、CustomerViewModel、AllCustomersViewModelのインスタンスを追加。
--OnWorkspacesChanged()により、生成されたViewModelのRequestCloseにOnWorkspaceRequestClose()を設定。
--これにより、×が押されたときに、MainWindowViewModelのOnWorkspaceRequestCloseが呼び出され結果としてビューが非表示になる。
ちなみに、タブ生成時は次のコードでタブの順番を制御している。TabControlを使っているのでバインドされているWorkspacesが変更されるだけで自動的にタブは増える。
#pre{{
void SetActiveWorkspace(WorkspaceViewModel workspace)
{
Debug.Assert(this.Workspaces.Contains(workspace));
ICollectionView collectionView = CollectionViewSource.GetDefaultView(this.Workspaces);
if (collectionView != null)
collectionView.MoveCurrentTo(workspace);
}
}}
※[[Bea Stollnitz » WPF’s CollectionViewSource:http://bea.stollnitz.com/blog/?p=387]]
**データモデルとリポジトリ [#pff35a78]
*データモデルとリポジトリ [#pff35a78]
-全てのCustomerはCustomerRepositoryで管理される。
-CustomerViewModelでSave()が実行されるとCustomerRepositoryに新しいCustomerが追加される。
**新しい顧客のデータ入力フォーム [#i138ca69]
*新しい顧客のデータ入力フォーム [#i138ca69]
-CustomerView.xamlが定義。
-Customerクラス・CustomerViewModelクラスの検証メソッドにより検証される。
-Customer Typeリストボックスの初期値は"(Not Specified)"。CustomerのIsCompanyプロパティはboolがたなので困る(true or falseしかとれない)。→ViewModelを使う。
-CustomerViewModelでは次のようにリストボックスの選択肢と、CustomerTypeのアクセサを定義。
#pre{{
public string[] CustomerTypeOptions
{
get
{
if (_customerTypeOptions == null)
{
_customerTypeOptions = new string[]
{
"(Not Specified)",
"Person",
"Company"
};
}
return _customerTypeOptions;
}
}
public string CustomerType
{
get { return _customerType; }
set
{
if (value == _customerType ||
String.IsNullOrEmpty(value))
return;
_customerType = value;
if (_customerType == "Company")
{
_customer.IsCompany = true;
}
else if (_customerType == "Person")
{
_customer.IsCompany = false;
}
base.OnPropertyChanged("CustomerType");
base.OnPropertyChanged("LastName");
}
}
}}
-保存時はCustomerクラスのIsValid()により検証している。
**顧客一覧 [#q08767ad]
*顧客一覧 [#q08767ad]
-AllCustomersView.xamlで定義。
-CustomerViewModelの一覧が、AllCustomersとして公開されている
#pre{{
public ObservableCollection<CustomerViewModel> AllCustomers { get; private set; }
}}
-CustomerRepositoryを監視し、Customerが追加されたタイミングで自分の管理するAllCustomersに新しいCustomerViewModelを追加する。
#pre{{
void OnCustomerAddedToRepository(object sender, CustomerAddedEventArgs e)
{
var viewModel = new CustomerViewModel(e.NewCustomer, _customerRepository);
this.AllCustomers.Add(viewModel);
}
}}
-顧客が選択されたり選択解除されたりしたときのために、CustomerViewModelのPropertyChangedに、OnCustomerViewModelPropertyChangedがセットされている。
-CustomerViewModelのIsSelectedが変更された結果OnCustomerViewModelPropertyChangedが呼ばれ、そのプロパティ名が"IsSelected"なら、TotalSelectedSalesが変更されたことをUIに通達する。
#pre{{
void OnCustomerAddedToRepository(object sender, CustomerAddedEventArgs e)
{
var viewModel = new CustomerViewModel(e.NewCustomer, _customerRepository);
this.AllCustomers.Add(viewModel);
}
}}