はじめに
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 ObservableCollectionData { 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>
vタグで内側のViewクラスを設定しています。
あと、内側の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とバインド】
... //画面内部のViewの表示を制御するコードを書く ...
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の連携方法の型を真似ることは、効果的というか効率的ですので、参考していただければと思います。
ただ、実際のコードよりも概念、本記事でいえば、ブロック図のほうが本質的な理解に重要になってきます。
本質的な理解ができないと、応用的な使い方ができないので、しっかり理解しましょう。