ソフトウェア

C# WPFアプリ MVVMモデル内の連携方法

はじめに

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>

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の連携方法の型を真似ることは、効果的というか効率的ですので、参考していただければと思います。

ただ、実際のコードよりも概念、本記事でいえば、ブロック図のほうが本質的な理解に重要になってきます。
本質的な理解ができないと、応用的な使い方ができないので、しっかり理解しましょう。

-ソフトウェア