C#でMVVMアプリを作ってみる

C#のXAMLを使ったWPFアプリを、MVVMパターンで作ってみます。MVVMについては、他のページを参照してください。 下記の環境で試しました。

  • Visual Studio 2015 Express for Windows Desktop (C#)

目次

  1. 作ってみた
    1. 何を作る?
    2. View
    3. Modelの一部
    4. ViewModel
    5. Model
    6. 実行してみた
  2. まとめ

作ってみた

何を作る?

下記のようなアプリを作ります。

  • テキストボックスに入力した文字列を、ボタンを押したら過去に入力済みの文字列に付け加える形で保持します。

  • ラベルに保持している入力済み文字列を表示します。

  • 入力済み文字列をクリアするボタンを付けます。

View

まず、Viewを作ります。フォームに、ボタン2つと、テキストボックスを1つと、ラベルを1つ配置します。上から順に、テキストボックス、ボタン、ラベル(見えませんが)、ボタンの順に並べてます。 160125-1-01 XAMLは下記のようになります。

<Window x:Class="mvvm_trial.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:mvvm_trial"
        mc:Ignorable="d"
        Title="MainWindow" Height="200" Width="200">
    <Grid>
        <TextBox x:Name="textBox" Text="{Binding InputText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Height="23" Margin="10,10,0,0" TextWrapping="Wrap" VerticalAlignment="Top" Width="175"/>
        <Button x:Name="buttonAddItem" Command="{Binding AddItem}" Content="Add Item" HorizontalAlignment="Left" Margin="10,38,0,0" VerticalAlignment="Top" Width="175"/>
        <Button x:Name="buttonReset" Command="{Binding ResetItem}" Content="Reset" HorizontalAlignment="Left" Margin="10,126,0,0" VerticalAlignment="Top" Width="175"/>
        <Label x:Name="label" Content="{Binding StoredText, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}" HorizontalAlignment="Left" Margin="10,83,0,0" VerticalAlignment="Top" Width="175"/>

    </Grid>
</Window>

ViewのコントロールとViewModelのプロパティのバインディングは下表のようにしました。

TextBox

Text

InputText

Text="{Binding InputText, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"

Button

Command

AddItem

Command="{Binding AddItem}"

Label

Content

StoredText

Content="{Binding StoredText, Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"

Button

Command

ResetItem

Command="{Binding ResetItem}"

Mode=TweWayは、ViewからViewModelを変更することもあるしViewModelからViewを変更することもあるということです。ユーザーがテキストボックスに入力した文字はデータに反映させなきゃいけないし、入力確定してボタンを押したらViewModelのデータをクリアしますのでそれをテキストボックスに反映しますので、両方向からプロパティが更新されるからです。 UpdateSourceTrigger=PropertyChangedは表示の内容を更新するタイミングの指定です。PropertyChangedというイベントが生じたときに表示が更新されるようにします。

Modelの一部

先にデータストック用のクラスのコードです。

namespace mvvm_trial
{
    public class DataStock
    {
        public string stockedItem { get; set; }
    }
}

文字列のデータを保持するだけのクラスです。

ViewModel

ViewModelのコードです。

using System.Windows.Input;
using Microsoft.TeamFoundation.MVVM;

namespace mvvm_trial
{
    public class MainWindowViewModel : ViewModelBase
    {
        // Addボタンとのバインド
        private ICommand _AddItem;
        public ICommand AddItem
        {
            get
            {
                if (_AddItem == null)
                {
                    _AddItem = new RelayCommand(ExecuteAddItem);
                }
                return _AddItem;
            }
        }
        private void ExecuteAddItem()
        {
            _stocker.stockedItem = _stocker.stockedItem + InputText;
            InputText = string.Empty;
            RaisePropertyChanged("StoredText");
        }

        // Resetボタンとのバインド
        private ICommand _ResetItem;
        public ICommand ResetItem
        {
            get
            {
                if (_ResetItem == null)
                {
                    _ResetItem = new RelayCommand(ExecuteResetItem);
                }
                return _ResetItem;
            }
        }
        private void ExecuteResetItem()
        {
            _stocker.stockedItem = string.Empty;
            RaisePropertyChanged("StoredText");
        }

        // データの場所を保管するためのフィールドとプロパティ
        private DataStock _stocker;
        public DataStock Stocker
        {
            set
            {
                _stocker = value;
            }
        }

        // テキストボックスとのバインド
        private string _InputText;
        public string InputText
        {
            get { return _InputText; }
            set
            {
                _InputText = value;
                RaisePropertyChanged("InputText");
            }
        }

        // ラベルとのバインド
        public string StoredText
        {
            get
            {
                return _stocker.stockedItem;
            }
        }
    }
}

以下、小分けして解説をメモっておきます。 usingでMicrosoft.TeamFoundation.MVVM名前空間を追加します。このコードを書く前に、ソリューションエクスプローラーでMicrosoft.TeamFoundation.Controlsを追加しておいてください。

using Microsoft.TeamFoundation.MVVM;

Microsoft.TeamFoundation.MVVM名前空間のViewModelBaseを継承します。

public class MainWindowViewModel : ViewModelBase

AddItemボタンが押された時の動作の設定です。AddItemプロパティにアクセスされると、getアクセサーが呼び出されます。そうすると、RelayCommand()がExcecuteAddItemメソッドを呼び出します。ExcecuteAddItemメソッドは、保存されているデータの文字列にInputTextプロパティの中身を追加して、InputTextプロパティを空にし、RaisePropertyChanged()メソッドでStoredTextプロパティが変更されたというPropertyChangedイベントを発行します。そうすると、StoredTextプロパティにバインディングされたLabelのContentプロパティが更新されます。

private ICommand _AddItem;
public ICommand AddItem
{
    get
    {
        if (_AddItem == null)
        {
            _AddItem = new RelayCommand(ExecuteAddItem);
        }
        return _AddItem;
    }
}
private void ExecuteAddItem()
{
    _stocker.stockedItem = _stocker.stockedItem + InputText;
    InputText = string.Empty;
    RaisePropertyChanged("StoredText");
}

もう一つのボタンのコードも同様の流れです。 Stockerプロパティは、データ保存用インスタンスの「参照」を受け取るプロパティです。というわけで、setアクセサーだけです。

private DataStock _stocker;
public DataStock Stocker
{
    set
    {
        _stocker = value;
    }
}

テキストボックスとのバインディングですが、setアクセサーでRaisePropertyChanged()メソッドを実行して、自身のPropertyChangedイベントを発行するようにします。InputTextプロパティに値が入れられたときに、このイベントでTextBoxのTextプロパティを更新するわけです。具体的には、ExcecuteAddItem()メソッドの中でInputTextプロパティに空文字を入れてますが、その際にPropertyChangedイベントを発行してTextBoxのTextプロパティを更新するのです。

private string _InputText;
public string InputText
{
    get { return _InputText; }
    set
    {
        _InputText = value;
        RaisePropertyChanged("InputText");
    }
}

Model

最後に、Modelの本体部分のコードです。

using System.Windows;

namespace mvvm_trial
{
    public partial class MainWindow : Window
    {
        public DataStock stocker = new DataStock(); // データ用のインスタンスの生成

        public MainWindow()
        {
            InitializeComponent();

            stocker.stockedItem = string.Empty; // データの初期化
            MainWindowViewModel mainWindowVM = new MainWindowViewModel(); // ViewModelの生成
            mainWindowVM.Stocker = stocker; // ViewModelへデータ用インスタンスの「参照」を渡す
            this.DataContext = mainWindowVM; // ViewModelとViewをバインド
        }
    }
}

このアプリではModel部分ではデータの保持しかしませんので、必要なインスタンスを作ってバインディングの処理をしているだけです。

実行してみた

プロジェクトをビルドして実行すると、下図のようなアプリが起動します。 160125-1-02 下図は、テキストボックスに「1回目」と入力して「Add Item」ボタンをクリックし、その後テキストボックスに「2回目」と入力したところです。ちゃんとラベルに「1回目」と表示されてますね。 160125-1-03 この状態で「Reset」ボタンをクリックするとラベルの表示が空になります。テキストボックスには「2回目」という文字列が入ったままです。「Reset」ではなく「Add Item」ボタンをクリックすると、ラベルに「1回目2回目」と表示されテキストボックスが空になります。

まとめ

MVVMの処理の流れは、ざっくり言うとこんな感じでしょうか。

  1. データ保持用インスタンス(Model)をViewModelのデータ用プロパティに参照渡しする。

  2. ViewModelのデータ用プロパティと、データ表示用コントロール(View)(テキストボックスとか)のプロパティをバインディングする。

  3. ViewModelの操作用プロパティと、入力用のコントロール(View)(ボタンとか)のプロパティをバインディングする。

  4. ユーザーが入力用コントロールを操作すると、ViewModelのプロパティのgetアクセサーが呼び出される。

  5. ViewModelのgetアクセサーがViewModelの処理用メソッドを呼び出す。

  6. ViewModelの処理用メソッドがViewModelのデータ用プロパティを操作する。

  7. ViewModelの処理用メソッドでデータ用プロパティ変更のイベントを発行する。

  8. データ用プロパティにバインディングされたViewのプロパティが更新される。

なんというか、結構遠回りな感じがします。

公開日

広告