t90 きっと怖くないmvvm & mvpvm

70
わわわわわわ わわわわわ #90 わわわわわわわ MVVM&MVPVM わ わわ @akatukisiden

Upload: -

Post on 28-May-2015

2.472 views

Category:

Technology


7 download

TRANSCRIPT

Page 1: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

きっと怖くない MVVM&MVPVM

暁 紫電@akatukisiden

Page 2: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

自己紹介

• HN: 暁 紫電• Twitter: @akatukisiden• 本名 : 伊藤 伸男• フリーランス プログラマー• 使用言語

–C++–C#–C++/CLI

Page 3: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

アジェンダ

• 目的• MVVM とは• View と ViewModel の分

離• 細かいことは程々にし

て実装してみた• MVVM での画面遷移• MVVM まとめ

• MVPVM とは• とりあえず実装してみた• MVPVM での画面遷移• MVPVM まとめ• まとめ

Page 4: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

このセッションの目的

• 細かいことは置いておいて、とりあえず MVVM,MVPVM っぽい形でプログラムを書けるようにする。

• MVVM でのナビゲーション手法について理解する

• MVPVM でのナビゲーションについて理解する

• 疎結合、密結合、コードビハインドなどの用語をできる限り使わずに説明する

Page 5: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MVVM(Model-View-ViewModel) とは

• 最近流行りの UI アーキテクチャパターン• いくつかの理由により XAML 系フレームワーク

(WPF,Silverlight,WinRT) では必須と言われている• UI(View) とビジネスロジック (Model) のあいだに

ViewModel を置くことで二つを分離する。• View ・ ViewModel 間のやり取りはデータバインドを用

いる

Page 6: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

View• ユーザーインターフェース• UI への出力と UI からの入力を担当する。• FrameworkElement の派生クラス• XAML の記述 + 対応する (partial )class

• View の必要な情報を保持公開• View からの入力やコマンドを処

理しModel を呼び出す

ViewModel Model• ビジネスロジック• プログラムの中核となる処理• View と ViewModel 以外の部

Page 7: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

View と ViewModel の分離(疎結合と密結合)

• MVVMの説明などでよく使われる用語、疎結合と密結合

• View と ViewModel を密結合にならないようにし、疎結合に保つと良いらしい

• 「疎結合に保つ」「密結合になってしまっている」という記述はよく見るが具体的にどのような状況を疎結合・密結合と言うのか書かれていることはあまりない

• もしかしたら一般的な用語で説明する必要もないのかもしれないが、少なくとも自分にとって MVVM/MVPVM の文脈でしか聞かない言葉

Page 8: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

さまざまな資料の記述を総合すると

• View/ViewModel で互いのインスタンスや型名を直接扱うと密結合

• データバインドを使えば疎結合

Page 9: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

具体的に何が許されて何が許されないのか

• 直接触れると密結合– View は ViewModel が特定の型であることに依存してはいけな

い– View で ViewModel のインスタンスを扱ってはいけない– ViewModel は View が特定の型であることに依存してはいけな

い– ViewModel で View のインスタンスを扱ってはいけない

• データバインドは疎結合– View は ViewModel が INotifyPropertyChanged を

実装していることに依存してよい。– View は ViewModel が特定の名前のプロパティを

持つことに依存してよい

Page 10: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

厳密にいうとこれもだめかもしれない

<Window x:Class="MVVM1.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="300" Width="300" > <Window.DataContext > <MainViewModel /> </Window.DataContext>

public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.DataContext = new MainViewModel(); }

View 内で ViewModel の型を扱っている

Page 11: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

細かいことは置いておいて

• MainWindow のイベントハンドラに全部の処理を書いた状態から少しずつ修正して MVVM の形にしてみようと思います。

• テキストボックス、ラベル、ボタンを配置し、ボタンを押すとテキストボックスに入力した文字列を加工してラベルに出力するアプリを作る

※ 小さすぎて MVVM にする意味がないとか言わないでください 意味がなくてもとりあえず始めることが大事です。

Page 12: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

STEP1 :全部イベントハンドラ等に記述

Page 13: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MainWindow.xaml

<Window x:Class=“Step1.MainWindow” // 略 Title="MainWindow" Height="300" Width="300"> <Grid> <Grid.RowDefinitions><RowDefinition /><RowDefinition /><RowDefinition /></Grid.RowDefinitions> <Button Content="Button" Grid.Row="0“ HorizontalAlignment="Center" VerticalAlignment="Center" Width="120" Click="Button_Click"/> <TextBox Name="textBox1" Grid.Row="1“ HorizontalAlignment="Center" VerticalAlignment="Center“ Height="23" Width="120" /> <Border Grid.Row="2" BorderThickness="1" BorderBrush="Black" HorizontalAlignment="Center" VerticalAlignment="Center" > <Label Name="label1" HorizontalAlignment="Center" VerticalAlignment="Center" Width="120"/> </Border> </Grid></Window>

Page 14: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MainWindow.xaml.cs

public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); }

private void Button_Click(object sender,RoutedEventArgs e) { // なんか複数の関数が絡んだ複雑な処理 string f1 = Func1(textBox1.Text); string f2 = Func2(f1); string f3 = Func3(f2); label1.Content = f3; }

// 頭に B をつける private string Func1(string input) { return “B” + input; }

// 末尾に E をつける private string Func2(string input) { return input+"E"; }

    // 順番をひっくり返す。 private string Func3(string input) { var rev = input.Reverse().ToArray(); string ret = new string(rev); return ret; } }

Page 15: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

STEP2:MainWindow.xaml.cs は

イベントハンドラだけにしたいので処理内容を別クラスに移す

C++ で云うところのpImpl

Page 16: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MainWindow.xaml.cs

public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); }

private MainWindowImpl impl = new MainWindowImpl();

private void Button_Click(object sender, RoutedEventArgs e) { label1.Content = impl.Logic(textBox1.Text); } }

処理を移すための別クラス

別クラスに移した処理の呼び出し

Page 17: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MainWindowImpl.cs

public class MainWindowImpl { public string Logic(string input) { string f1 = Func1(input); string f2 = Func2(f1); string f3 = Func3(f2);

return f3; }

private string Func1(string input) { return "B" + input;}

private string Func2(string input) { return input + "E";}

private string Func3(string input) { var rev = input.Reverse().ToArray(); string ret = new string(rev); return ret; } }

Page 18: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

STEP3:入出力 (引数 /戻り値 ) が多く必要になると

記述が大変なのでデータバインディングを使ってみる

Page 19: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MainWindow.xaml

<Window x:Class=“Step3.MainWindow" // 略 Title="MainWindow" Height="300" Width="300"> <Grid><Grid.RowDefinitions><RowDefinition /><RowDefinition /><RowDefinition /></Grid.RowDefinitions> <Button Content="Button" Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center" Width="120" Click="Button_Click"/>

<TextBox Name="textBox1" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" Height="23" Width="120" Text="{Binding Input}“

<Border Grid.Row="2" BorderThickness="1" BorderBrush="Black" HorizontalAlignment="Center" VerticalAlignment="Center" > <Label Name=“label1” HorizontalAlignment=“Center” VerticalAlignment=“Center” Width=“120“   Content="{Binding Output}" /> </Border> </Grid></Window>

Page 20: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MainWindow.xaml.cs

public partial class MainWindow : Window { //  コントロールからの入力、出力をデータバインドに変換 private MainWindowImpl impl = new MainWindowImpl();

public MainWindow() { InitializeComponent(); this.DataContext = impl; }

private void Button_Click(object sender, RoutedEventArgs e) { impl.Logic(); } }

バインド対象の設定

入出力はバインドしたので引数・戻り値なしになっている

Page 21: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MainWindowImpl.cs

public class MainWindowImpl:BindingSourceBase{ public string input_; public string Input { get { return input_; } set { if (input_ != value) { input_ = value; OnPropertyChanged("Input"); } } }

public void Logic() { string f1 = Func1(Input); string f2 = Func2(f1); string f3 = Func3(f2); Output = f3; }

private string Func1(string input){ 略 } private string Func2(string input){ 略 } private string Func3(string input){ 略 }}

public string output_;public string Output{ get { return output_; } set { if (output_ != value) { output_ = value; OnPropertyChanged("Output"); } }}

Page 22: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

BindingSourceBase

public class BindingSourceBase:System.ComponentModel.INotifyPropertyChanged

{

public event PropertyChangedEventHandler PropertyChanged;

public void OnPropertyChanged(string propertyname)

{

if(PropertyChanged != null)

{

PropertyChanged(this,new PropertyChangedEventArgs(propertyname));

}

}

}

Page 23: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

STEP4: 引数・戻り値なしなら直接呼出したいの

で、 イベントハンドラを Impl に移動する。

Page 24: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MainWindow.xaml.cs

public partial class MainWindow : Window { private MainWindowImpl impl = new MainWindowImpl();

public MainWindow() { InitializeComponent();

// バインド対象の設定 this.DataContext = impl;

Button1.Click += impl.Button_Click; }

}

Click イベントに impl クラスの関数を登録

Page 25: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MainWindow.xaml

<Window x:Class=“Step3.MainWindow" // 略 Title="MainWindow" Height="300" Width="300"> <Grid><Grid.RowDefinitions><RowDefinition /><RowDefinition /><RowDefinition /></Grid.RowDefinitions> <Button Content=“Button” Grid.Row=“0” HorizontalAlignment=“Center” VerticalAlignment=“Center” Width=“120” /> <TextBox Name="textBox1" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" Height="23" Width="120" Text="{Binding Input}" /> <Border Grid.Row="2" BorderThickness="1" BorderBrush="Black" HorizontalAlignment="Center" VerticalAlignment="Center" > <Label Name="label1" HorizontalAlignment="Center" VerticalAlignment="Center" Width="120“ Content="{Binding Output}" /></Border> </Grid></Window>

イベントの登録部分を削除

Page 26: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MainWindowImpl.cs

public class MainWindowImpl:BindingSourceBase{ public void Button_Click(object sender,RoutedEventArgs e) { this.Logic(); }

public string input_; public string Input { get { return input_; } set { 略 } } public string output_; public string Output { get { return output_; } set { 略 } }

public void Logic() { string f1 = Func1(Input); string f2 = Func2(f1); string f3 = Func3(f2); Output = f3; }

private string Func1(string input){ 略 } private string Func2(string input){ 略 } private string Func3(string input){ 略 }}

Button.Click イベントから呼び出される

ビジネスロジック

Page 27: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

STEP5:Impl クラスにデータバインドと

ビジネスロジック両方があるとじゃまなのでビジネスロジックをさらに別クラスへ移動する。

Page 28: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MainWindowImpl.cs

public class MainWindowImpl:BindingSourceBase{ private BusinessLogic BL = new BusinessLogic();

public void Button_Click(object sender, RoutedEventArgs e) { Output = BL.Logic(Input); }

public string input_; public string Input { get { return input_; } set { 略 } }

public string output_; public string Output { get { return output_; } set { 略 }}

}

Page 29: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

BusinessLogic.cs

public class BusinessLogic{ public string Logic(string Input) { string f1 = Func1(Input); string f2 = Func2(f1); string f3 = Func3(f2); return f3; }

private string Func1(string input){ 略 } private string Func2(string input){ 略 } private string Func3(string input){ 略 }}

関数が増えてクラスが肥大化するのであれば外部とのインターフェースになる関数だけ残して内部実装はさらに別のクラスに移した方がいいかも

Page 30: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

STEP6:XAML環境ではロジックもコマンドとしてバインドできるので

イベントハンドラからコマンドバインディングに変更する

※WinForms でも Form クラス等に Action プロパティを作りOn○○メソッド等でアクションを発火するようにすれば可能

Page 31: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MainWindow.xaml.cs

public partial class MainWindow : Window { private MainWindowImpl impl = new MainWindowImpl();

public MainWindow() { InitializeComponent();

// バインド対象の設定 this.DataContext = impl;

// Button1.Click += impl.Button_Click; }

}

削除

Page 32: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MainWindow.xaml

<Window x:Class=“Step6.MainWindow" // 略 Title="MainWindow" Height="300" Width="300"> <Grid><Grid.RowDefinitions><RowDefinition /><RowDefinition /><RowDefinition /></Grid.RowDefinitions> <Button Content="Button" Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center" Width="120" Command="{Binding MainCommand}“ />

<TextBox Name="textBox1" Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" Height="23" Width="120" Text="{Binding Input}" />

<Border Grid.Row="2" BorderThickness="1" BorderBrush="Black" HorizontalAlignment="Center" VerticalAlignment="Center" > <Label Name="label1" HorizontalAlignment="Center" VerticalAlignment="Center" Width="120" Content="{Binding Output}" /> </Border> </Grid></Window>

Page 33: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MainWindowImpl.cs

public class MainWindowImpl:BindingSourceBase{ private BusinessLogic BL = new BusinessLogic(); public ICommand MainCommand{get;set;}

public MainWindowImpl() { MainCommand = new Microsoft.TeamFoundation.

MVVM.RelayCommand(() => { Output = BL.Logic(Input); }); } public string input_; public string Input { get { return input_; } set { 略 } }

public string output_; public string Output { get { return output_; } set{ 略 } }

ロジックのバインド用のコマンド

コマンドの中身を作成。

Page 34: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

(LastSTEP:) クラス名の変更

• MainWindowImpl→ MainViewModel• BindingSourceBase → ViewModelBase• BussinesLogic → MainModel

Page 35: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

とりあえず MVVM完成

※但し画面遷移無し

Page 36: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

画面遷移とは

• 表示されているデータでは無く、コントロール自体を変更する

• 主にユーザーコントロールやパネルの切り替え

Page 37: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MVVM での画面遷移手法

• ViewModel の型に応じてコントロールを切り替える方法。– データテンプレート

• 表示するデータの型に応じて表示方法を変える機能• 指定した型のデータの表示方法を定義する。

• コントロールの切り替えロジックを呼び出し、新しいコントロールに新しい ViewModel を関連付ける手法。– ViewModel から View を呼び出す。

• 逆方向バインディングコマンド• Behavior• TriggerAndAction

Page 38: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

とりあえずサンプル作る

• ボタンを押すことで背景色の異なる (赤・青 ) 二つのユーザーコントロールを切り替える。

• Model は省略• 切り替えロジック呼び出しのサンプルは逆方向バインディングコマンドで実装

Page 39: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

共通部分 (赤 )

<UserControl x:Class="NavigationCommon.UCRed“ ( 略 ) d:DesignHeight="150" d:DesignWidth="150" Background="Red"> <Grid> <TextBlock HorizontalAlignment="Center“ VerticalAlignment="Center" Text="{Binding Text}" /> </Grid></UserControl>

public partial class UCRed : UserControl{ public UCRed() { InitializeComponent(); }}

public class RedViewModel:ViewModelBase{ private string text = "赤 "; public string Text { set{ if (text != value) { text = value; OnPropertyChanged("Text"); } } get{ return text; } }}

View ViewModel

Page 40: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

共通部分 (青 )

<UserControl x:Class="NavigationCommon.UCBlue“ ( 略 ) d:DesignHeight="150" d:DesignWidth="150" Background=“Blue"> <Grid> <TextBlock HorizontalAlignment="Center“ VerticalAlignment="Center" Text="{Binding Text}" /> </Grid></UserControl>

public partial class UCBlue : UserControl{ public UCBlue() { InitializeComponent(); }}

public class BlueViewModel:ViewModelBase{ private string text = “青 "; public string Text { set{ if (text != value) { text = value; OnPropertyChanged("Text"); } } get{ return text; } }}

View ViewModel

Page 41: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

データテンプレート

Page 42: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

View

<Window x:Class="MainWindow" ( 略 ) Title="MainWindow" > <Window.Resources> <DataTemplate DataType="{x:Type navi:RedViewModel}"> <navi:UCRed x:Name="RedUC" /> </DataTemplate> <DataTemplate DataType=“{x:Type navi:BlueViewModel}”> <navi:UCBlue x:Name="BlueUC" /> </DataTemplate> </Window.Resources><Grid > <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions>

<Grid Grid.Column="0" Name="ParentGrid"> <ContentControl Name="ContentControl“ Content="{Binding ChildViewModel}" /> </Grid> <Grid Grid.Column="1" ><Button Content="Button“ Command="{Binding NavigationCommand}" HorizontalAlignment="Center" VerticalAlignment="Center" Width="75" Height="25" /> </Grid></Grid></Window>

ViewModel の型を直接扱っている。

public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); this.DataContext = new MainViewModel(); } }

ViewModel を表示しようとしている

Page 43: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

ViewModelpublic class MainViewModel:ViewModelBase{ ViewModelBase childVM; public ViewModelBase ChildViewModel { get { return childVM; } set { 略 } }

ICommand nCommand; public ICommand NavigationCommand { get { return nCommand; } set { 略 } }

public MainViewModel(){ NavigateRed(); }

private void NavigateRed() { ChildViewModel = new RedViewModel(); NavigationCommand = new Microsoft.TeamFoundation. MVVM.RelayCommand( (p) =>{NavigateBlue();}); }

private void NavigateBlue() { ChildViewModel = new BlueViewModel(); NavigationCommand = new Microsoft.TeamFoundation. MVVM.RelayCommand( (p) =>{ NavigateRed();} ); }}

もう一度ボタンを押したら元の色に戻るようにコマンドをリセット

データテンプレートで表示する子ViewModel

表示する ViewModel を変更

Page 44: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

逆方向バインディングコマンド

Page 45: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

Command UserControl

public class DependencyCommandControl : Control { public DependencyCommandControl():base(){ }

static DependencyCommandControl() { DefaultStyleKeyProperty.OverrideMetadata( typeof(DependencyCommandControl), new FrameworkPropertyMetadata( typeof(DependencyCommandControl)) ); }

public static readonly DependencyProperty CommandProperty = DependencyProperty.Register( "Command“, typeof(ICommand), typeof(DependencyCommandControl) );

public ICommand Command { set { SetValue(CommandProperty, value); } get { return (ICommand)GetValue(CommandProperty); } }}

XAML上で扱うには依存関係プロパティである必要があるので依存関係プロパティ化した ICommand を持つカスタムコントロールを作る

Page 46: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

View

<Window x:Class="MainWindow"( 略 )Title="MainWindow"><Grid > <Grid.ColumnDefinitions> <ColumnDefinition Width="*"/><ColumnDefinition Width="*"/> </Grid.ColumnDefinitions> <Grid Grid.Column="0" Name="ParentGrid“ >

</Grid> <Grid Grid.Column="1" > <Button Content="Button" Command="{Binding NavigationCommand}" HorizontalAlignment="Center" VerticalAlignment="Center" Width="75" Height="25"/> <DependencyCommandControl x:Name="CommandHolder“ Command="{Binding Mode=OneWayToSource,Path='SouceToTargetCommand' }" /> </Grid></Grid></Window>

ボタンクリック時に呼ばれる順方向コマンド

ViewModel から呼ばれる逆方向コマンド

逆方向バインディング

カスタムコントロール

Page 47: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

ViewModel

public class MainViewModel:ViewModelBase{ ViewModelBase childVM; public ViewModelBase ChildViewModel{get;set}

ICommand navigationcommand; public ICommand NavigationCommand { get { return navigationcommand; }set { 略 } }

ICommand sttCommand; public ICommand SouceToTargetCommand { get { return sttCommand; } set{ 略 }   }

public MainViewModel() { NavigateToRed(); }

ボタンクリックから呼び出される( 順方向 ) コマンド

ViewModel から View を呼び出すための逆方向バインディングをするコマンド

Page 48: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

ViewModel

private void NavigateToRed(){ ChildViewModel = new RedViewModel();

if (SouceToTargetCommand != null) SouceToTargetCommand.Execute(ChildViewModel);

NavigationCommand = new Microsoft.TeamFoundation.MVVM.RelayCommand( (p) => { NavigateToBlue(); });}

private void NavigateToBlue(){ ChildViewModel = new BlueViewModel();

if (SouceToTargetCommand != null) SouceToTargetCommand.Execute(ChildViewModel);

NavigationCommand = new Microsoft.TeamFoundation.MVVM.RelayCommand( (p) => { NavigateToRed(); } ); }}

子コントロール用の新しい ViewModel を作成、コマンドで View に送る

もう一度ボタンを押したら元に戻すために順方向コマンドをリセット

Page 49: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

View

public partial class MainWindow : Window{ public MainWindow() { InitializeComponent(); var mainVM = new MainViewModel(); ChangeToRed(mainVM.ChildViewModel); this.DataContext = mainVM; }

private void ChangeToBlue(object vm) { ParentGrid.Children.Clear(); var newPanel = new UCBlue(); newPanel.DataContext = vm; ParentGrid.Children.Add(newPanel); CommandHolder.Command = new Microsoft.TeamFoundation.MVVM.RelayCommand(ChangeToRed); }

private void ChangeToRed(object vm) { ParentGrid.Children.Clear(); var newPanel = new UCRed(); newPanel.DataContext = vm; ParentGrid.Children.Add(newPanel); CommandHolder.Command = new  Microsoft.TeamFoundation.MVVM.RelayCommand(ChangeToBlue); }}

ViewModel から受け取った新 ViewModel をView で直接扱っている

新しい View を作成

新しい ViewModel を受け取る

Page 50: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MVVM まとめ

• View と ViewModel が互いを扱わなくていいはずのMVVM で画面遷移を行おうとすると View で ViewModel を扱ってまう。→ そもそも MVVM には XAML環境に適応するための 最低限必要な機能しかなく、 画面遷移の存在を前提にしていないのでは? • MVVM の利点 ( XAML≒ 環境への適応≒データバインディ

ング ) を生かしつつ画面遷移の存在を前提とするパターンが必要

→ MVPVM

Page 51: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MVPVM(Model-View-Presenter-ViewModel) とは

• MVP と MVVM を合体させたもの• View,ViewModel の上に Presenter を置き、そこで、画

面遷移、ナビゲーションを管理する。

• ViewModel に書かれていたロジックを Presenter に移動することで ViewModel にはバインド対象となるプロパティのみが含まれるようにする。

• ViewModel にロジックが含まれなくなるので使いまわしが容易になる。 

Page 52: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

View

• ユーザーインターフェース• UI への出力と UI からの入力を担当する。• FrameworkElement の派生クラス• XAML の記述 + 対応する partial class

Model

• ビジネスロジック / プログラムの中核となる処理• View と ViewModel( と Presenter) 以外の部分

Page 53: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

• View の必要な情報を保持公開• View から受け取った入力やコマンドをを Presenter

に渡す (MVPVM)• View からの入力やコマンドを処理し Model を呼び出

す (MVVM)

ViewModel

Page 54: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

• View,ViewModel を保持、その接続を管理する• ViewModel から受け取った入力やコマンドを処理し

Model を呼び出す。• ViewModel のプロパティを通して View を変更する。• ナビゲーション・画面遷移の管理

(子 View,子 ViewModel の作成、接続 )

Presenter

Page 55: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

とりあえず組んでみた

• MVVM の時と同じようにテキストボックスから入力、加工してラベルに出力するアプリ

※ 画面遷移がないのに  MVPVM で組む必要性がないとか言わないように

Page 56: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

View

<Window x:Class="MVPVM1.MainWindow"( 略 ) Title="MainWindow" Height="300" Width="300"> <Grid> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition /> <RowDefinition /> </Grid.RowDefinitions> <Button Content="Button" Grid.Row="0" HorizontalAlignment="Center" VerticalAlignment="Center" Width="120" Command="{Binding MainCommand}"/> <TextBox Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center" Height="23" Width="120" Text="{Binding Input}" /> <Border Grid.Row="2" BorderThickness="1" BorderBrush="Black" HorizontalAlignment="Center" VerticalAlignment="Center" > <Label HorizontalAlignment="Center" VerticalAlignment="Center" Content="{Binding Output}" Width="120" /> </Border> </Grid></Window>

public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); } }

Page 57: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

ViewModel

class MainViewModel:ViewModelBase { public MainViewModel() { }

public override void Cleanup() { MainCommand = null; base.Cleanup(); }

private ICommand _mainCommand; public ICommand MainCommand { set{ 略 } get{ return _mainCommand;} } private string _input; public string Input { set{ 略 } get{ return _input;} } private string _output; public string Output { set { 略 }   get{ return _output;} } }

Command の中身の定義Model の関数の呼び出しコードがなくなっている

Page 58: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

PresenterBase class PresenterBase<TView,TViewModel> where TView:System.Windows.FrameworkElement where TViewModel:ViewModelBase { public PresenterBase(TView view, TViewModel viewmodel) { View = view; ViewModel = viewmodel; }

public TView View { set; get; } public TViewModel ViewModel { set; get; }

public virtual void Initialize() { View.DataContext = ViewModel; }

DataContext の設定View-ViewModel の接続

public virtual void CleanUp() { if (ViewModel != null) { ViewModel.Cleanup(); ViewModel = null; }

if (View != null) { View.DataContext = null; View = null; } } }

View-ViewModel 間の接続解除View,ViewModel の参照解除

引数のView,ViewModelメンバに代入

Page 59: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

Presenter

class MainPresenter:PresenterBase<MainWindow,MainViewModel> { MainModel model = new MainModel();

public MainPresenter(MainWindow view,MainViewModel viewmodel) :base(view,viewmodel){ }

public override void Initialize() { ViewModel.MainCommand = new Microsoft.TeamFoundation.MVVM.RelayCommand( ()=>{ViewModel.Output = model.Logic(ViewModel.Input); });

base.Initialize(); }

public override void CleanUp() { base.CleanUp(); } }

ViewModel のコマンドの中身を作成

コンストラクタが View とViewModel のインスタンスを受け取る

Model の関数の呼び出し。View の操作はデータバインドしたViewModel のプロパティを通して行う。

Page 60: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

App.xaml

<Application x:Class="MVPVM1.App"( 略 )Startup="Application_Startup“Exit="Application_Exit“

> <Application.Resources> </Application.Resources></Application>

public partial class App : Application { MainPresenter presenter ; private void Application_Startup(object sender, StartupEventArgs e) { var view =new MainWindow(); var viewmodel = new MainViewModel(); presenter = new MainPresenter(view, viewmodel); presenter.Initialize();

view.Show(); }

private void Application_Exit(object sender, ExitEventArgs e) { presenter.CleanUp(); } }

App.xaml.cs

StartupUri=“MainWindow.xaml” を削除Startup,Exit を追加

アプリ起動時に、 V,VM,P を作成、ウィンドウを表示

アプリ終了時に Presenter から全体をクリア

Page 61: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MVPVM での画面遷移

• View,ViewModel両方を扱うことが出来るPresenter に記述する

• MVVM では View,ViewModel の二か所に分割されていた画面遷移ロジックを Presenter 一つにまとめられるため自然な形で実装することができる。

Page 62: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

View<Window x:Class="MVPVMNavigation.MainWindow" ( 略 ) Title="MainWindow" Height="350" Width="525"> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="*" /> <ColumnDefinition Width="*" /> </Grid.ColumnDefinitions> <Grid Grid.Column="0" Name="ParentGrid" > </Grid> <Grid Grid.Column="1" > <Button Content=“Button” HorizontalAlignment=“Center” VerticalAlignment=“Center” Command="{Binding NavigationCommand}" Width="75" /> </Grid> </Grid></Window>

public partial class MainWindow: Window { public MainWindow() { InitializeComponent(); } }

View に画面遷移用のロジックが存在しない

ボタンがクリックされた時に呼び出されるコマンドのバインド

Page 63: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

ViewModel

public class MainViewModel:ViewModelBase{ ViewModelBase childVM; public ViewModelBase ChildViewModel { get { return childVM; } set { 略 } } ICommand navigationCommand; public ICommand NavigationCommand { get { return navigationCommand; } set { 略 } }

public MainViewModel(){}

public override void Cleanup() { if (ChildViewModel != null) { NavigationCommand = null; ChildViewModel.Cleanup(); ChildViewModel = null; } base.Cleanup(); }}

子 ViewModel子 Presenter からアクセスできるのでもしかしたら要らないかも

ボタンクリックで呼び出される画面遷移用コマンド

各プロパティのクリア

Page 64: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

子 Presenter(赤 ,青 )

public class RedPresenter : PresenterBase<UCRed, RedViewModel>{ public RedPresenter(UCRed view, RedViewModel viewmodel) : base(view, viewmodel) { }

public override void Initialize() { base.Initialize(); }

public override void CleanUp() { base.CleanUp(); } }

public class BluePresenter :PresenterBase<UCBlue,BlueViewModel>{ public BluePresenter(UCBlue view, BlueViewModel viewmodel) : base(view, viewmodel) { }

public override void Initialize() { base.Initialize(); }

public override void CleanUp() { base.CleanUp(); } }

Page 65: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MainPresenter(1)public class MainPresenter: PresenterBase<MainWindow,MainViewModel>{ public MainPresenter(MainWindow view,MainViewModel viewmodel):base(view,viewmodel){ }

public IPresenter ChildPresenter { get; set; }

public override void Initialize() { base.Initialize(); NavigateToRed(); }

public override void CleanUp() { ChildPresenter.CleanUp(); base.CleanUp(); }

子コントロール (赤・青 )用の Presenter

子 Presenter の解放

Page 66: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MainPresenter(2)

private void NavigateToRed(){ if (ChildPresenter != null) { ChildPresenter.CleanUp(); }

var v = new UCRed(); var vm = new RedViewModel(); var presenter = new RedPresenter(v, vm); View.ParentGrid.Children.Clear(); View.ParentGrid.Children.Add(v);

ViewModel.ChildViewModel = vm; ViewModel.NavigationCommand = new Microsoft.TeamFoundation.MVVM.RelayCommand( (p) => {NavigateToBlue();} );

ChildPresenter = presenter; ChildPresenter.Initialize(););

新しいView,ViewModel,Presenter の作成

次回ボタンクリック時に元の色に戻すためにコマンドをリセット

新しい View を Grid に追加

旧 Presenter のクリア

Page 67: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MainPresenter(3)

private void NavigateToBlue(){ if (ChildPresenter != null) { ChildPresenter.CleanUp(); }

var v = new UCBlue(); var vm = new BlueViewModel(); var presenter = new RedPresenter(v, vm); View.ParentGrid.Children.Clear(); View.ParentGrid.Children.Add(v);

ViewModel.ChildViewModel = vm; ViewModel.NavigationCommand = new Microsoft.TeamFoundation.MVVM.RelayCommand( (p) => {NavigateToRed();} );

ChildPresenter = presenter; ChildPresenter.Initialize(););

新しい View,ViewModelPresenter の作成

次回ボタンクリック時に元の色に戻すためにコマンドをリセット

新しい View を Grid に追加

旧 Presenter のクリア

Page 68: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

MVPVM まとめ

• ViewModel は Binding 対象になるプロパティのみを記述しコマンドの中身は Presenter に記述する

• Presenter は View,ViewModel どちらも扱えるのでその二つに分けて記述していた画面遷移ロジックをPresenter 1ヵ所に記述することができる。

 →MVVM では三ケ所すべてにロジックが含まれる可能性があったのが   MVPVM では基本的に P,M の二か所に減る

• 何らかの事情 (ActiveX,低スキル ,etc…) により View がビジネスロジックを持っている場合でも Presenter から直接呼べる

Page 69: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

まとめ

• View との入出力はバインディングで行い、インターフェースになる関数だけ残して実装は別クラスに移動することを繰り返せばとりあえず MVVM の形にはなる

• Presenter で View,ViewModel を管理し ViewModel のコマンドの中身をPresenter で作れば MVPVM になる。

• 一画面で完結するアプリなら MVVM でもいいが、ナビゲーション ( 画面遷移 ) を含む場合は MVPVM の方がオススメ

Page 70: T90 きっと怖くないmvvm & mvpvm

わんくま同盟 東京勉強会 #90

今回扱わなかったこと

• XAML環境でデータバインディングが必須な理由• コレクションコントロール• エラー処理• Command の CanExecute()• 非同期処理 (Model)• View に合わせた、 ViewModel,Presenterそれぞれツリー

の構造