More Related Content Similar to T90 きっと怖くないmvvm & mvpvm
Similar to T90 きっと怖くないmvvm & mvpvm (20) T90 きっと怖くないmvvm & mvpvm3. わんくま同盟 東京勉強会 #90
アジェンダ
• 目的
• MVVMとは
• ViewとViewModelの分離
• 細かいことは程々にして
実装してみた
• MVVMでの画面遷移
• MVVMまとめ
• MVPVMとは
• とりあえず実装してみた
• MVPVMでの画面遷移
• MVPVMまとめ
• まとめ
4. わんくま同盟 東京勉強会 #90
このセッションの目的
• 細かいことは置いておいて、
とりあえずMVVM,MVPVMっぽい形で
プログラムを書けるようにする。
• MVVMでのナビゲーション手法について理解する
• MVPVMでのナビゲーションについて理解する
• 疎結合、密結合、コードビハインドなどの用語を
できる限り使わずに説明する
6. わんくま同盟 東京勉強会 #90
View
• ユーザーインターフェース
• UIへの出力とUIからの入力を担当する。
• FrameworkElementの派生クラス
• XAMLの記述+対応する(partial )class
• Viewの必要な情報を保持公開
• Viewからの入力やコマンドを処理し
Modelを呼び出す
ViewModel Model
• ビジネスロジック
• プログラムの中核となる処理
• ViewとViewModel以外の部分
7. わんくま同盟 東京勉強会 #90
ViewとViewModelの分離(疎結合と密結合)
• MVVMの説明などでよく使われる用語、
疎結合と密結合
• ViewとViewModelを密結合にならないようにし、
疎結合に保つと良いらしい
• 「疎結合に保つ」「密結合になってしまっている」という記述
はよく見るが具体的にどのような状況を疎結合・密結合と
言うのか書かれていることはあまりない
• もしかしたら一般的な用語で説明する必要もないのかもし
れないが、
少なくとも自分にとってMVVM/MVPVMの文脈でしか聞か
ない言葉
9. わんくま同盟 東京勉強会 #90
具体的に何が許されて何が許されないのか
• 直接触れると密結合
– ViewはViewModelが特定の型であることに依存してはいけない
– ViewでViewModelのインスタンスを扱ってはいけない
– ViewModelはViewが特定の型であることに依存してはいけない
– ViewModelでViewのインスタンスを扱ってはいけない
• データバインドは疎結合
– ViewはViewModelがINotifyPropertyChangedを
実装していることに依存してよい。
– ViewはViewModelが特定の名前のプロパティを
持つことに依存してよい
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. わんくま同盟 東京勉強会 #90
細かいことは置いておいて
• MainWindowのイベントハンドラに全部の処理を書いた状態
から
少しずつ修正してMVVMの形にしてみようと思います。
• テキストボックス、ラベル、ボタンを配置し、ボタンを押すとテ
キストボックスに入力した文字列を加工してラベルに出力する
アプリを作る
※小さすぎてMVVMにする意味がないとか言わないでください
意味がなくてもとりあえず始めることが大事です。
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. わんくま同盟 東京勉強会 #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;
}
}
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. わんくま同盟 東京勉強会 #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;
}
}
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. わんくま同盟 東京勉強会 #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. わんくま同盟 東京勉強会 #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. わんくま同盟 東京勉強会 #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));
}
}
}
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. わんくま同盟 東京勉強会 #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. わんくま同盟 東京勉強会 #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イベントから呼
び出される
ビジネスロジック
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. わんくま同盟 東京勉強会 #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){略}
}
関数が増えてクラスが肥大化するので
あれば
外部とのインターフェースになる関数
だけ残して内部実装はさらに別のクラ
スに移した方がいいかも
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. わんくま同盟 東京勉強会 #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. わんくま同盟 東京勉強会 #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{ 略 } }
ロジックのバインド用のコマン
ド
コマンドの中身を作成。
37. わんくま同盟 東京勉強会 #90
MVVMでの画面遷移手法
• ViewModelの型に応じてコントロールを切り替える方法。
– データテンプレート
• 表示するデータの型に応じて表示方法を変える機能
• 指定した型のデータの表示方法を定義する。
• コントロールの切り替えロジックを呼び出し、
新しいコントロールに新しいViewModelを関連付ける手法。
– ViewModelからViewを呼び出す。
• 逆方向バインディングコマンド
• Behavior
• TriggerAndAction
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. わんくま同盟 東京勉強会 #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
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. わんくま同盟 東京勉強会 #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を変更
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. わんくま同盟 東京勉強会 #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. わんくま同盟 東京勉強会 #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. わんくま同盟 東京勉強会 #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. わんくま同盟 東京勉強会 #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. わんくま同盟 東京勉強会 #90
MVVMまとめ
• View と ViewModel が互いを扱わなくていいはずのMVVMで
画面遷移を行おうとするとViewでViewModelを扱ってまう。
→ そもそもMVVMにはXAML環境に適応するための
最低限必要な機能しかなく、
画面遷移の存在を前提にしていないのでは?
• MVVMの利点(≒XAML環境への適応≒データバインディング)
を
生かしつつ画面遷移の存在を前提とするパターンが必要
→ MVPVM
52. わんくま同盟 東京勉強会 #90
View
• ユーザーインターフェース
• UIへの出力とUIからの入力を担当する。
• FrameworkElementの派生クラス
• XAMLの記述+対応する partial class
Model
• ビジネスロジック/プログラムの中核となる処理
• ViewとViewModel(とPresenter)以外の部分
53. わんくま同盟 東京勉強会 #90
• Viewの必要な情報を保持公開
• Viewから受け取った入力やコマンドををPresenterに渡す
(MVPVM)
• Viewからの入力やコマンドを処理しModelを呼び出す
(MVVM)
ViewModel
54. わんくま同盟 東京勉強会 #90
• View,ViewModelを保持、その接続を管理する
• ViewModelから受け取った入力やコマンドを処理しModel
を呼び出す。
• ViewModelのプロパティを通してViewを変更する。
• ナビゲーション・画面遷移の管理
(子View,子ViewModelの作成、接続)
Presenter
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. わんくま同盟 東京勉強会 #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. わんくま同盟 東京勉強会 #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. わんくま同盟 東京勉強会 #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. わんくま同盟 東京勉強会 #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. わんくま同盟 東京勉強会 #90
MVPVMでの画面遷移
• View,ViewModel両方を扱うことが出来る
Presenterに記述する
• MVVMではView,ViewModelの二か所に分割されていた
画面遷移ロジックをPresenter一つにまとめられるため
自然な形で実装することができる。
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. わんくま同盟 東京勉強会 #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. わんくま同盟 東京勉強会 #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. わんくま同盟 東京勉強会 #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. わんくま同盟 東京勉強会 #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. わんくま同盟 東京勉強会 #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. わんくま同盟 東京勉強会 #90
MVPVMまとめ
• ViewModelはBinding対象になるプロパティのみを記述し
コマンドの中身はPresenterに記述する
• PresenterはView,ViewModelどちらも扱えるので
その二つに分けて記述していた画面遷移ロジックを
Presenter 1ヵ所に記述することができる。
→MVVMでは三ケ所すべてにロジックが含まれる可能性があったのが
MVPVMでは基本的にP,M の二か所に減る
• 何らかの事情(ActiveX,低スキル,etc…)によりViewがビジネスロジッ
クを持っている場合でもPresenterから直接呼べる
69. わんくま同盟 東京勉強会 #90
まとめ
• Viewとの入出力はバインディングで行い、
インターフェースになる関数だけ残して
実装は別クラスに移動することを繰り返せば
とりあえずMVVMの形にはなる
• PresenterでView,ViewModelを管理しViewModelのコマ
ンドの中身を
Presenterで作ればMVPVMになる。
• 一画面で完結するアプリならMVVMでもいいが、
ナビゲーション(画面遷移)を含む場合はMVPVMの方がオ
ススメ
70. わんくま同盟 東京勉強会 #90
今回扱わなかったこと
• XAML環境でデータバインディングが必須な理由
• コレクションコントロール
• エラー処理
• CommandのCanExecute()
• 非同期処理(Model)
• Viewに合わせた、ViewModel,Presenterそれぞれツリーの構
造