C# WPFアプリケーション View、ViewModel、Modelの連携方法について理解しましょう。
WPFアプリケーション開発を行う上で必要不可欠なものです。
View、ViewModel、Modelの連携方法の型を真似るのも効果的ですので、例を交えながら、解説していきます。
View-ViewModel-Model間の連携例
Viewの初期化
Viewの初期化、画面の初期化ではViewとViewModelのバインドを行います。
では、コードの例をみてみましょう。
【View側】
xamlファイルです。
<Window x:Class="xxxView"
…
<DataGrid ItemsSource="{Binding Path=Data}" x:Name="dataGrid" />
…
</Window>
DataはViewModel側のDataプロパティと紐づきます。
ここを介して表に表示するリストのデータをやりとりします。
xaml.csファイルです。
public partial class xxxView : Window {
public xxxView() {
InitializeComponent();
this.DataContext = new xxxViewModel();
}
}
コードビハインド側、View側が主導権をもってViewModel間とのバインドを行っています。
this.DataContext = new xxxViewModel();
でViewとViewModelのバインディングを実行しています。
【ViewModel側】
public class xxxViewModel {
// バインディングの指定先プロパティ
public ObservableCollection Data { get; }
// コンストラクタ(データ入力)
public xxxViewModel() {
Data = new ObservableCollection {
new Test { Subj="国語", Points=90, Name="田中 一郎", ClassName="A" },
new Test { Subj="数学", Points=50, Name="鈴木 二郎", ClassName="A" },
new Test { Subj="英語", Points=90, Name="佐藤 三郎", ClassName="B" }
};
}
}
View側でもあったDataプロパティをViewModel側でも定義します。
2つ目の例をみてみましょう。
Viewを外側と内側に分けて設計する例です。
【View側,Windowクラス,外枠】
<Window ...>
<TabItem Header="yyyタブ">
<v:xxxView DataContext="{Binding xxxVM}" />
</TabItem>
</Window>
vタグで内側のViewクラスを設定しています。
【View側,UserControlクラス,内側】
<UserControl ...>
...
画面内部の細かい表示方法を書く
...
</UserControl>
内側のViewクラスは.NetのWindowクラスではなく、UserControlクラスで作成するのがポイントです。
【ViewModel側,外枠のViewとバインド】
#region xxxVM変更通知プロパティ
private xxxViewModel _xxxVM;
public xxxViewModel xxxVM {
get { return _xxxVM; }
set {
if (_xxxVM == value)
return;
_xxxVM = value;
RaisePropertyChanged("xxxVM");
}
}
#endregion
...
xxxVM = new xxxViewModel();
...
外側のViewとバインドするViewModelの中で内側のViewModelのインスタンス、プロパティを定義と生成を行います。
ViewModelからViewへの情報通知
ViewModel側からViewとバインドした変数、プロパティの変化を通知する例です。
では、コードの例をみてみましょう。
【ViewModel側】
#region xxx_Prop変更通知プロパティ
private string _xxx_Prop;
public string xxx_Prop
{
get { return _xxx_Prop; }
set
{
if (_xxx_Prop == value)
return;
_xxx_Prop = value;
RaisePropertyChanged("xxx_Prop");
}
}
#endregion
...
...
xxx_Prop = "Viewへ通知だ";
...
ViewModel側でViewとバインドする変数、プロパティの定義、プロパティの値の更新、プロパティの変更に関するイベントの発行を行っています。
RaisePropertyChangedはLivetライブラリのイベント発行関数です。
【View側】
...
<TextBlock x:Name="textBlock" ...,Text="{Binding xxx_Prop}"/>
...
View側ではViewModelからのxxx_Propプロパティの変更通知を受信すると、TextBlockクラスのTextプロパティの値を更新します。
Viewの変化をViewModelへ通知
View側で発生したプロパティの変更をViewModelへ通知する例です。
では、コードの例をみてみましょう。
【View側】
...
<Button Content="ViewModelへ通知ボタン" ... Command="{Binding xxx_Prop}"/>
...
ボタンがクリックされるとCommand型のxxx_Propプロパティの変更通知をViewModelへ通知します。
【ViewModel側】
...
#region xxx_Prop変更通知プロパティ
private ViewModelCommand _xxx_Prop;
public ViewModelCommand xxx_Prop
{
get
{
if (_xxx_Prop == null)
{
_xxx_Prop = new ViewModelCommand(xxxProc_Handler);
}
return _xxx_Prop;
}
}
...
//イベントハンドラの定義
public void xxxProc_Handler()
{
…
}
#endregion
...
ViewModel側ではViewからのCommand型のxxx_Propプロパティの変更通知を受信すると、イベント関数xxxProc_Handlerを実行します。
異なるViewModel間の情報通知
異なるViewModel間で情報をやりとりする例です。
例えば、内側のViewとバインドしたViewModelから外側のViewとバインドしたViewModelへ情報を通知したい場合があります。
では、コードの例をみてみましょう。
【ViewModelA側(送信側)】
DispatcherHelper.UIDispatcher.BeginInvoke((Action)(() =>
{
xxxModelInstance.ProrertyNotify.Raise("xxxMessage");
}));
Raise関数はModel側で定義したイベント通知を行う関数です。
【ViewModelB側(受信側)】
public class xxxWindowViewModel : xxxViewModel, IDisposable
{
//イベントリスナーの宣言
private PropertyChangedEventListener listener_xxx;
...
new public void Initialize()
{
//イベントリスナーの生成
listener_xxx = new PropertyChangedEventListener(xxxModelInstance.ProrertyNotify)
{
{ "xxxMessage", (_, __) => { xxx_Func(); } }
};
//イベントリスナーの自動解放設定(Disposeが呼ばれた時に解放されるようにするため)
CompositeDisposable.Add(listener_xxx);
...
}
//イベントハンドラの定義
private void xxx_Func()
{
…
}
受信側のViewModelBは送信側のViewModelAからModelを経由してxxxMessageイベントを受信するとコールバック関数xxx_Funcを実行します。
CompositeDisposable.Add(イベントリスナー)は画面を閉じたとき、Disposeイベントが発生したタイミングで自動的にイベントリスナーのメモリを解放させるための登録です。
このCompositeDisposableへの登録を忘れるとメモリリークの原因となるので、忘れないように注意しましょう。
ModelからViewModelへの情報通知
ModelからViewModelへ情報通知する例です。
では、コードの例をみてみましょう。
【Model側】
public class xxxModel : Model
{
...
#region xxx_Prop変更通知プロパティ
private string _xxx_Prop;
public string xxx_Prop
{
get { return _xxx_Prop; }
set
{
_xxx_Prop = value;
//Livetによるイベント発行
RaisePropertyChanged("xxx_Prop");
}
}
#endregion
...
xxx_Prop = "ViewModelへ通知!";
...
}
Model内で使うxxx_Propプロパティの定義及び変更を行っています。
定義の中のRaisePropertyChanged関数はLivetライブラリのイベント発行関数です。
【ViewModel側】
public class xxxViewModel : ViewModel
{
private xxxModel xxxmodel_instance;
//イベントリスナーの宣言
private PropertyChangedEventListener listenerXxxModelInstance; ...
//コンストラクタ
public xxxViewModel()
{
xxxmodel_instance = new xxxModel();
…
//イベントリスナーの生成
listenerXxxModelInstance = new PropertyChangedEventListener(xxxmodel_instance)
{
{"xxx_Prop", (sender, e) => { xxx_PropFunc(); } }
}
//イベントリスナーの自動解放設定(Disposeが呼ばれた時に解放されるようにするため)
CompositeDisposable.Add(listenerXxxModelInstance);
…
}
//イベントハンドラの定義
private void xxx_PropFunc()
{
Console.WriteLine("Modelからの通知受け取ったよ!");
}
…
}
受信側のxxxViewModelは送信側のxxxModelからModelを経由してxxx_Propイベントを受信するとコールバック関数xxx_PropFuncを実行します。
CompositeDisposable.Add(イベントリスナー)は画面を閉じたとき、Disposeイベントが発生したタイミングで自動的にイベントリスナーのメモリを解放させるための登録です。
このCompositeDisposableへの登録を忘れるとメモリリークの原因となるので、忘れないように注意しましょう。
ViewModelからModelへ情報通知
ViewModelからModelへの情報通知の例です。
では、コードの例をみてみましょう。
【ViewModel側】
{
…
xxxModelInstance.SetInfo(100);
…
}
ViewModel側ではModel側が用意したSetInfo関数に値を設定して、呼んでいます。
【Model側】
public class xxxModel : Model
{
…
// 情報通知
public void SetInfo(int val) {
Console.WriteLine($@"{val}を受信したよ");
return;
}
…
}
Model側ではViewModel側から自クラスで定義したSetInfo関数が呼ばれることで、ViewModelからの情報を受け取ります。
最後に
今回はC# WPFアプリケーション View、ViewModel、Modelの連携方法について、コード例を示しながらご紹介しました。
View、ViewModel、Modelの連携方法の型を真似ることは、効果的というか効率的ですので、参考していただければと思います。
ただ、実際のコードよりも概念、本記事でいえば、ブロック図のほうが本質的な理解に重要になってきます。
本質的な理解ができないと、応用的な使い方ができないので、しっかり理解しましょう。