Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

T90 きっと怖くないmvvm & mvpvm

6,799 views

Published on

  • Be the first to comment

T90 きっと怖くないmvvm & mvpvm

  1. 1. わんくま同盟 東京勉強会 #90 きっと怖くないMVVM&MVPVM 暁 紫電 @akatukisiden
  2. 2. わんくま同盟 東京勉強会 #90 自己紹介 • HN:暁 紫電 • Twitter: @akatukisiden • 本名:伊藤 伸男 • フリーランス プログラマー • 使用言語 –C++ –C# –C++/CLI
  3. 3. わんくま同盟 東京勉強会 #90 アジェンダ • 目的 • MVVMとは • ViewとViewModelの分離 • 細かいことは程々にして 実装してみた • MVVMでの画面遷移 • MVVMまとめ • MVPVMとは • とりあえず実装してみた • MVPVMでの画面遷移 • MVPVMまとめ • まとめ
  4. 4. わんくま同盟 東京勉強会 #90 このセッションの目的 • 細かいことは置いておいて、 とりあえずMVVM,MVPVMっぽい形で プログラムを書けるようにする。 • MVVMでのナビゲーション手法について理解する • MVPVMでのナビゲーションについて理解する • 疎結合、密結合、コードビハインドなどの用語を できる限り使わずに説明する
  5. 5. わんくま同盟 東京勉強会 #90 MVVM(Model-View-ViewModel)とは • 最近流行りのUIアーキテクチャパターン • いくつかの理由によりXAML系フレームワーク (WPF,Silverlight,WinRT)では必須と言われている • UI(View)とビジネスロジック(Model)のあいだにViewModelを 置くことで二つを分離する。 • View・ViewModel間のやり取りはデータバインドを用いる
  6. 6. わんくま同盟 東京勉強会 #90 View • ユーザーインターフェース • UIへの出力とUIからの入力を担当する。 • FrameworkElementの派生クラス • XAMLの記述+対応する(partial )class • Viewの必要な情報を保持公開 • Viewからの入力やコマンドを処理し Modelを呼び出す ViewModel Model • ビジネスロジック • プログラムの中核となる処理 • ViewとViewModel以外の部分
  7. 7. わんくま同盟 東京勉強会 #90 ViewとViewModelの分離(疎結合と密結合) • MVVMの説明などでよく使われる用語、 疎結合と密結合 • ViewとViewModelを密結合にならないようにし、 疎結合に保つと良いらしい • 「疎結合に保つ」「密結合になってしまっている」という記述 はよく見るが具体的にどのような状況を疎結合・密結合と 言うのか書かれていることはあまりない • もしかしたら一般的な用語で説明する必要もないのかもし れないが、 少なくとも自分にとってMVVM/MVPVMの文脈でしか聞か ない言葉
  8. 8. わんくま同盟 東京勉強会 #90 さまざまな資料の記述を総合すると • View/ViewModelで互いのインスタンスや 型名を直接扱うと密結合 • データバインドを使えば疎結合
  9. 9. わんくま同盟 東京勉強会 #90 具体的に何が許されて何が許されないのか • 直接触れると密結合 – ViewはViewModelが特定の型であることに依存してはいけない – ViewでViewModelのインスタンスを扱ってはいけない – ViewModelはViewが特定の型であることに依存してはいけない – ViewModelでViewのインスタンスを扱ってはいけない • データバインドは疎結合 – ViewはViewModelがINotifyPropertyChangedを 実装していることに依存してよい。 – ViewはViewModelが特定の名前のプロパティを 持つことに依存してよい
  10. 10. わんくま同盟 東京勉強会 #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の型を 扱っている
  11. 11. わんくま同盟 東京勉強会 #90 細かいことは置いておいて • MainWindowのイベントハンドラに全部の処理を書いた状態 から 少しずつ修正してMVVMの形にしてみようと思います。 • テキストボックス、ラベル、ボタンを配置し、ボタンを押すとテ キストボックスに入力した文字列を加工してラベルに出力する アプリを作る ※小さすぎてMVVMにする意味がないとか言わないでください 意味がなくてもとりあえず始めることが大事です。
  12. 12. わんくま同盟 東京勉強会 #90 STEP1:全部イベントハンドラ等に記述
  13. 13. わんくま同盟 東京勉強会 #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>
  14. 14. わんくま同盟 東京勉強会 #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; } }
  15. 15. わんくま同盟 東京勉強会 #90 STEP2: MainWindow.xaml.csは イベントハンドラだけにしたいので 処理内容を別クラスに移す C++で云うところの pImpl
  16. 16. わんくま同盟 東京勉強会 #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); } } 処理を移すための別クラス 別クラスに移した処理の 呼び出し
  17. 17. わんくま同盟 東京勉強会 #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; } }
  18. 18. わんくま同盟 東京勉強会 #90 STEP3: 入出力(引数/戻り値)が多く必要になると 記述が大変なので データバインディングを使ってみる
  19. 19. わんくま同盟 東京勉強会 #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>
  20. 20. わんくま同盟 東京勉強会 #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(); } } バインド対象の設定 入出力はバインドしたので 引数・戻り値なしになっている
  21. 21. わんくま同盟 東京勉強会 #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"); } } }
  22. 22. わんくま同盟 東京勉強会 #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)); } } }
  23. 23. わんくま同盟 東京勉強会 #90 STEP4: 引数・戻り値なしなら直接呼出したいので、 イベントハンドラをImplに移動する。
  24. 24. わんくま同盟 東京勉強会 #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クラスの 関数を登録
  25. 25. わんくま同盟 東京勉強会 #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> イベントの登録部分を削除
  26. 26. わんくま同盟 東京勉強会 #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イベントから呼 び出される ビジネスロジック
  27. 27. わんくま同盟 東京勉強会 #90 STEP5: Implクラスにデータバインドと ビジネスロジック両方があるとじゃまなので ビジネスロジックをさらに別クラスへ移動する。
  28. 28. わんくま同盟 東京勉強会 #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 {略}} }
  29. 29. わんくま同盟 東京勉強会 #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){略} } 関数が増えてクラスが肥大化するので あれば 外部とのインターフェースになる関数 だけ残して内部実装はさらに別のクラ スに移した方がいいかも
  30. 30. わんくま同盟 東京勉強会 #90 STEP6:XAML環境ではロジックも コマンドとしてバインドできるので イベントハンドラからコマンドバインディングに変更する ※WinFormsでもFormクラス等にActionプロパティを作り On○○メソッド等でアクションを発火するようにすれば可能
  31. 31. わんくま同盟 東京勉強会 #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; } } 削除
  32. 32. わんくま同盟 東京勉強会 #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>
  33. 33. わんくま同盟 東京勉強会 #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{ 略 } } ロジックのバインド用のコマン ド コマンドの中身を作成。
  34. 34. わんくま同盟 東京勉強会 #90 (LastSTEP:)クラス名の変更 • MainWindowImpl → MainViewModel • BindingSourceBase → ViewModelBase • BussinesLogic → MainModel
  35. 35. わんくま同盟 東京勉強会 #90 とりあえずMVVM完成 ※但し画面遷移無し
  36. 36. わんくま同盟 東京勉強会 #90 画面遷移とは • 表示されているデータでは無く、 コントロール自体を変更する • 主にユーザーコントロールやパネルの切り替え
  37. 37. わんくま同盟 東京勉強会 #90 MVVMでの画面遷移手法 • ViewModelの型に応じてコントロールを切り替える方法。 – データテンプレート • 表示するデータの型に応じて表示方法を変える機能 • 指定した型のデータの表示方法を定義する。 • コントロールの切り替えロジックを呼び出し、 新しいコントロールに新しいViewModelを関連付ける手法。 – ViewModelからViewを呼び出す。 • 逆方向バインディングコマンド • Behavior • TriggerAndAction
  38. 38. わんくま同盟 東京勉強会 #90 とりあえずサンプル作る • ボタンを押すことで背景色の異なる(赤・青)二つのユーザーコ ントロールを切り替える。 • Modelは省略 • 切り替えロジック呼び出しのサンプルは 逆方向バインディングコマンドで実装
  39. 39. わんくま同盟 東京勉強会 #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
  40. 40. わんくま同盟 東京勉強会 #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
  41. 41. わんくま同盟 東京勉強会 #90 データテンプレート
  42. 42. わんくま同盟 東京勉強会 #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を表示しようとしている
  43. 43. わんくま同盟 東京勉強会 #90 ViewModel public 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を変更
  44. 44. わんくま同盟 東京勉強会 #90 逆方向バインディングコマンド
  45. 45. わんくま同盟 東京勉強会 #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を持つ カスタムコントロールを作る
  46. 46. わんくま同盟 東京勉強会 #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から呼ばれる逆方向コマンド逆方向バインディング カスタムコントロール
  47. 47. わんくま同盟 東京勉強会 #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を呼び出すための 逆方向バインディングをするコマンド
  48. 48. わんくま同盟 東京勉強会 #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に送る もう一度ボタンを押したら元に戻すために順方向コマンドをリセット
  49. 49. わんくま同盟 東京勉強会 #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を受け取る
  50. 50. わんくま同盟 東京勉強会 #90 MVVMまとめ • View と ViewModel が互いを扱わなくていいはずのMVVMで 画面遷移を行おうとするとViewでViewModelを扱ってまう。 → そもそもMVVMにはXAML環境に適応するための 最低限必要な機能しかなく、 画面遷移の存在を前提にしていないのでは? • MVVMの利点(≒XAML環境への適応≒データバインディング) を 生かしつつ画面遷移の存在を前提とするパターンが必要 → MVPVM
  51. 51. わんくま同盟 東京勉強会 #90 MVPVM(Model-View-Presenter-ViewModel)とは • MVPとMVVMを合体させたもの • View,ViewModelの上にPresenterを置き、そこで、画面遷 移、 ナビゲーションを管理する。 • ViewModelに書かれていたロジックをPresenterに移動する ことでViewModelにはバインド対象となるプロパティのみが 含まれるようにする。 • ViewModelにロジックが含まれなくなるので使いまわしが容 易になる。
  52. 52. わんくま同盟 東京勉強会 #90 View • ユーザーインターフェース • UIへの出力とUIからの入力を担当する。 • FrameworkElementの派生クラス • XAMLの記述+対応する partial class Model • ビジネスロジック/プログラムの中核となる処理 • ViewとViewModel(とPresenter)以外の部分
  53. 53. わんくま同盟 東京勉強会 #90 • Viewの必要な情報を保持公開 • Viewから受け取った入力やコマンドををPresenterに渡す (MVPVM) • Viewからの入力やコマンドを処理しModelを呼び出す (MVVM) ViewModel
  54. 54. わんくま同盟 東京勉強会 #90 • View,ViewModelを保持、その接続を管理する • ViewModelから受け取った入力やコマンドを処理しModel を呼び出す。 • ViewModelのプロパティを通してViewを変更する。 • ナビゲーション・画面遷移の管理 (子View,子ViewModelの作成、接続) Presenter
  55. 55. わんくま同盟 東京勉強会 #90 とりあえず組んでみた • MVVMの時と同じようにテキストボックスから入力、加工して ラベルに出力するアプリ ※画面遷移がないのに MVPVMで組む必要性がないとか言わないように
  56. 56. わんくま同盟 東京勉強会 #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(); } }
  57. 57. わんくま同盟 東京勉強会 #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の関数の呼び出しコードがなくなっている
  58. 58. わんくま同盟 東京勉強会 #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 メンバに代入
  59. 59. わんくま同盟 東京勉強会 #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のプロパティを通して行う。
  60. 60. わんくま同盟 東京勉強会 #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から全体をクリア
  61. 61. わんくま同盟 東京勉強会 #90 MVPVMでの画面遷移 • View,ViewModel両方を扱うことが出来る Presenterに記述する • MVVMではView,ViewModelの二か所に分割されていた 画面遷移ロジックをPresenter一つにまとめられるため 自然な形で実装することができる。
  62. 62. わんくま同盟 東京勉強会 #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に画面遷移用のロ ジックが存在しない ボタンがクリックされた時に呼び出さ れるコマンドのバインド
  63. 63. わんくま同盟 東京勉強会 #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からアクセスできるので もしかしたら要らないかも ボタンクリックで呼び出される 画面遷移用コマンド 各プロパティのクリア
  64. 64. わんくま同盟 東京勉強会 #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(); } }
  65. 65. わんくま同盟 東京勉強会 #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の解放
  66. 66. わんくま同盟 東京勉強会 #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のクリア
  67. 67. わんくま同盟 東京勉強会 #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のクリ ア
  68. 68. わんくま同盟 東京勉強会 #90 MVPVMまとめ • ViewModelはBinding対象になるプロパティのみを記述し コマンドの中身はPresenterに記述する • PresenterはView,ViewModelどちらも扱えるので その二つに分けて記述していた画面遷移ロジックを Presenter 1ヵ所に記述することができる。 →MVVMでは三ケ所すべてにロジックが含まれる可能性があったのが MVPVMでは基本的にP,M の二か所に減る • 何らかの事情(ActiveX,低スキル,etc…)によりViewがビジネスロジッ クを持っている場合でもPresenterから直接呼べる
  69. 69. わんくま同盟 東京勉強会 #90 まとめ • Viewとの入出力はバインディングで行い、 インターフェースになる関数だけ残して 実装は別クラスに移動することを繰り返せば とりあえずMVVMの形にはなる • PresenterでView,ViewModelを管理しViewModelのコマ ンドの中身を Presenterで作ればMVPVMになる。 • 一画面で完結するアプリならMVVMでもいいが、 ナビゲーション(画面遷移)を含む場合はMVPVMの方がオ ススメ
  70. 70. わんくま同盟 東京勉強会 #90 今回扱わなかったこと • XAML環境でデータバインディングが必須な理由 • コレクションコントロール • エラー処理 • CommandのCanExecute() • 非同期処理(Model) • Viewに合わせた、ViewModel,Presenterそれぞれツリーの構 造

×