わんくま同盟 東京勉強会 #119
5年間の試行錯誤で進化した
MVPVMパターン
暁 紫電
@akatukisiden
わんくま同盟 東京勉強会 #119
自己紹介
• 本名:伊藤 伸男
• フリーランス プログラマー
• スキル
– C#
– C++
– C++/CLI
– JavaScript
– Salesforce
わんくま同盟 東京勉強会 #119
はじめに
• 2014/06/07 東京勉強会 #90
「きっと怖くないMVVM&MVPVM」から5年
• 仕事でWPFの経験を積みながら
• 何かか作りたいアプリが出来た時の為に
MVPVMフレームワークだけでも作っておこうと思って
作り始めるも、なかなかうまくいかなかったり、他の勉強を
始めたりで中断
という事を繰り返してきました
• その試行錯誤の成果がまとまりつつあるので
紹介したいと思います。
わんくま同盟 東京勉強会 #119
MVPVMとは
• Model-View-Presenter-ViewModel
• MVVMパターンにMVPパターンを合体させた
もの
• MVVMでは,ViewとViewModelに分散してい
た画面遷移に関する処理をPresenter一つに
まとめることができるため処理が追いやすい
わんくま同盟 東京勉強会 #119
Model
• ビジネスロジック
• View,Presenter,ViewModel以外の部分
View
• ユーザーインターフェース
• UIへの出力とUIからの入力を担当する。
• FrameworkElementの派生クラス
• XAMLの記述+対応する partial class
わんくま同盟 東京勉強会 #119
ViewModel
• Viewの情報を保持(データバインド)
• Viewから受け取った入力やコマンドをPresenterに渡す
Presenter
• View,ViewModelを保持、その接続を管理する
• ViewModelから受け取った入力やコマンドを処理し
Modelを呼び出す。
• ViewModelのプロパティを通してViewを変更する。
• ナビゲーション・画面遷移の管理
わんくま同盟 東京勉強会 #119
参照関係
Model
ViewModel
View
Presenter
(DataContext)
わんくま同盟 東京勉強会 #119
MVPVMパターン
Model
ViewModel
View
Presenter
ナビゲーション
コマンド等
データバインド
呼び出し
コマンドの
内容はPに実装
イベント
わんくま同盟 東京勉強会 #119
画面遷移時にどちらのコントロールを使うか
• ContentControl
ひとつのコントロールを表示するだけ
シンプルで扱いやすい
• Frame,NavigationWindow (UWPではFrameのみ)
ナビゲーション前・後イベント
履歴(戻る、進む)
Pageインスタンスのキャッシュ等
画面遷移に関する機能を持つ
正直、履歴機能とかあまり使わないので
ContentControlで良い気がする
わんくま同盟 東京勉強会 #119
UWPアプリでは
Frameの使用がほぼ必須
画面遷移時のアニメーションサポート機能が付いているため
わんくま同盟 東京勉強会 #119
{x:Bind}マークアップ
• UWPで追加されたバインド方法
• {Binding}より高速
• PathのルートはDataContext ではなく、Page
• コンパイル時にバインド式が検証されコードが生成される
(obj/*.g.cs に生成)
• バインド式が間違っていたらコンパイルエラーが発生
わんくま同盟 東京勉強会 #119
{x:Bind}マークアップ
• Pageを表すクラスからバインディング ソース(ViewModel)
クラスを公開する必要がある
public sealed partial class Page1 : Page
{
public Page1()
{
this.InitializeComponent();
this.ViewModel = new Page1ViewModel();
}
public Page1ViewModel ViewModel { get; set; }
}
<Page x:Class=“Page1" ... >
<TextBlock Text="{x:Bind Path=ViewModel.Text, Mode=OneWay}" ... />
</Page>
わんくま同盟 東京勉強会 #119
obj/Page1.g.cs ({x:bind})
partial class Page1 :…
{
private static class XamlBindingSetters
{
public static void Set_Windows_UI_Xaml_Controls_TextBlock_Text
(TextBlock obj, String value, string targetNullValue)
{
if (value == null && targetNullValue != null)
{value = targetNullValue;}
obj.Text = value ?? String.Empty;
}
};
private class Page1_obj1_Bindings : …
{
public void Update()
{
this.Update_(this.dataRoot, NOT_PHASED);
this.initialized = true;
}
private void Update_(Views.Page1 obj, int phase)
{
if (obj != null)
{
if ((phase & (NOT_PHASED | (1 << 0))) != 0)
{this.Update_ViewModel(obj.ViewModel, phase);}
}
}
private void Update_ViewModel(Page1ViewModel obj, int phase)
{
if (obj != null)
{
if ((phase & (NOT_PHASED | (1 << 0))) != 0)
{this.Update_ViewModel_Text(obj.Text, phase);}
}
}
private void Update_ViewModel_Text(System.String obj, int phase)
{
if ((phase & ((1 << 0) | NOT_PHASED )) != 0)
{
if (!isobj2TextDisabled)
{
XamlBindingSetters.
Set_Windows_UI_Xaml_Controls_TextBlock_Text(
this.obj2, obj, null);
}
}
}
}
}
わんくま同盟 東京勉強会 #119
obj/Page1.g.cs ({Binding})
partial class Page1 :…
{
private class Page1_obj1_Bindings : …
{
public void Update()
{
this.Update_(this.dataRoot, NOT_PHASED);
this.initialized = true;
}
private void Update_(Views.Page1 obj, int phase)
{
if (obj != null)
{
}
}
}
}
わんくま同盟 東京勉強会 #119
{x:Bind}とイベント、関数
• コマンドだけでなく、イベント(と関数)のバインドが可能
• <Button Click="{x:Bind ViewModel.OnClick}" />
• RoutedEventも使用可
<Button Click="{x:Bind ViewModel.Clicked}" />
• Bind構文内で関数を呼び出して
Converterのように使うことができる。
<TextBlock Text=“{x:Bind ViewModel.ConvertFunc(ViewModel.Text) }” />
わんくま同盟 東京勉強会 #119
便利そうだけどこれって
厳密に言うと密結合では?
わんくま同盟 東京勉強会 #119
気にしないことにしよう!
わんくま同盟 東京勉強会 #119
最終的にこんな感じに
public sealed partial class Page1 : Page
{
public Page1ViewModel ViewModel
{
set { this.DataContext = value; }
get { return this.DataContext as Page1ViewModel; }
}
}
わんくま同盟 東京勉強会 #119
クラス図(UWP)
Frame
PresenterBase
Page
PresenterBase
Parent
PresenterBaseCore
ChildPage
PresenterBaseCore
PresenterBase
PresenterBaseCore
〇〇FramePresenter
〇〇Page
Presenter
Window
PresenterBase
〇〇Window
Presenter
通常のWindowとは
別物
View,ViewMode
非ジェネリック
View,ViewMode
ジェネリック
わんくま同盟 東京勉強会 #119
クラス図(WPF)
Frame
PresenterBase
NavigationWindow
PresenterBase
Page
PresenterBase
Parent
PresenterBaseCore
ChildPage
PresenterBaseCore
PresenterBase
PresenterBaseCore
Window
PresenterBase
〇〇FramePresenter
〇〇NavigationWindow
Presenter
〇〇Page
Presenter
〇〇Window
Presenter
Frame,NavigationWindowで
できる限り実装を共有
わんくま同盟 東京勉強会 #119
サンプル
• UWP
• Frame(NavigationWindow)で
ボタンを押すとPage1からPage2,Page2から
Page1へと遷移する
• https://github.com/akatukisiden/MVPVM
わんくま同盟 東京勉強会 #119
事前起動
• OS がバックグラウンドでプロセスを起動する
ことで、使用開始時の読み込み時間を短縮
パフォーマンスを向上させる
わんくま同盟 東京勉強会 #119
アプリケーションクラス
public sealed partial class App : Application
{
public WindowPresenter WindowPresenter { get; set; }
public App()
{
// 事前起動有効化
CoreApplication.EnablePrelaunch(true);
this.InitializeComponent();
this.EnteredBackground += App_EnteredBackground;
this.LeavingBackground += App_LeavingBackground;
this.Resuming += App_Resuming;
this.Suspending += App_Suspending;
this.UnhandledException += App_UnhandledException;
WindowPresenter = new WindowPresenter();
}
わんくま同盟 東京勉強会 #119
protected override void OnLaunched(LaunchActivatedEventArgs e)
{
Frame rootFrame = Window.Current.Content as Frame;
if (rootFrame == null)
{ WindowPresenter.Initialize(); }
if (e.PreviousExecutionState == ApplicationExecutionState.Terminated)
{ /* 以前中断したアプリケーションから状態を読み込みます*/ }
if (e.PrelaunchActivated == false)
{
if (rootFrame.Content == null)
{
WindowPresenter.Navigate(typeof(Page1));
}
WindowPresenter.Activate();
}
else
{ /* 事前起動 */ }
}
初期化処理
事前起動済みの時は本起動時
は実行しない
本起動時のみ実行
画面を表示する
わんくま同盟 東京勉強会 #119
事前起動のテスト方法
• デバッグ>その他のデバッグターゲット>ユニバーサルWindowsアプリ事前起動のデバッグ
• 事前起動後の本起動のデバッグはスタートメニューなどからの起動で可能
わんくま同盟 東京勉強会 #119
WindowPresenterBase.cs
public abstract class WindowPresenterBase
{
public WindowPresenterBase()
{ }
public Window Window { set; get; }
public virtual void Initialize()
{
if (Window == null)
{
Window = Window.Current;
Window.Activated += Window_Activated;
Window.Closed += Window_Closed;
Window.SizeChanged += Window_SizeChanged;
Window.VisibilityChanged += Window_VisibilityChanged;
}
}
public virtual void Cleanup()
{
Window.Activated -= Window_Activated;
Window.Closed -= Window_Closed;
Window.SizeChanged -= Window_SizeChanged;
Window.VisibilityChanged -= Window_VisibilityChanged;
Window = null;
}
わんくま同盟 東京勉強会 #119
WindowPresenter.cs
public class WindowPresenter :WindowPresenterBase
{
public RootFramePresenter RootFramePresenter { get; set; }
public WindowPresenter()
{
RootFramePresenter = new RootFramePresenter();
}
public override void Initialize()
{
base.Initialize();
// ナビゲーション コンテキストとして動作するフレームを作成
var root = new Frame();
this.Window.Content = root;
this.RootFramePresenter.View = root;
this.RootFramePresenter.Initialize();
}
public bool Navigate(Type key) { return RootFramePresenter.Navigate(key);}
public override void Cleanup() { base.Cleanup(); }
わんくま同盟 東京勉強会 #119
Frame.Navigate(Type, object,NavigationTransitionInfo)
• 遷移先ページを指すType,パラメータ、ナビゲーション時のア
ニメの種類を引数に取る
• WPFではアニメの種類は無しでTypeの代わりにUriを用いる
– Pageのインスタンスを使うオーバーロードもあるが、ナビ
ゲーションのキャンセル時等は、
作成したインスタンスが使われないままになるので個人的
には非推奨
• Navigating(キャンセル可),Navigatedイベントが呼
び出される
わんくま同盟 東京勉強会 #119
Frame.Navigate(Type, object,NavigationTransitionInfo)
• Navigatingイベントでキャンセルされた場合
NavigationStoppedイベントが発火
– WPFではキャンセル時には発生しないがStopLoading()
を呼び出すことで手動で発生させることができる
– ナビゲーション中に別のナビゲーションを開始しても
発生するらしいが詳しい条件は不明
• Navigatedイベント中に例外がが発生すると
NavigationFailedイベントが発火
– WPFだとIOExceptionじゃないといけないので注意
わんくま同盟 東京勉強会 #119
RootFramePresenter.cs
public class RootFramePresenter: FramePresenterBase<Frame,EmptyViewModel>
{
public RootFramePresenter()
{
AddChild(typeof(Page1), new Page1Presenter());
AddChild(typeof(Page2), new Page2Presenter());
}
public override void Initialize()
{
base.Initialize();
this.IsNavigationStackEnabled = true;
}
public override void Cleanup()
{
base.Cleanup();
}
}
最初に、Presenterのインスタンスを作成
Viewの型とセットで登録
わんくま同盟 東京勉強会 #119
Presenterの作成タイミング
• Presenter,とViewModelは最初にまとめて作成して
おく。
• Viewのインスタンスが作られるタイミングで
Presenterを作ろうとしたこともあるのだが、
気が付いたらFrameのもつ履歴やインスタンス
キャッシュの機能をPresenterに再実装しようとして
しまったり、
Viewの型に応じたPresenterを作成するしくみが必
要になったりしたため、
これなら最初に作成しておいた方が楽だと判断した
わんくま同盟 東京勉強会 #119
FramePresenterBase
public abstract class FramePresenterBase<TView, TViewModel>
: ParentPresenterBaseCore
where TView : Windows.UI.Xaml.Controls.Frame, new()
where TViewModel : ViewModelBase, new()
{
public FramePresenterBase() { ViewModel = new TViewModel(); }
public bool IsNavigationStackEnabled
{
get { return View.IsNavigationStackEnabled; }
set { View.IsNavigationStackEnabled = value; }
}
public TView View { get { return this.ViewBase as TView; } set { this.ViewBase = value; } }
public TViewModel ViewModel
{ get { return this.ViewModelBase as TViewModel; } set { this.ViewModelBase = value; } }
ナビゲーション履歴を使うかどうか
Viewの同名プロパティにアクセス
非ジェネリック基底クラス(Core)の
プロパティをキャスト
わんくま同盟 東京勉強会 #119
FramePresenterBase
public override void Initialize()
{
base.Initialize();
IsNavigationStackEnabled = false;
View.Navigating += View_Navigating;
View.Navigated += View_Navigated;
View.NavigationFailed += View_NavigationFailed;
View.NavigationStopped += View_NavigationStopped;
}
public override void Cleanup()
{
View.Navigating -= View_Navigating;
View.Navigated -= View_Navigated;
View.NavigationFailed -= View_NavigationFailed;
View.NavigationStopped -= View_NavigationStopped;
base.Cleanup();
}
わんくま同盟 東京勉強会 #119
FramePresenterBase
public override bool Navigate
(Type key, object extraData = null, NavigationTransitionInfo infoOrverride =null)
{ return View.Navigate(key, extraData, infoOrverride); }
public override void GoForward()
{
View.GoForward();
}
public override void GoBack(NavigationTransitionInfo infoOrverride = null)
{ View.GoBack(infoOrverride); }
private void View_Navigated(object sender, NavigationEventArgs e)
{ View_NavigatedCore(sender, e); }
}
画面遷移関係の関数
Frameの同名関数を呼び出し
通常の遷移,進む,戻る
遷移時のアニメーションの種類
ParentPresenterBaseCore
の関数を呼び出し
わんくま同盟 東京勉強会 #119
ParentPresenterBaseCore.cs
public abstract class ParentPresenterBaseCore : PresenterBaseCore
{
protected
Dictionary<Type, ChildPagePresenterBaseCore> Children { get; private set; };
public void AddChild(Type key, ChildPagePresenterBaseCore child)
{
Children.Add(key, child);
}
public void RemoveChild(Type key)
{
Children.Remove(key);
}
本体はDictionary
子Presenterの登録
子Presenterの解除
わんくま同盟 東京勉強会 #119
ParentPresenterBaseCore.cs
public ParentPresenterBaseCore()
{ Children = new Dictionary<Type, ChildPagePresenterBaseCore>(); }
public ChildPagePresenterBaseCore CurrentChild { get; internal set; };
public override void Initialize()
{ base.Initialize(); }
public override void Cleanup()
{
if (CurrentChild != null)
{
CurrentChild.Cleanup();
CurrentChild = null;
}
base.Cleanup();
}
表示中の子(Page)Presenter
わんくま同盟 東京勉強会 #119
ParentPresenterBaseCore.cs
protected void View_NavigationStopped(object sender, NavigationEventArgs e)
{
CurrentChild.InvokeOnNavigationStopped(e);
}
protected void View_Navigating(object sender, NavigatingCancelEventArgs e)
{
if (CurrentChild != null)
{ CurrentChild.InvokeOnNavigatingFrom(e); }
}
protected void View_NavigationFailed(object sender, NavigationFailedEventArgs e)
{ CurrentChild.InvokeOnNavigationFailed(e); }
Internal 関数を通してイベントに対応するPagePresenterの関数を呼び出し
わんくま同盟 東京勉強会 #119
ParentPresenterBaseCore.cs
protected internal void View_NavigatedCore(object sender, NavigationEventArgs e)
{
if (e != null)
{
if (CurrentChild != null)
{
CurrentChild.InvokeOnNavigatedFrom(e);
CurrentChild.Cleanup();
CurrentChild = null;
}
Type key = e.Content.GetType();
var childPresenter = Children[key];
var element = e.Content as FrameworkElement;
childPresenter.Parent = this;
childPresenter.ViewBase = element;
CurrentChild = childPresenter;
childPresenter.Initialize();
childPresenter.InvokeOnNavigatedTo(e);
}
}
表示中のページの終了処理
遷移先Pageに対応したPagePresenterを取得
遷移先Presenterの初期化処理
internal関数を介して
PagePresenter.OnNavigatedFrom(e)
の呼び出し
PagePresenter.OnNavigatedTo(e)
の呼び出し
わんくま同盟 東京勉強会 #119
• UWPのPageには3つのオーバーライド用関数が存在
– OnNavigatedTo (Pageへ遷移してきたときに実行)
– OnNavigatingFrom (Pageから遷移するときに実行,キャンセル可)
– OnNavigatedFrom (Pageから遷移したときに実行)
• MVPVMではできるだけViewにコードを書きたくないため同
名の関数をPagePresenterに用意して同じような感覚で使え
るようにしている
わんくま同盟 東京勉強会 #119
Page1Presenter
public class Page1Presenter : PagePresenterBase<Views.Page1, Page1ViewModel>
{
public override void Initialize()
{
base.Initialize();
ViewModel.ClickEvent += Click;
this.ViewModel.Text = "Page1";
}
public void Click(object sender, RoutedEventArgs args)
{ this.Parent.Navigate(typeof(Page2), null); }
public override void Cleanup()
{
ViewModel.ClickEvent -= Click;
base.Cleanup();
}
x:bindで呼び出すイベント
わんくま同盟 東京勉強会 #119
PagePresenterBase
public abstract class PagePresenterBase<TView, TViewModel> : ChildPagePresent
erBaseCore
where TView : Page, new()
where TViewModel : ViewModelBase, new()
{
public PagePresenterBase()
{ ViewModel = new TViewModel(); }
public TView View
{ get { return this.ViewBase as TView; } set { this.ViewBase = value; } }
public TViewModel ViewModel
{
get { return this.ViewModelBase as TViewModel; }
set { this.ViewModelBase = value; }
}
わんくま同盟 東京勉強会 #119
PagePresenterBase
public override void Initialize()
{
base.Initialize();
View.Loaded += OnPageLoaded;
}
protected virtual void OnPageLoaded(object sender, RoutedEventArgs e)
{}
public override void Cleanup()
{
View.Loaded -= OnPageLoaded;
base.Cleanup();
}
}
わんくま同盟 東京勉強会 #119
ChildPresenterBaseCore
public abstract class ChildPagePresenterBaseCore : PresenterBaseCore
{
public ParentPresenterBaseCore Parent { get; set; }
public override void Initialize()
{
base.Initialize();
}
public override void Cleanup()
{
Parent = null;
base.Cleanup();
}
親FramePresenter
参照外し
わんくま同盟 東京勉強会 #119
ChildPresenterBaseCore
internal void InvokeOnNavigatedTo(NavigationEventArgs e)
{ OnNavigatedTo(e);}
internal void InvokeOnNavigatingFrom(NavigatingCancelEventArgs e)
{ OnNavigatingFrom(e); }
internal void InvokeOnNavigatedFrom(NavigationEventArgs e)
{ OnNavigatedFrom(e); }
internal void InvokeOnNavigationStopped(NavigationEventArgs e)
{OnNavigationStopped (e); }
internal void InvokeOnNavigationFailed(NavigationFailedEventArgs e)
{ OnNavigationFailed(e); }
わんくま同盟 東京勉強会 #119
ChildPresenterBaseCore
protected virtual void OnNavigatedTo(NavigationEventArgs e)
{ }
protected virtual void OnNavigatingFrom(NavigatingCancelEventArgs e)
{}
protected virtual void OnNavigatedFrom(NavigationEventArgs e)
{}
protected virtual void OnNavigationFailed(NavigationFailedEventArgs e)
{}
protected virtual void OnNavigationStopped(NavigationEventArgs e)
{}
}
わんくま同盟 東京勉強会 #119
PresenterBaseCore
public abstract class PresenterBaseCore
{
protected TaskScheduler UIThreadTaskScheduler { get; private set; }
public bool IsEnable { get; private set; }
internal FrameworkElement ViewBase { get; set; }
internal ViewModelBase ViewModelBase { get; set; }
public PresenterBaseCore()
{
this.IsEnable = false;
}
Presenterが有効かどうか
Initialize()でtrue
CleanUp()でfalse
View,ViewModel
非ジェネリック基底クラス用
わんくま同盟 東京勉強会 #119
PresenterBaseCore
public virtual void Initialize()
{
this.UIThreadTaskScheduler
= TaskScheduler.FromCurrentSynchronizationContext();
ViewBase.DataContext = ViewModelBase;
IsEnable = true;
}
public virtual void Cleanup()
{
if (ViewBase != null)
{
ViewBase.DataContext = null;
ViewBase = null;
}
IsEnable = false;
}
ViewとViewModelを接続
接続解除
Viewの参照外し
破壊ではなく解放
管理をViewフレームワークに
返すイメージ
わんくま同盟 東京勉強会 #119
まとめ
• UWPでは画面遷移アニメーションがあるので
ContentControlではなくてFrameを使う
• Presenter,ViewModelは最初にまとめて作る
• View(Page)の作成はできる限りフレームワー
クに任せ、
使用後は参照を解除し
管理権をフレームワークに返すことでViewの
機能を最大限生かす

T119_5年間の試行錯誤で進化したMVPVMパターン

  • 1.
  • 2.
    わんくま同盟 東京勉強会 #119 自己紹介 •本名:伊藤 伸男 • フリーランス プログラマー • スキル – C# – C++ – C++/CLI – JavaScript – Salesforce
  • 3.
    わんくま同盟 東京勉強会 #119 はじめに •2014/06/07 東京勉強会 #90 「きっと怖くないMVVM&MVPVM」から5年 • 仕事でWPFの経験を積みながら • 何かか作りたいアプリが出来た時の為に MVPVMフレームワークだけでも作っておこうと思って 作り始めるも、なかなかうまくいかなかったり、他の勉強を 始めたりで中断 という事を繰り返してきました • その試行錯誤の成果がまとまりつつあるので 紹介したいと思います。
  • 4.
    わんくま同盟 東京勉強会 #119 MVPVMとは •Model-View-Presenter-ViewModel • MVVMパターンにMVPパターンを合体させた もの • MVVMでは,ViewとViewModelに分散してい た画面遷移に関する処理をPresenter一つに まとめることができるため処理が追いやすい
  • 5.
    わんくま同盟 東京勉強会 #119 Model •ビジネスロジック • View,Presenter,ViewModel以外の部分 View • ユーザーインターフェース • UIへの出力とUIからの入力を担当する。 • FrameworkElementの派生クラス • XAMLの記述+対応する partial class
  • 6.
    わんくま同盟 東京勉強会 #119 ViewModel •Viewの情報を保持(データバインド) • Viewから受け取った入力やコマンドをPresenterに渡す Presenter • View,ViewModelを保持、その接続を管理する • ViewModelから受け取った入力やコマンドを処理し Modelを呼び出す。 • ViewModelのプロパティを通してViewを変更する。 • ナビゲーション・画面遷移の管理
  • 7.
  • 8.
  • 9.
    わんくま同盟 東京勉強会 #119 画面遷移時にどちらのコントロールを使うか •ContentControl ひとつのコントロールを表示するだけ シンプルで扱いやすい • Frame,NavigationWindow (UWPではFrameのみ) ナビゲーション前・後イベント 履歴(戻る、進む) Pageインスタンスのキャッシュ等 画面遷移に関する機能を持つ 正直、履歴機能とかあまり使わないので ContentControlで良い気がする
  • 10.
  • 11.
    わんくま同盟 東京勉強会 #119 {x:Bind}マークアップ •UWPで追加されたバインド方法 • {Binding}より高速 • PathのルートはDataContext ではなく、Page • コンパイル時にバインド式が検証されコードが生成される (obj/*.g.cs に生成) • バインド式が間違っていたらコンパイルエラーが発生
  • 12.
    わんくま同盟 東京勉強会 #119 {x:Bind}マークアップ •Pageを表すクラスからバインディング ソース(ViewModel) クラスを公開する必要がある public sealed partial class Page1 : Page { public Page1() { this.InitializeComponent(); this.ViewModel = new Page1ViewModel(); } public Page1ViewModel ViewModel { get; set; } } <Page x:Class=“Page1" ... > <TextBlock Text="{x:Bind Path=ViewModel.Text, Mode=OneWay}" ... /> </Page>
  • 13.
    わんくま同盟 東京勉強会 #119 obj/Page1.g.cs({x:bind}) partial class Page1 :… { private static class XamlBindingSetters { public static void Set_Windows_UI_Xaml_Controls_TextBlock_Text (TextBlock obj, String value, string targetNullValue) { if (value == null && targetNullValue != null) {value = targetNullValue;} obj.Text = value ?? String.Empty; } }; private class Page1_obj1_Bindings : … { public void Update() { this.Update_(this.dataRoot, NOT_PHASED); this.initialized = true; } private void Update_(Views.Page1 obj, int phase) { if (obj != null) { if ((phase & (NOT_PHASED | (1 << 0))) != 0) {this.Update_ViewModel(obj.ViewModel, phase);} } } private void Update_ViewModel(Page1ViewModel obj, int phase) { if (obj != null) { if ((phase & (NOT_PHASED | (1 << 0))) != 0) {this.Update_ViewModel_Text(obj.Text, phase);} } } private void Update_ViewModel_Text(System.String obj, int phase) { if ((phase & ((1 << 0) | NOT_PHASED )) != 0) { if (!isobj2TextDisabled) { XamlBindingSetters. Set_Windows_UI_Xaml_Controls_TextBlock_Text( this.obj2, obj, null); } } } } }
  • 14.
    わんくま同盟 東京勉強会 #119 obj/Page1.g.cs({Binding}) partial class Page1 :… { private class Page1_obj1_Bindings : … { public void Update() { this.Update_(this.dataRoot, NOT_PHASED); this.initialized = true; } private void Update_(Views.Page1 obj, int phase) { if (obj != null) { } } } }
  • 15.
    わんくま同盟 東京勉強会 #119 {x:Bind}とイベント、関数 •コマンドだけでなく、イベント(と関数)のバインドが可能 • <Button Click="{x:Bind ViewModel.OnClick}" /> • RoutedEventも使用可 <Button Click="{x:Bind ViewModel.Clicked}" /> • Bind構文内で関数を呼び出して Converterのように使うことができる。 <TextBlock Text=“{x:Bind ViewModel.ConvertFunc(ViewModel.Text) }” />
  • 16.
  • 17.
  • 18.
    わんくま同盟 東京勉強会 #119 最終的にこんな感じに publicsealed partial class Page1 : Page { public Page1ViewModel ViewModel { set { this.DataContext = value; } get { return this.DataContext as Page1ViewModel; } } }
  • 19.
  • 20.
  • 21.
    わんくま同盟 東京勉強会 #119 サンプル •UWP • Frame(NavigationWindow)で ボタンを押すとPage1からPage2,Page2から Page1へと遷移する • https://github.com/akatukisiden/MVPVM
  • 22.
    わんくま同盟 東京勉強会 #119 事前起動 •OS がバックグラウンドでプロセスを起動する ことで、使用開始時の読み込み時間を短縮 パフォーマンスを向上させる
  • 23.
    わんくま同盟 東京勉強会 #119 アプリケーションクラス publicsealed partial class App : Application { public WindowPresenter WindowPresenter { get; set; } public App() { // 事前起動有効化 CoreApplication.EnablePrelaunch(true); this.InitializeComponent(); this.EnteredBackground += App_EnteredBackground; this.LeavingBackground += App_LeavingBackground; this.Resuming += App_Resuming; this.Suspending += App_Suspending; this.UnhandledException += App_UnhandledException; WindowPresenter = new WindowPresenter(); }
  • 24.
    わんくま同盟 東京勉強会 #119 protectedoverride void OnLaunched(LaunchActivatedEventArgs e) { Frame rootFrame = Window.Current.Content as Frame; if (rootFrame == null) { WindowPresenter.Initialize(); } if (e.PreviousExecutionState == ApplicationExecutionState.Terminated) { /* 以前中断したアプリケーションから状態を読み込みます*/ } if (e.PrelaunchActivated == false) { if (rootFrame.Content == null) { WindowPresenter.Navigate(typeof(Page1)); } WindowPresenter.Activate(); } else { /* 事前起動 */ } } 初期化処理 事前起動済みの時は本起動時 は実行しない 本起動時のみ実行 画面を表示する
  • 25.
    わんくま同盟 東京勉強会 #119 事前起動のテスト方法 •デバッグ>その他のデバッグターゲット>ユニバーサルWindowsアプリ事前起動のデバッグ • 事前起動後の本起動のデバッグはスタートメニューなどからの起動で可能
  • 26.
    わんくま同盟 東京勉強会 #119 WindowPresenterBase.cs publicabstract class WindowPresenterBase { public WindowPresenterBase() { } public Window Window { set; get; } public virtual void Initialize() { if (Window == null) { Window = Window.Current; Window.Activated += Window_Activated; Window.Closed += Window_Closed; Window.SizeChanged += Window_SizeChanged; Window.VisibilityChanged += Window_VisibilityChanged; } } public virtual void Cleanup() { Window.Activated -= Window_Activated; Window.Closed -= Window_Closed; Window.SizeChanged -= Window_SizeChanged; Window.VisibilityChanged -= Window_VisibilityChanged; Window = null; }
  • 27.
    わんくま同盟 東京勉強会 #119 WindowPresenter.cs publicclass WindowPresenter :WindowPresenterBase { public RootFramePresenter RootFramePresenter { get; set; } public WindowPresenter() { RootFramePresenter = new RootFramePresenter(); } public override void Initialize() { base.Initialize(); // ナビゲーション コンテキストとして動作するフレームを作成 var root = new Frame(); this.Window.Content = root; this.RootFramePresenter.View = root; this.RootFramePresenter.Initialize(); } public bool Navigate(Type key) { return RootFramePresenter.Navigate(key);} public override void Cleanup() { base.Cleanup(); }
  • 28.
    わんくま同盟 東京勉強会 #119 Frame.Navigate(Type,object,NavigationTransitionInfo) • 遷移先ページを指すType,パラメータ、ナビゲーション時のア ニメの種類を引数に取る • WPFではアニメの種類は無しでTypeの代わりにUriを用いる – Pageのインスタンスを使うオーバーロードもあるが、ナビ ゲーションのキャンセル時等は、 作成したインスタンスが使われないままになるので個人的 には非推奨 • Navigating(キャンセル可),Navigatedイベントが呼 び出される
  • 29.
    わんくま同盟 東京勉強会 #119 Frame.Navigate(Type,object,NavigationTransitionInfo) • Navigatingイベントでキャンセルされた場合 NavigationStoppedイベントが発火 – WPFではキャンセル時には発生しないがStopLoading() を呼び出すことで手動で発生させることができる – ナビゲーション中に別のナビゲーションを開始しても 発生するらしいが詳しい条件は不明 • Navigatedイベント中に例外がが発生すると NavigationFailedイベントが発火 – WPFだとIOExceptionじゃないといけないので注意
  • 30.
    わんくま同盟 東京勉強会 #119 RootFramePresenter.cs publicclass RootFramePresenter: FramePresenterBase<Frame,EmptyViewModel> { public RootFramePresenter() { AddChild(typeof(Page1), new Page1Presenter()); AddChild(typeof(Page2), new Page2Presenter()); } public override void Initialize() { base.Initialize(); this.IsNavigationStackEnabled = true; } public override void Cleanup() { base.Cleanup(); } } 最初に、Presenterのインスタンスを作成 Viewの型とセットで登録
  • 31.
    わんくま同盟 東京勉強会 #119 Presenterの作成タイミング •Presenter,とViewModelは最初にまとめて作成して おく。 • Viewのインスタンスが作られるタイミングで Presenterを作ろうとしたこともあるのだが、 気が付いたらFrameのもつ履歴やインスタンス キャッシュの機能をPresenterに再実装しようとして しまったり、 Viewの型に応じたPresenterを作成するしくみが必 要になったりしたため、 これなら最初に作成しておいた方が楽だと判断した
  • 32.
    わんくま同盟 東京勉強会 #119 FramePresenterBase publicabstract class FramePresenterBase<TView, TViewModel> : ParentPresenterBaseCore where TView : Windows.UI.Xaml.Controls.Frame, new() where TViewModel : ViewModelBase, new() { public FramePresenterBase() { ViewModel = new TViewModel(); } public bool IsNavigationStackEnabled { get { return View.IsNavigationStackEnabled; } set { View.IsNavigationStackEnabled = value; } } public TView View { get { return this.ViewBase as TView; } set { this.ViewBase = value; } } public TViewModel ViewModel { get { return this.ViewModelBase as TViewModel; } set { this.ViewModelBase = value; } } ナビゲーション履歴を使うかどうか Viewの同名プロパティにアクセス 非ジェネリック基底クラス(Core)の プロパティをキャスト
  • 33.
    わんくま同盟 東京勉強会 #119 FramePresenterBase publicoverride void Initialize() { base.Initialize(); IsNavigationStackEnabled = false; View.Navigating += View_Navigating; View.Navigated += View_Navigated; View.NavigationFailed += View_NavigationFailed; View.NavigationStopped += View_NavigationStopped; } public override void Cleanup() { View.Navigating -= View_Navigating; View.Navigated -= View_Navigated; View.NavigationFailed -= View_NavigationFailed; View.NavigationStopped -= View_NavigationStopped; base.Cleanup(); }
  • 34.
    わんくま同盟 東京勉強会 #119 FramePresenterBase publicoverride bool Navigate (Type key, object extraData = null, NavigationTransitionInfo infoOrverride =null) { return View.Navigate(key, extraData, infoOrverride); } public override void GoForward() { View.GoForward(); } public override void GoBack(NavigationTransitionInfo infoOrverride = null) { View.GoBack(infoOrverride); } private void View_Navigated(object sender, NavigationEventArgs e) { View_NavigatedCore(sender, e); } } 画面遷移関係の関数 Frameの同名関数を呼び出し 通常の遷移,進む,戻る 遷移時のアニメーションの種類 ParentPresenterBaseCore の関数を呼び出し
  • 35.
    わんくま同盟 東京勉強会 #119 ParentPresenterBaseCore.cs publicabstract class ParentPresenterBaseCore : PresenterBaseCore { protected Dictionary<Type, ChildPagePresenterBaseCore> Children { get; private set; }; public void AddChild(Type key, ChildPagePresenterBaseCore child) { Children.Add(key, child); } public void RemoveChild(Type key) { Children.Remove(key); } 本体はDictionary 子Presenterの登録 子Presenterの解除
  • 36.
    わんくま同盟 東京勉強会 #119 ParentPresenterBaseCore.cs publicParentPresenterBaseCore() { Children = new Dictionary<Type, ChildPagePresenterBaseCore>(); } public ChildPagePresenterBaseCore CurrentChild { get; internal set; }; public override void Initialize() { base.Initialize(); } public override void Cleanup() { if (CurrentChild != null) { CurrentChild.Cleanup(); CurrentChild = null; } base.Cleanup(); } 表示中の子(Page)Presenter
  • 37.
    わんくま同盟 東京勉強会 #119 ParentPresenterBaseCore.cs protectedvoid View_NavigationStopped(object sender, NavigationEventArgs e) { CurrentChild.InvokeOnNavigationStopped(e); } protected void View_Navigating(object sender, NavigatingCancelEventArgs e) { if (CurrentChild != null) { CurrentChild.InvokeOnNavigatingFrom(e); } } protected void View_NavigationFailed(object sender, NavigationFailedEventArgs e) { CurrentChild.InvokeOnNavigationFailed(e); } Internal 関数を通してイベントに対応するPagePresenterの関数を呼び出し
  • 38.
    わんくま同盟 東京勉強会 #119 ParentPresenterBaseCore.cs protectedinternal void View_NavigatedCore(object sender, NavigationEventArgs e) { if (e != null) { if (CurrentChild != null) { CurrentChild.InvokeOnNavigatedFrom(e); CurrentChild.Cleanup(); CurrentChild = null; } Type key = e.Content.GetType(); var childPresenter = Children[key]; var element = e.Content as FrameworkElement; childPresenter.Parent = this; childPresenter.ViewBase = element; CurrentChild = childPresenter; childPresenter.Initialize(); childPresenter.InvokeOnNavigatedTo(e); } } 表示中のページの終了処理 遷移先Pageに対応したPagePresenterを取得 遷移先Presenterの初期化処理 internal関数を介して PagePresenter.OnNavigatedFrom(e) の呼び出し PagePresenter.OnNavigatedTo(e) の呼び出し
  • 39.
    わんくま同盟 東京勉強会 #119 •UWPのPageには3つのオーバーライド用関数が存在 – OnNavigatedTo (Pageへ遷移してきたときに実行) – OnNavigatingFrom (Pageから遷移するときに実行,キャンセル可) – OnNavigatedFrom (Pageから遷移したときに実行) • MVPVMではできるだけViewにコードを書きたくないため同 名の関数をPagePresenterに用意して同じような感覚で使え るようにしている
  • 40.
    わんくま同盟 東京勉強会 #119 Page1Presenter publicclass Page1Presenter : PagePresenterBase<Views.Page1, Page1ViewModel> { public override void Initialize() { base.Initialize(); ViewModel.ClickEvent += Click; this.ViewModel.Text = "Page1"; } public void Click(object sender, RoutedEventArgs args) { this.Parent.Navigate(typeof(Page2), null); } public override void Cleanup() { ViewModel.ClickEvent -= Click; base.Cleanup(); } x:bindで呼び出すイベント
  • 41.
    わんくま同盟 東京勉強会 #119 PagePresenterBase publicabstract class PagePresenterBase<TView, TViewModel> : ChildPagePresent erBaseCore where TView : Page, new() where TViewModel : ViewModelBase, new() { public PagePresenterBase() { ViewModel = new TViewModel(); } public TView View { get { return this.ViewBase as TView; } set { this.ViewBase = value; } } public TViewModel ViewModel { get { return this.ViewModelBase as TViewModel; } set { this.ViewModelBase = value; } }
  • 42.
    わんくま同盟 東京勉強会 #119 PagePresenterBase publicoverride void Initialize() { base.Initialize(); View.Loaded += OnPageLoaded; } protected virtual void OnPageLoaded(object sender, RoutedEventArgs e) {} public override void Cleanup() { View.Loaded -= OnPageLoaded; base.Cleanup(); } }
  • 43.
    わんくま同盟 東京勉強会 #119 ChildPresenterBaseCore publicabstract class ChildPagePresenterBaseCore : PresenterBaseCore { public ParentPresenterBaseCore Parent { get; set; } public override void Initialize() { base.Initialize(); } public override void Cleanup() { Parent = null; base.Cleanup(); } 親FramePresenter 参照外し
  • 44.
    わんくま同盟 東京勉強会 #119 ChildPresenterBaseCore internalvoid InvokeOnNavigatedTo(NavigationEventArgs e) { OnNavigatedTo(e);} internal void InvokeOnNavigatingFrom(NavigatingCancelEventArgs e) { OnNavigatingFrom(e); } internal void InvokeOnNavigatedFrom(NavigationEventArgs e) { OnNavigatedFrom(e); } internal void InvokeOnNavigationStopped(NavigationEventArgs e) {OnNavigationStopped (e); } internal void InvokeOnNavigationFailed(NavigationFailedEventArgs e) { OnNavigationFailed(e); }
  • 45.
    わんくま同盟 東京勉強会 #119 ChildPresenterBaseCore protectedvirtual void OnNavigatedTo(NavigationEventArgs e) { } protected virtual void OnNavigatingFrom(NavigatingCancelEventArgs e) {} protected virtual void OnNavigatedFrom(NavigationEventArgs e) {} protected virtual void OnNavigationFailed(NavigationFailedEventArgs e) {} protected virtual void OnNavigationStopped(NavigationEventArgs e) {} }
  • 46.
    わんくま同盟 東京勉強会 #119 PresenterBaseCore publicabstract class PresenterBaseCore { protected TaskScheduler UIThreadTaskScheduler { get; private set; } public bool IsEnable { get; private set; } internal FrameworkElement ViewBase { get; set; } internal ViewModelBase ViewModelBase { get; set; } public PresenterBaseCore() { this.IsEnable = false; } Presenterが有効かどうか Initialize()でtrue CleanUp()でfalse View,ViewModel 非ジェネリック基底クラス用
  • 47.
    わんくま同盟 東京勉強会 #119 PresenterBaseCore publicvirtual void Initialize() { this.UIThreadTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext(); ViewBase.DataContext = ViewModelBase; IsEnable = true; } public virtual void Cleanup() { if (ViewBase != null) { ViewBase.DataContext = null; ViewBase = null; } IsEnable = false; } ViewとViewModelを接続 接続解除 Viewの参照外し 破壊ではなく解放 管理をViewフレームワークに 返すイメージ
  • 48.
    わんくま同盟 東京勉強会 #119 まとめ •UWPでは画面遷移アニメーションがあるので ContentControlではなくてFrameを使う • Presenter,ViewModelは最初にまとめて作る • View(Page)の作成はできる限りフレームワー クに任せ、 使用後は参照を解除し 管理権をフレームワークに返すことでViewの 機能を最大限生かす