LoginSignup
53
64

More than 5 years have passed since last update.

WPF/MVVM/C#/Prism5.0 ViewModelを少し便利に-BindableBaseとDelegateCommand-

Last updated at Posted at 2015-03-13

WPFでMVVMは難しい

残念なことに、WPFでMVVMパターンを適用する際には、.NET標準だけ使うとなると、綺麗でわかりやすく保守が容易なコードが書けません。
書けないような基盤しかないのです。
なので、PrismなどのMVVM基盤ライブラリが必要となります。
https://msdn.microsoft.com/ja-jp/library/gg406140.aspx

他にも様々なライブラリが公開されていますが、MS謹製ということで今回はPrismを利用しようと思います。
Prismを利用することで得られるメリットを公開します。
以下の予定です。

  1. BindableBase/DelegateCommand ~ViewModelの基盤~
  2. ErrorsContaier ~便利なエラー通知~
  3. ViewModelLocationProvider ~ViewとViewModelを自動で関連付け~
  4. Regionってなんなのさ ~Viewの配置をお手軽に~
  5. IModuleとUnity ~UIでDI~
  6. DIPパターンの恩恵 ~MSBuildで並列ビルド~

本稿は上記1の記事になります。

※Visual Studio 2013 Community Editionで実験しています。

Prismのインストール

Nuget Package Manager Consoleにて、以下のように打ちます。

Install-Package Prism

そうすると、以下のようにモジュール参照が追加になります。

Image.png

package.configは以下のようになります。
xml:package.config
<?xml version=" 1.0" encoding=" utf-8"?>
<packages>
<package id=" CommonServiceLocator" version=" 1.2" targetFramework=" net45" />
<package id=" Prism" version=" 5.0.0" targetFramework=" net45" />
<package id=" Prism.Composition" version=" 5.0.0" targetFramework=" net45" />
<package id=" Prism.Interactivity" version=" 5.0.0" targetFramework=" net45" />
<package id=" Prism.Mvvm" version=" 1.0.0" targetFramework=" net45" />
<package id=" Prism.PubSubEvents" version=" 1.0.0" targetFramework=" net45" />
</packages>

たくさんあって混乱しそうですが、とりあえず今はこのまま!
ビルドすると、出力パスにはこんなにたくさんのDLLやフォルダが!!
Image.png

Prismのサイズは、多言語Resource含めて約1MBです。
これを多いと思うか否かは環境次第ですが、訳もわからずDLLが増えるのは嫌ですよね。

使ってみる

UIはこんな感じを想定しています。ボタンを押したら下に答えが出るようなアプリです。
(かずきさんのアプリを参考しています。)
https://code.msdn.microsoft.com/MVVM-Light-toolkitMessenger-0ec2e5c4

Image.png

XAMLはこんな感じです。

CalcView.xaml
<Window x :Class="KStore.Calc._1.CalcView"
        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:KStore.Calc._1"
        mc:Ignorable ="d"
        Title="Calc" Height ="350" Width="525">
    <Grid >
        <Grid.RowDefinitions>
            <RowDefinition Height ="120*"/>
            <RowDefinition Height ="200*"/>
        </Grid.RowDefinitions>
        <Grid Name ="InputRegion" Grid.Row="0">
            <Grid.RowDefinitions>
                <RowDefinition Height ="30*"/>
                <RowDefinition Height ="30*"/>
                <RowDefinition Height ="30*"/>
                <RowDefinition Height ="30*"/>
            </Grid.RowDefinitions>
            <TextBox Grid.Row ="0" Text="{ Binding LeftValue}" Name="LeftValue" VerticalContentAlignment="Center" TextAlignment="Center" />
            <TextBlock Grid.Row ="1"  Text ="+" HorizontalAlignment="Center" VerticalAlignment="Center"/>
            <TextBox Grid.Row ="2" Text="{ Binding RightValue}" Name="RightValue" VerticalContentAlignment="Center" TextAlignment="Center" />
            <Button Grid.Row ="3" Name="CalcButton" Content="=" Command ="{Binding CalcCommand , Mode=OneWay}" />
        </Grid>
        <Grid Name ="OutputRegion" Grid.Row="1">
            <TextBlock Name ="Answer" Text="{ Binding AnswerValue}" HorizontalAlignment="Center" VerticalAlignment="Center" FontSize="48pt"/>
        </Grid>
    </Grid >
</Window>

ViewModelとViewの紐付けは、今は下記のようになります。今回はベタでいきます。わかりやすいですけどね。

CalcView.xaml.cs
using System.Windows;

namespace KStore.Calc._1
{   
    public partial class CalcView : Window
    {
        public CalcView()
        {
            InitializeComponent();
            this.DataContext = new CalcViewModel(); // これが紐付けって行。
        }
    }
}

ViewModelは以下のようになります。ここでBindableBaseとDelagateCommandを使用しています。

CalcViewModel.cs
using KStore.Calc._1.Model;
using Microsoft.Practices.Prism.Commands;
using Microsoft.Practices.Prism.Mvvm;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;

namespace KStore.Calc._1
{
    public class CalcViewModel : BindableBase
    {
        private string _leftValue;
        public string LeftValue
        {
            get { return _leftValue; }
            set { this .SetProperty(ref this._leftValue, value); }
        }

        private string _rightValue;
        public string RightValue
        {
            get { return _rightValue; }
            set { this .SetProperty(ref this._rightValue, value); }
        }

        private string _answerValue;
        public string AnswerValue
        {
            get { return _answerValue; }
            set { this .SetProperty(ref this._answerValue, value); }
        }

        private ICommand calcCommand;

        public ICommand CalcCommand
        {
            get { return this.calcCommand ?? ( this.calcCommand = new DelegateCommand(CalcExecute, CanCalcExecute)); }
        }

        private bool CanCalcExecute()
        {
            return true ;
        }

        private void CalcExecute()
        {        
            AnswerValue = IntToString( Calculation.Sum(StringToInt(LeftValue), StringToInt(RightValue)));
        }

        private int StringToInt(string src)
        {
            int ret = 0;
            if( int .TryParse(src, out ret) )
            {
                return ret;
            }
            throw new ArgumentException( "src" + src);
        }

        private string IntToString(int src)
        {
            return src.ToString();
        }

    }

}

考え方は単純で、

INotifyPropertyChanged

使いたくないんですよね。
使っちゃうと、文字列でプロパティを表現しないといけません。

下記のようなメソッドを用意してあげて、

notifypropertychangedexample.cs
private void NotifyPropertyChanged(string info)
{
    if (PropertyChanged != null)
    {
        PropertyChanged(this, new PropertyChangedEventArgs(info));
    }
}

public string RightValue
{
     get { return _rightValue; }
     set
     { 
         this .(ref this._rightValue, value); 
         NotifyPropertyChanged("RightValue"); // <-- これがダルい。
     }
}

プロパティのセッターで、文字列指定が必要になります。。
プロパティ名が変わってもVisual Studioのリファクタ機能で追従してくれない。
実行時にバグがわかると。
これではC#のような静的言語の旨味も半減です。
コンパイルを活用したいんですよね。せっかくコンパイルするんだから。

これを回避してくれるのが、BindableBase。
https://msdn.microsoft.com/en-us/library/microsoft.practices.prism.mvvm.bindablebase%28v=pandp.50%29.aspx
説明少なっ!

BindableBaseを継承すると、上記サンプルのように、

set { this .SetProperty(ref this._leftValue, value); }

これで良いということになります。
これだけでもPrism使う価値がある!と思う。 ちなみに昔は

NotificationObject

っていう名前だったんですよね。
BindableBaseのほうがわかりやすくて良い。

つづいて、DelegateCommandです。
https://msdn.microsoft.com/en-us/library/microsoft.practices.prism.commands.delegatecommand%28v=pandp.50%29.aspx
これは、もしかしたら不要かも。

このサンプルレベルだと恩恵を受けますが、ある程度の規模の開発になって、ViewやViewModelが超複雑になる場合、
Commandの処理とViewModelを切り離したくなる時がきます。
DelegateCommandだと、ViewModelに直接delegate用メソッドを定義しないとなりません。
ViewやViewModelは、UserControlで切り出して小分けで作るのは、キツイ場合があります。
全部一緒だと何かと都合が良い。UIってそうなんですよね。Class分割しすぎると生産性落とします。
だけどViewModelにロジックを書きたくない。
ただのViewのデータ部分を反映する箱にしておきたい。そんな場合に対応できないです。

そうゆう場合は、ICommandを直接実装してもいいんじゃないかなと考えます。

Commands.cs
public class CalcCommand_SeparatedViewModel : ICommand
{
    CalcViewModel _parentViewModel;

    public CalcCommand_SeparatedViewModel(CalcViewModel parentViewModel)
    {
        _parentViewModel = parentViewModel;
    }

    public bool CanExecute(object parameter)
    {
        return true ;
    }

    public event EventHandler CanExecuteChanged;

    public void Execute(object parameter)
    {
        _parentViewModel.AnswerValue = Util.IntToString(Calculation .Sum(Util.StringToInt(_parentViewModel.LeftValue), Util.StringToInt(_parentViewModel.RightValue)));
    }
}

ViewModelでの定義は以下のようになります。

CalcViewModel.cs
private ICommand _calcCommand_SeparetedViewModel;

public ICommand CalcCommand_SeparetedViewModel
{
    get { return this._calcCommand_SeparetedViewModel ?? ( this._calcCommand_SeparetedViewModel = new CalcCommand_SeparatedViewModel (this)); }
}

まとめ

という訳で、Prism5.0使うなら
BindableBaseは活用しよう!
DelegateCommandは用途に応じて活用しよう!
ということでした。

続いて、ErrorsContainerも試してみます。

53
64
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
53
64