漫坊亭

社会の底辺プログラマ

(自分用翻訳) Caliburn.Micro > Documentation > Screens, Conductors and Composition

原文へのリンク (google翻訳)

Screens, Conductors and Composition

Actionsは、CoroutinesとConventionsはCaliburn.Microに最も注意を引くために傾向がありますが、画面とConductors部品は、あなたのUIがよく操作することがしたい場合は理解することは、おそらく最も重要です。あなたは、組成物を活用したい場合に特に重要です。用語Screen、Screen_ConductorとScreen_Collectionはより最近アディソンウェスリーのための本「プレゼンテーションパターン」の彼の作業中にジェレミー·ミラーによって体系化されています。

これらのパターンは、主に特定の基本クラスからViewModelsを継承することによって、CMで使用されているが、その重要な役割としてではなくView_Modelsと考えることができます。実際には、あなたのアーキテクチャに応じて、ScreenはUserControl、Presenterまたは_ViewModelである可能性があります。それは、しかし、自分自身の少し先を得ています。まずは、これらのものは、一般的には何かについて話しましょう。

Theory

Screen

これは、理解するための最も簡単な構成です。あなたはアプリケーションのプレゼンテーション層内の既存の作業のステートフル単位と考えるかもしれません。これは、アプリケーションシェルから独立したのです。シェルはいくつかあっても、同時に、多くの異なる画面を表示することができます。シェルは、同様のウィジェットの多くを表示することがありますが、これらは任意の画面の一部ではありません。いくつかの画面例では、アプリケーションの設定、Visual Studioでコードエディタウィンドウまたはブラウザでのページのためのモーダルダイアログであるかもしれません。おそらく、このことについてかなり良い直感的な感覚を持っています。

多くの場合、画面は画面がカスタムアクティブ化および非アクティブ化のロジックを実行することができ、それに関連付けられたライフサイクルを持っています。これは、ジェレミーはScreenActivatorを呼ぶものです。たとえば、Visual Studioのコードエディタウィンドウを取ります。 1つのタブでC#コードファイルを編集している場合は、XML文書を含むタブに切り替えるには、ツールバーのアイコンが変化していることがわかります。これらの画面のそれぞれは、それらがアクティブな画面に基づいて適切なアイコンを提供するように、アプリケーションのツールバーをsetup/teardownすることを可能にするカスタム起動/停止ロジックを持っています。単純なシナリオでは、ScreenActivatorは画面と同じクラスであることが多いです。 ただし、これらの2つの別々の役割があることを覚えておいてください。特定のスクリーンは、複雑な起動ロジックがある場合は、画面の複雑さを低減するために、独自のクラスにScreenActivatorを考慮する必要があるかもしれない。これは、多くの異なる画面を使用してアプリケーションを持っている場合に特に重要ですが、同じ有効化/無効化ロジックを持つすべての。

Screen Conductor

あなたがあなたのアプリケーションに画面起動ライフサイクルの概念を導入したら、それを強制するための手段が必要です。これはScreenConductorの役割です。あなたは、画面を表示すると、コンダクタは、それが適切にアクティベートされていることを確認します。あなたが画面から離れて移行している場合は、それが非アクティブ化されますを確認します。 同様に重要なの別のシナリオがあります。あなたが保存されていないデータが含まれていると、誰かがその画面、あるいはアプリケーションを終了しようとすると画面があるとします。すでに非アクティブ化を強制されScreenConductorは、正常なシャットダウンを実行することによって助けることができます。 あなたの画面が活性化/非活性化のためのインタフェースを実装する場合があります 同様に、それはまた、コンダクタがそれを依頼することができますいくつかのインタフェースを実装することができる。これは重要なポイントが表示されます「あなたは閉じることができますか?」:画面を不活性化するいくつかのシナリオであります画面を閉じるなど、他でも同じ、それは異なっています。タブ間を切り替えるときにたとえば、Visual Studioで、それは、文書を閉じません。それはちょうど活性化する/それらを不活性化します。あなたが明示的にタブを閉じる必要があります。つまり、正常なシャットダウン·ロジックをトリガーするものです。しかし、ナビゲーションベースのアプリケーションで、ページから離れて移動すると、間違いなく不活性化の原因となるが、それはまた、そのページを閉じてしまう可能性があります。それはすべて特定のアプリケーションのアーキテクチャに依存し、それはあなたがについて慎重に検討すべきものです。

Screen Collection

Visual Studioのようなアプリケーションでは、あなただけの等活性化、非活性化を管理ScreenConductorを持っていないだろうが、また、現在開いている画面やドキュメントのリストを維持ScreenCollectionを持っているでしょう。パズルのこの部分を追加することで、我々はまた、非活性化のVisual Studioの問題を解決することができます。近接しています。 ScreenCollectionにあるものはすべて開いたままであるが、これらの項目のうちの1つのみが一度にアクティブです。 VSのようなMDIスタイルのアプリケーションでは、コンダクタはScreenCollectionのメンバー間のアクティブな画面を切り替える管理であろう。新しい文書を開くと、ScreenCollectionに追加し、アクティブな画面に切り替えることになります。ドキュメントを閉じると、それだけを非アクティブ化しませんが、ScreenCollectionからそれを削除でしょう。それは質問に答えるか否かに依存するであろうとすべてが"あなたは閉じることはできますか?」とは、正に。文書を閉じた後もちろん、導体は次のアクティブなドキュメントになるべきScreenCollection内の他の項目のどれかを決定する必要があります。

Implementations

これらのアイデアを実装するさまざまな方法がたくさんあります。あなたはTabControlのから継承し、IScreenConductorインタフェースを実装し、コントロールに直接すべてのロジックを構築することができます。あなたのIoCコンテナにそれを追加すると、オフにして実行しています。カスタムユーザーコントロールのIScreenインタフェースを実装することができるか、POCOは、コントローラを管理するためのベースとして使用されるようにあなたそれを実装することができます。 ScreenCollectionは、アクティブな画面を維持するための特別なロジックを使用してカスタムコレクションをすることができ、またはそれは単純なのIListである可能性があります。

Caliburn.Micro Implementations

これらの概念は_ViewModelsを構築するためにmostly1を使用することができ、様々なインタフェースと基底クラスを介してCMに実装されています。 それらを見てみましょう:

Screen

Caliburn.Microでは、いくつかのインタフェースに画面起動の概念を破壊しました:

  • IActivate - 実装者は、活性化が必要であることを示します。このインタフェースは、Activateメソッド、プロパティのisActiveおよび活性化が発生したときに発生する必要があります活性化イベントを提供します。
  • IDeactivateは - 実装者は、不活性化が必要であることを示します。このインタフェースは、それを不活性化することに加えて、画面を閉じているかどうかを示すブール値プロパティを取る非アクティブ化メソッドがあります。また、2つのイベントがあります。AttemptingDeactivationは不活性化する前に送出しなければなりません。Deactivatedは不活性化した後に上げるする必要があります。 +IGuardClose - 実装者は、閉じる操作をキャンセルする必要があることを示します。 CanClose:それは1つのメソッドがあります。この方法は、近接判断しながら行われるような非同期のユーザインタラクションのような複雑なロジックを可能にする非同期パターンで設計されています。呼び出し側はCanCloseメソッドに_Actionを渡します。ガード·ロジックが完了すると、実装者は、アクションを呼び出す必要があります。そうでない場合はfalse、実装者は閉じることができることを示すためにtrueを渡します。

これらのコアのライフサイクル·インタフェースに加えて、我々は、プレゼンテーション層のクラス間の一貫性を作成するのに役立つためにいくつか他を持っています:

  • IHaveDisplayName - DisplayNameと呼ばれる単一のプロパティを持っています
  • INotifyPropertyChangedEx このインタフェースは、標準INotifyPropertyChangedのを継承し、追加の動作とそれを強化します。これは、すべての変更通知、プロパティの変更やオブジェクトのすべてのバインディングをリフレッシュするために使用することができる最新の情報に更新メソッドを上げるために呼び出すことができますNotifyOfPropertyChangeメソッドにオン/オフするために使用することができIsNotifyingプロパティを追加します。
  • IObservableCollection - 次のインタフェース合成します:IListINotifyPropertyChangedExおよびINotifyCollectionChanged
  • IChild 階層の一部であるか、またはそれはオーナーへの参照を必要とする要素によって実装されます。それは、親のプロパティを1つ指名しました。
  • IViewAwareは - それらが結合しているという見解を認識させるために必要なクラスによって実装されます。それはインスタンスへのビューを結合する場合に、フレームワークによって呼び出されAttachViewメソッドがあります。これは、フレームワークは、インスタンスのビューを作成する前に呼び出す_GetViewメソッドがあります。これは、複雑なビューや、複雑なビューの解決ロジックのキャッシュが有効になります。最後に、ビューはViewAttachedというインスタンスに接続されているときに発生する必要があるイベントがあります。

特定の組み合わせは非常に一般的であるため、我々はいくつかの便利なインタフェースと基底クラスを持っています:

  • PropertyChangedBase - INotifyPropertyChangedEx(したがって、INotifyPropertyChangedの)を実装します。これは、厳密に型指定された変更通知を有効にする、標準の文字列機構に加えてラムダベースNotifyOfPropertyChange方法を提供します。また、すべてのプロパティ変更イベントは自動的にUIスレッドにマーシャリングされます。
  • BindableCollection - 標準のObservableCollectionから継承し、INotifyPropertyChangedExで指定された追加の行動を追加することにより、IObservableCollectionを実装します。また、このクラスは、すべてのプロパティの変更、コレクション変更イベントは、UIスレッド上で発生していることを保証します。
  • IScreen - このインタフェースには、いくつかの他のインタフェースを構成する:IHaveDisplayName、IActivate、IDeactivate、IGuardCloseとINotifyPropertyChangedExを
  • Screenは - PropertyChangedBaseから継承し、IScreenインタフェースを実装しています。さらに、IChildとIViewAwareが実装されています。

このすべての手段は、あなたはおそらくPropertyChangedBaseまたは画面のいずれかからあなたのビューモデルのほとんどを継承することです。一般的にあなたが他のすべてのアクティベーション機能およびPropertyChangedBaseのいずれかを必要とする場合には、画面を使用し、話します。 CMのデフォルトの画面の実装では、同様にいくつかの追加機能を持っており、それが簡単にライフサイクルの適切な部分にフックすることができます:

  • OnInitialize - 画面が活性化されることを初回のみ実行すべきロジックを追加するには、このメソッドをオーバーライドします。初期化が完了すると、初期化されるには、trueになります。
  • OnActivate - 画面が起動されるたびに実行すべきロジックを追加するには、このメソッドをオーバーライドします。アクティベーションが完了した後、のisActiveはtrueになります。
  • OnDeactivate - 画面が非アクティブ化またはクローズされたときに実行されるべきカスタムロジックを追加するには、このメソッドをオーバーライドします。不活性化は、実際に近い場合にブール値プロパティが示されます。不活性化が完了した後、のisActiveは偽になります。
  • CanClose - デフォルトの実装は常に閉鎖を可能にします。カスタムガード·ロジックを追加するには、このメソッドをオーバーライドします。
  • OnViewLoaded - 画面がIViewAwareを実装しているので、それはあなたのビューのLoadedイベントが発生したときに知らせるために機会としてこれを取ります。あなたがSupervisingControllerやPassiveViewスタイルに従っている場合は、これを使用して、ビューを使用する必要があります。これはまた、あなたが直接ビューで作業されていなくても、ビューの存在に依存することができるビューモデルロジックを置く場所です。
  • TryCloseは - 画面を閉じて、このメソッドを呼び出します。画面がコンダクタによって制御されている場合には、画面のシャットダウン処理を開始するためにコンダクタに求めます。画面をコンダクタすることによって制御するが、(これはウィンドウマネージャを使用して示されたなどの理由で)独立して存在していない場合、このメソッドは、ビューをクローズしよう。いずれのシナリオでもCanCloseロジックが呼び出され、許可されていれば、OnDeactivateのは、真の値で呼び出されます。

だから、ただの反復再:あなたがライフサイクルを必要とする場合、_Screenを継承。そうでなければPropertyChangedBaseから継承します。

Conductors

私は前述したようにあなたは、ライフサイクルを導入したら、あなたはそれを強制するために何かを必要としています。 Caliburn.Microでは、この役割は、以下のメンバーを持つIConductorインターフェースによって表されます。

  • ActivateItem - 特定のアイテムをアクティブにするために、このメソッドを呼び出します。コンダクタが使用している場合、それはまた、現在実施項目に追加します」画面の収集を。」
  • DeactivateItem - 特定のアイテムを無効にするには、このメソッドを呼び出します。二番目のパラメータは、アイテムも閉じる必要があるかどうかを示します。もしそうであればコンダクタが使用している場合、それはまた、現在実施項目から削除されます」画面コレクションを。」
  • ActivationProcessed - コンダクタがアイテムの活性化を処理した場合に発生します。それは活性化が成功したかどうかを示します。
  • GetChildren - コンダクタが追跡しているすべての項目のリストを返すようにこのメソッドを呼び出します。コンダクタが「画面」これがすべて返されます」、画面集」を使用している場合はそれ以外の場合はこれが唯一のActiveItemを返します。 (IParentインターフェースから)
  • INotifyPropertyChangedEx - このインタフェースは、IConductorに構成されています。

我々はまた、次のメンバーを追加するIConductorとIHaveActiveItemを構成IConductActiveItemと呼ばれるインターフェースを持っています:

  • ActiveItem - コンダクタが現在のようなアクティブな追跡してどのような項目を示すプロパティ。

あなたは、CMのIConductorインターフェースは用語「項目」ではなく、「画面」を使用することに気づいたかもしれませんし、私は引用符で用語「画面の収集」を入れていたこと。この理由は、アイテムを必要としないCMのコンダクタの実装がIScreenまたは任意の特定のインターフェイスを実装するために行われていることです。実施項目はPOCOSすることができます。むしろIScreenの使用を強制するよりも、コンダクタの実装の各タイプには制約で、総称です。コンダクタは、それが導通している各項目をactivate/deactivate/close/etcするように要求されるように、以下の細粒度のインターフェイスに個別にチェックします。IActivate、IDeactive、IGuardCloseとIChild。実際には、私は通常画面から実施項目を継承しますが、これはあなたがクラス単位で気にライフサイクル·イベントのためのインタフェースを実装する独自の基本クラスを使用するかのみにするための柔軟性を提供します。あなたも、画面と特定のインターフェイスまたはなしですべて実装他から継承し、そのいくつかの不均一なアイテムを、追跡するコンダクタを持つことができます。

箱から出してCMがIConductorの3つの実装を持っています、 ない「画面の収集」と1で動作する2。 私たちは、最初のコレクションなしコンダクタを見てみましょう。

Conductor

この単純なコンダクタは、明示的なインターフェイス機構を介してIConductorのメンバーの過半数を実装し、強力に公開されている同じメソッドのバージョンを入力した追加されます。これは、一般的にインターフェイスを介してだけでなく、彼らが行っている項目に基づいて厳密に型指定された方法でコンダクタを扱うことができます。Conductorは不活性化と同義閉鎖を扱い。Conductorは「画面の収集」を維持しないので、それぞれの新しいアイテムの活性化は、不活性化し、以前にアクティブな項目の近くに両方の原因となります。

実施項目が原因IGuardCloseの非同期性質と実施項目は、このインタフェースを実装してもしなくてもよいか、という事実のために複雑になる可能性が閉じることができるか否かを判断するための実際のロジック。そのため、これを処理し、コンダクタにお問い合わせの結果を通知しますICloseStrategyにconductor delegatesこれ。  時間のほとんどは、あなたが自動的に提供されDefaultCloseStrategyで大丈夫ですが、あなたは、独自の戦略にあなたはConductorにCloseStrategyプロパティを設定することもできます(おそらくIGuardCloseはあなたの目的のためには十分ではない)のものを変更する必要があります

Conductor.Collection.OneActive

この実装はConductorのすべての機能を持っているだけでなく、"screen_collection"の概念が追加されます。 CM内のコンダクタは、クラスの任意の型を行うことができるので、このコレクションはIObservableCollectionと呼ばれるアイテムではなく、画面を介して公開されます。 Itemsコレクション、不活性化及び実施項目の閉鎖の存在の結果として同義に扱われません。新しい項目が起動されると、以前のアクティブな項目のみを無効化され、それがItemsコレクションに残ります。このコンダクタのアイテムを閉じるには、明示的CloseItemメソッドを呼び出す必要があります。アイテムは閉鎖され、その項目がアクティブな項目であったされている場合、コンダクタは、次のアクティベートすべき項目を決定する必要があります。デフォルトでは、リスト内の前のアクティブな項目前の項目です。あなたはこの動作を変更する必要がある場合は、DetermineNextItemToActivateを上書きすることができます。

Conductor.Collection.AllActive

同様に、この実装はまたConductorの特徴を有しており、screen_collection_の概念が追加されます。 主な違いはなく、一度にアクティブになる単一の項目よりも、多くのアイテムをアクティブにすることができることです。アイテムを閉じると、それを不活性化し、コレクションから削除します。

私はまだ言及していないのCM IConductorの実装に関する2つの非常に重要な詳細があります。第一に、それらの両方が画面から継承します。それは画面と導体間の複合パターンを作成しますので、これは、これらの実装の重要な特徴です。それでは、あなたは基本的なナビゲーションスタイルのアプリケーションを構築しているとしましょう。それは、一度に1つの画面が表示され、コレクションを保持していないためにあなたのシェルはConductor_IScreenのインスタンスになります。しかし、のは、それらのいずれかの画面は非常に複雑で、各タブのライフサイクル·イベントを必要とするマルチタブ付きインタフェースを持つことが必要であったとしましょう。まあ、その特定の画面がConductor_T.Collection.OneActiveを継承可能性があります。シェルは個々の画面の複雑さを心配する必要はありません。それらのいずれかの画面があってもそれが必要とされたものだ場合のViewModelの代わりにIScreenを実装し、ユーザーコントロールである可能性があります。第二の重要な詳細は、最初の結果です。 IConductorのすべてのOOTBの実装が画面から継承するので、それは彼らがあまりにもライフサイクルと、彼らが行っているどのような項目に、そのライフサイクルのカスケードを有することを意味します。コンダクタが無効になっているのであれば、それはActiveItemも同様に無効になります。あなたはコンダクタを閉じるしようとすると、それだけで、それが閉じることができます行ったすべての項目場合は閉じることができることになるだろう。これは非常に強力な機能であることが判明しました。私は頻繁に気づいた開発者をトリップしてきたこれについての一つの側面があります。あなたは、それ自体がアクティブでないコンダクタでアイテムを有効化するとコンダクタが活性化されるまで、そのアイテムは実際には有効になりません。これは、あなたがそれについて考える意味になりますが、時折髪を引っ張る原因になります。

Quasi-Conductors

画面には、コンダクタの内側に根ざしていることができCMではないすべてのもの。たとえば、ルートビューモデルについては何?それは指揮者の場合は、誰がそれを活性化しているのですか?まあ、それは、ブートストラップが実行するジョブの一つです。Bootstrapper自体はコンダクタではありませんが、前述したファイングレイン·ライフサイクル·インターフェースを理解し、あなたのルートビューモデルはそれに値する敬意を持って扱われることを保証します。 WindowManagerはあなたのモーダルウィンドウのライフサイクルを実施する目的で、コンダクタのように少しを作用することにより、同様の方法で動作します。だから、ライフサイクルは魔法ではありません。すべての画面は/導体はいずれかコンダクタに根ざしたか、正しく動作するようにブートストラップやウィンドウマネージャによって管理されなければなりません。そうしないと、ライフサイクルを自分で管理する必要があるとしています。

View-First

あなたはWP7での作業やSilverlightのナビゲーションフレームワークを使用している場合は、画面や導体を活用する方法/場合は、疑問に思うかもしれません。これまでのところ、私は工学をシェルに主にViewModelに、最初のアプローチを想定してきました。しかし、WP7プラットフォームは、ページナビゲーションを制御することにより、ビュー·ファーストアプローチを実施します。同じことがSLナビゲーションフレームワークの真のです。これらのケースでは、電話/ナビゲーションフレームワークは、導体のように作用します。これはのviewmodelsとよく遊ぶようにするために、CMのWP7版はのNavigationServiceにフックFrameAdapterを持っています。 PhoneBootstrapperによって設定されたこのアダプタは、導体が行うのと同じ粒度の細かいライフサイクル·インターフェースを理解し、彼らはナビゲーション中に適切な時点で、あなたのviewmodelsで呼び出されることを保証します。あなたもあなたのViewModelにIGuardCloseを実装することにより、携帯電話のページナビゲーションをキャンセルすることができます。 FrameAdapterはCMのWP7版の一部でしかありませんが、それは、Silverlightにも容易に移植できる必要がありますSilverlightのナビゲーションフレームワークと一緒にそれを使用したいはずです。

Simple Navigation

以前、我々はCaliburn.Microで画面とコンダクタのための理論と基本的なAPIを説明しました。今、私はいくつかのサンプルの最初のウォークスルーしたいと思います。この特定のサンプルでは、Conductorと二つの「ページ」ビューモデルを使用して、簡単なナビゲーションスタイルのシェルを設定する方法を示します。あなたがプロジェクト構造から見ることができるように、我々はブートストラップとShellViewModelの典型的なパターンを持っています。できるだけ単純このサンプルを保つために、私もブートストラップとIoCコンテナを使用していませんよ。まずはShellViewModelを見てみましょう。これは次のようにConductorを継承し、実装されています。

public class ShellViewModel : Conductor<object> 
{
    public ShellViewModel() {
        ShowPageOne();
    }

    public void ShowPageOne() {
        ActivateItem(new PageOneViewModel());
    }

    public void ShowPageTwo() {
        ActivateItem(new PageTwoViewModel());
    }
}

ここでは、対応するShellViewは、次のとおりです。

<UserControl x:Class="Caliburn.Micro.SimpleNavigation.ShellView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             xmlns:tc="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Toolkit">
    <tc:DockPanel>
        <StackPanel Orientation="Horizontal"
                    HorizontalAlignment="Center"
                    tc:DockPanel.Dock="Top">
            <Button x:Name="ShowPageOne"
                    Content="Show Page One" />
            <Button x:Name="ShowPageTwo"
                    Content="Show Page Two" />
        </StackPanel>

        <ContentControl x:Name="ActiveItem" />
    </tc:DockPanel>
</UserControl>

ShellViewModelがActivateItemメソッドにビューモデルのインスタンスを渡すそれぞれが2つのメソッドを持っていることに注意してください。ActivateItemがこのインスタンスに導体のActiveItemプロパティを切り替えて、(それがIActivateを実装することによって、それをサポートしている場合)画面のライフサイクルの活性化段階を介してインスタンスをプッシュしますConductor_Tの方法で私たちの以前の議論から思い出してください。ActiveItemがすでにインスタンスに設定されている場合は、新しいインスタンスが設定されているその前に、前のインスタンスがまたはActiveItemの切り替えをキャンセルしてもしなくてもよいIGuardCloseの実施のためにチェックされることも思い出してください。閉じることができ、現在のActiveItemを仮定すると、コンダクタは、ビューモデルもクローズされることを示すために、無効化メソッドにtrueを渡す、ライフサイクルの不活性化段階を通してそれをプッシュします。これは、Caliburn.Microのナビゲーションアプリケーションを作成するのにかかるすべてのです。導体のActiveItemは「現在のページ」を表し、コンダクタは、他のページに遷移を管理します。これは、すべてのコンダクタ以来のViewModelファースト方式で行われ、それがナビゲーションやない運転している子ビューモデルの「ビューを。」

基本的な導体構造が適所に配置されると、それがレンダリングを取得するのは非常に簡単です。 ShellViewはこれを示しています。私たちがしなければならないのは、ビューにContentControlを置くです。それに名前を付けることによって「ActiveItem"我々のデータは、結合規則がでキック。ContentControlの規則は少し面白いです。我々はに結合されているアイテムは値型ではなく、文字列でない場合は、我々はコンテンツはViewModelにあることを前提としています。 View.Model:だから、代わりに私たちは他の場合と同じようにContentプロパティに結合するのは、我々は実際にはCMのカスタム添付プロパティにバインディング設定します。このプロパティは、2つを一緒に結合するビューモデルとCMのViewModelBinderための適切なビューを検索するために、CMのViewLocatorを引き起こします。それが完了すると、我々はContentControlのコンテンツプロパティへのビューをポップ。この単一の規則は、フレームワークで強力な、まだ簡単なViewModelに、まず構成を可能するものです。

完全を期すのための PageOneViewModel と PageTwoViewModel を見てをみましょう。

public class PageOneViewModel {}

public class PageTwoViewModel : Screen 
{
    protected override void OnActivate() 
    {
        MessageBox.Show("Page Two Activated"); //Don't do this in a real VM.
        base.OnActivate();
    }
}

その_viewsと一緒に:

<UserControl x:Class="Caliburn.Micro.SimpleNavigation.PageOneView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <TextBlock FontSize="32">Page One</TextBlock>
</UserControl>

<UserControl x:Class="Caliburn.Micro.SimpleNavigation.PageTwoView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <TextBlock FontSize="32">Page Two</TextBlock>
</UserControl>

それはで遊んで、これらピースがどのように連携するかを見るのは簡単だように、私は意図的にこのベアボーンを守ってきました。これは、少しでも印象的ではないのですが、ここでは次のようになります。

私はいくつかの最終的な事を指摘したいと思います。 PageOneViewModelがちょうどPOCOであることに注意してください、しかしPageTwoViewModelはScreenから継承します。CM内のコンダクタを行うことができるものに制約を置かないことに注意してください。むしろ、必要な時間に様々なファイングレイン·ライフサイクル·インスタンスをサポートするために、各インスタンスを確認してください。 ActivateItemがPageTwoViewModelのために呼び出されたときに、それは最初にそれがIGuardCloseを実装するかどうかを確認するためにPageOneViewModelをチェックします。そうでないので、それを閉じしようとします。それはそれはIDeactivateを実装するかどうかをチェックします。そうでないので、それだけで、新しいアイテムをアクティブに進みます。新しいアイテムがIChildを実装する場合最初に、チェックします。画面がないので、階層関係をフックします。次に、それはIActivateを実装するかどうかを確認するためにPageTwoViewModelをチェックします。画面がないので、私のOnActivateイベントメソッドのコードは、次に実行されます。最後に、コンダクタ上にActiveItemプロパティを設定し、適切なイベントが発生します。ここで忘れてはならないこれの重要な結果は次のとおりです。活性化はViewModelに固有のライフサイクル·プロセスであり、表示の状態については何も保証しません。多くの場合、あなたのViewModelが活性化されているにもかかわらず、それのビューはまだ表示されない場合があります。サンプルを実行するときには、このように表示されます。活性化が発生したときにメッセージボックスが表示されますが、2ページのためのビューがまだ表示されません。あなたはすでにロードビューであることに依存している任意の活性化ロジックを持っている場合、注意してください、あなたはOnActivateイベントと組み合わせて/の代わりにScreen.OnViewLoadedオーバーライドする必要があります。

Simple MDI

この時にScreenCollectionsを使用する単純なMDIシェルを:の別の例を見てみましょう。 あなたが見ることができるように、もう一度、私はかなり小さくてシンプルなものを保持しています:

ここでは、実行中のアプリケーションのスクリーンショットです:

ここでは、一連のタブを持つ単純なWPFアプリケーションを持っています。OpenTabボタンをクリックすると、明白なことを行います。タブ内の「X」をクリックすると、(おそらく、また明らかな)その特定のタブを閉じます。私たちのShellViewModelを見てコードを掘り下げてみましょう:

public class ShellViewModel : Conductor<IScreen>.Collection.OneActive {
    int count = 1;

    public void OpenTab() {
        ActivateItem(new TabViewModel {
            DisplayName = "Tab " + count++
        });
    }
}

我々が開いているアイテムのリストを維持しますが、一度に一つのアイテムをアクティブに維持したいので、私たちは私たちの基本クラスとしてConductor_T.Collection.OneActiveを使用しています。前の例とは異なることに注意してください、私は実際にIScreenに行っアイテムの種類を拘束しています。そこにこのサンプルでは、このための技術的な理由は本当にないのですが、これはより密接に、私は実際に実際のアプリケーションでどうなるのか反映しています。 OpenTabメソッドは、単純にTabViewModelのインスタンスを作成し、それは、人間が読める、固有の名前を持つように(IScreenから)そのDisplayNameプロパティを設定します。のは、コンダクタと、いくつかの重要なシナリオでの画面間の相互作用のためのロジックを介して考えてみましょう:

最初の項目を開きます 1. Itemsコレクションに項目を追加します。 2. IActivateする項目をチェックし、存在する場合、それを起動します。 3. ActiveItemなどの項目を設定します。

追加の項目を開きます 1. IDeactivateの現在のActiveItemをチェックし、存在する場合起動します。Falseのは、それが唯一の非アクティブ化、および閉じられていない必要があることを示すために渡されます。 2. 既にItemsコレクション内に存在するかどうかを確認するために、新しいアイテムをチェックします。そうでない場合には、コレクションに追加されます。それ以外の場合は、既存の項目が返されます。 3. IActivateする項目をチェックし、存在する場合起動します。 4. ActiveItemとして新しいアイテムを設定します。

既存項目を閉じます 1. それは(デフォルトそれはIGuardClose探しによって)閉じることができるかどうかを判断するためにCloseStrategyにアイテムを渡します。そうでない場合、アクションがキャンセルされます。 2. 閉アイテムが現在ActiveItemであるかどうかを確認します。その場合は、次の活性化からの手順に従うようにどの項目を決定「追加のアイテムを開きます。」 3. クロージングアイテムがIDeactivateであるかどうかを確認します。もしそうであれば、それは非アクティブにして閉じる必要があることを示すために真で起動します。 4. Itemsコレクションから項目を削除します。

それらは主なシナリオです。うまくいけば、コレクションなしConductorとの相違点のいくつかを見て、それらの違いがある理由を理解することができます。 ShellViewのレンダリング方法を見てみましょう:

<Window x:Class="Caliburn.Micro.SimpleMDI.ShellView"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:cal="http://www.caliburnproject.org"
        Width="640"
        Height="480">
    <DockPanel>
        <Button x:Name="OpenTab"
                Content="Open Tab" 
                DockPanel.Dock="Top" />
        <TabControl x:Name="Items">
            <TabControl.ItemTemplate>
                <DataTemplate>
                    <StackPanel Orientation="Horizontal">
                        <TextBlock Text="{Binding DisplayName}" />
                        <Button Content="X"
                                cal:Message.Attach="CloseItem($dataContext)" />
                    </StackPanel>
                </DataTemplate>
            </TabControl.ItemTemplate>
        </TabControl>
    </DockPanel>
</Window>

ご覧のようにあなたは私たちがWPFのTabControlを使用している。CMの規則は、ItemsコレクションへののItemsSourceとActiveItemへののSelectedItemをバインドします。また、ActiveItemためのViewModel/ビューのペアで構成するために使用されるデフォルトのContentTemplateを追加します。私たちのタブは、すべての(画面を介して)IHaveDisplayNameを実装するため、規則もItemTemplateにを供給することができますが、私は自分自身を供給することにより、タブを閉じて有効にすることをオーバーライドすることを選択しました。我々は今後の記事で規則について深くより話してみますよ。完全にするために、ここではそのビューと共にTabViewModelの些細な実装を次のとおりです。

namespace Caliburn.Micro.SimpleMDI {
    public class TabViewModel : Screen {}
}
<UserControl x:Class="Caliburn.Micro.SimpleMDI.TabView"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <StackPanel Orientation="Horizontal">
        <TextBlock Text="This is the view for "/>
        <TextBlock x:Name="DisplayName" />
        <TextBlock Text="." />
    </StackPanel>
</UserControl>

私は、これまでそれをシンプルに保つために試みたが、それは私たちの次のサンプルの場合ではないのです。製造においては、少なくともを通じて考えるかこれらのことを行うにしようとする場合があります:

  • ジェネリックTabViewModelを取り除きます。あなたは本当に実際のアプリケーションでこのようなことをしないだろう。カスタムビューモデルとビューのカップルを作成します。あなたはコンダクタに異なるビューモデルを開くことができるように、アップワイヤーもの。各ビューモデルが起動されたときに、タブコントロール内の正しいビューを参照してくださいことを確認します。
  • Silverlightでこのサンプルを再構築します。残念ながら、シルバーのTabControlのは全く破壊され、完全にデータバインディングを利用することはできません。その代わりに、タブのように水平リストボックスとタブのコンテンツとしてContentControlを使用してみてください。 DockPanelにこれらを入れて、いくつかの命名規則を使用して、あなたは同じTabControlのように影響しています。
  • ツールバーのビューモデルを作成します。 IoCコンテナを追加し、シングルトンとしてToolBarViewModelを登録します。 ShellViewModelに追加し、それがShellViewでレンダリングされていることを確認します(このために命名ContentControlを使用することができます覚えておいてください)。次に、ToolBarViewModelはTabViewModelsのそれぞれに注入しています。特定TabViewModelが活性化されると、ツールバーから文脈項目を追加/削除するTabViewModel OnActivateイベントやOnDeactivateのにコードを記述します。ボーナス:OnDeactivateのオーバーライドで、明示的なコードを必要としないこれを行うためのDSLを作成します。ヒント:イベントを使用してください。
  • SimpleMDIサンプルとSimpleNavigationサンプルを取り、それらを一緒に構成しています。いずれかのナビゲーションサンプルでPageViewModelとしてMDIシェルを追加したり、MDIサンプルにタブとしてナビゲーションシェルを追加します。

Hybrid

このサンプルは、大まかに、このよく知られたDNRテレビのエピソードでビリー·ホリスによって実証アイデアに基づいています。むしろUIが何を説明するのに時間がかかるよりも、簡単な視覚的な説明については、この短いビデオを見ています。

[OK]を、今、あなたはそれが何を見てきたこと、のは、それが一緒に入れています方法を見てみましょう。顧客、注文、設定など:あなたはスクリーンショットからわかるように、私は機能してプロジェクトを整理することにしました。ほとんどのプロジェクトでは、私はむしろ、このようなViewsやViewModelsとして「技術」グループによって整理よりも、このような何かを行うことを好みます。 私は、複雑な機能を持っている場合は、私はそれらの領域にそれを分解するすることがあります。

私はこのサンプルを通じてline_by_lineに行くつもりはありません。 あなたはそれを見て、物事が自分自身をどのように機能するかを把握するために時間がかかる場合は良いでしょう。しかし、私はいくつかの興味深い実装の詳細を指摘しておきたい。

ViewModel Composition

Caliburn.Microで画面とコンダクタの最も重要な機能の1つは、彼らがそれらを簡単に異なる構成で一緒に構成すること、Compositeパターンの実装であるということです。一般的に言えば、組成物は、オブジェクト指向プログラミングや大きな利益を得ることができ、あなたのプレゼンテーション層にそれを使用する方法を学習の最も重要な側面の1つです。組成物は、この特定のサンプルでは役割を果たしているかを確認するために、2つのスクリーンショットを見てみましょう。最初は、特定の顧客の住所を編集し、ビューのCustomersWorkspaceを使用してアプリケーションを示しています。 2番目の画面が同じですが、あなたはUIが構成されてどのように見ることができますように、そのビューで/ビューモデルのペアは、3次元的に回転させました。

f:id:jfactory:20150715131838p:plainf:id:jfactory:20150715131855p:plain

このアプリケーションでは、ShellViewModelはConductor_IWorkspace_.Collection.OneActiveです。これは、視覚的にウィンドウクロム、ヘッダーとボトムドックで表されます。ドックは、実施されている各IWorkspaceのための1つのボタンがあります。特定のボタンをクリックすると、シェルは、その特定のワークスペースをアクティブになります。ShellViewはActiveItemにバインドTransitioningContentControlを持っているので、アクティベートワークスペースが注入され、それは、ビューがその位置に表示されます。 この場合には、アクティブであるCustomerWorkspaceViewModelです。それはとてもCustomerWorkspaceViewModelがConductor_CustomerViewModel.Collection.OneActiveから継承することが起こります。このViewModelにするための2つのコンテキストのビュー(下記参照)があります。上のスクリーンショットでは、詳細図を示しています。 詳細は、また、このように現在のCustomerViewModelが沿って、そのビューで構成されていることが原因、CustomerWorkspaceViewModelのActiveItemにバインドTransitioningContentControlを持って表示します。CustomerViewModelはローカルモーダルダイアログを表示する機能(彼らは唯一のその特定のカスタムレコードではなく、何か他のものへのモーダルしている)を有しています。これはCustomerViewModelのプロパティであるDialogConductorのインスタンスによって管理されます。DialogConductorするための図でCustomerViewに重なるが、DialogConductorのActiveItemがnullでない場合(値コンバータを介して)にのみ表示されます。上に示した状態では、DialogConductorのActiveItemはAddressViewModelのインスタンス、このようにモーダルダイアログがAddressViewで表示され、下にあるCustomerViewが無効になっているに設定されています。 このサンプルで使用されるシェル全体のフレームワークは、この方法で動作し、単にIWorkspaceを実装することで、完全に拡張可能です。 CustomerViewModelとSettingsViewModelはあなたに掘ることができ、このインタフェースの二つの異なる実装です。

Multiple Views over the Same ViewModel

あなたはこのことを認識ではないかもしれないが、Caliburn.Microは同じViewModelに上の複数のビューを表示することができます。これは、ビュー/ビューモデルの注射部位にView.Context添付プロパティを設定することでサポートされています。ここではデフォルトのCustomerWorkspaceViewから例を示します。

<clt:TransitioningContentControl cal:View.Context="{Binding State, Mode=TwoWay}"
                                 cal:View.Model="{Binding}" 
                                 Style="{StaticResource specialTransition}"/>

そこCustomerWorkspaceViewのクロムを形成するために、これを取り巻く他のXAMLの多くがあるが、コンテンツ領域は、ビューの中で最も注目すべき部分です。我々はCustomerWorkspaceViewModelのStateプロパティにView.Context添付プロパティをバインドしていることに注意してください。これは、私たちは、動的にそのプロパティの値に基づいてビューを変更することができます。これは、すべてのTransitioningContentControlでホストされているので、私たちはいつでも素晴らしい遷移ビューの変更を取得します。この技術は、それはそれに伴って現在アクティブ化CustomerViewModelを表示する「詳細」ビューに開いているすべてのCustomerViewModels、検索UIと新しいボタンが表示され、「マスター」ビューからCustomerWorkspaceViewModelを切り替えるために使用される特定のビュー(です)で構成される。 CMはこれらののコンテキストのビューを見つけるためには、コンテキストに対応した名前のいくつかのビューとビューモデル名に基づいて名前空間、マイナスの言葉「表示」と「モデル」を、必要とします。フレームワークはCaliburn.Micro.HelloScreens.Customers.CustomersWorkspaceViewModelの詳細ビューを検索する場合、例えば、それはそれはアウトオブボックスの命名規則ですCaliburn.Micro.HelloScreens.Customers.CustomersWorkspace.Detailを探すために起こっています。それはあなたのために動作しない場合は、単にViewLocator.LocateForModelType funcをカスタマイズすることができます。

Custom IConductor Implementation

Caliburn.MicroはIScreenとIConductorのデフォルトの実装を開発者に提供しています。それはあなた自身を実装するのは簡単です。このサンプルの場合、私は他の部分に影響を与えることなく、アプリケーションの特定の部分へのモーダル可能性がダイアログ·マネージャを必要としました。通常は、デフォルトのConductor_Tは動作しますが、私はシャットダウンシーケンスを微調整するために必要な発見、ので、私は自分自身を実装しました。のは、その見てみましょう:

[Export(typeof(IDialogManager)), PartCreationPolicy(CreationPolicy.NonShared)]
public class DialogConductorViewModel : PropertyChangedBase, IDialogManager, IConductActiveItem {
    readonly Func<IMessageBox> createMessageBox;

    [ImportingConstructor]
    public DialogConductorViewModel(Func<IMessageBox> messageBoxFactory) {
        createMessageBox = messageBoxFactory;
    }

    public IScreen ActiveItem { get; private set; }

    public IEnumerable GetChildren() {
        return ActiveItem != null ? new[] { ActiveItem } : new object[0];
    }

    public void ActivateItem(object item) {
        ActiveItem = item as IScreen;

        var child = ActiveItem as IChild;
        if(child != null)
            child.Parent = this;

        if(ActiveItem != null)
            ActiveItem.Activate();

        NotifyOfPropertyChange(() => ActiveItem);
        ActivationProcessed(this, new ActivationProcessedEventArgs { Item = ActiveItem, Success = true });
    }

    public void DeactivateItem(object item, bool close) {
        var guard = item as IGuardClose;
        if(guard != null) {
            guard.CanClose(result => {
                if(result)
                    CloseActiveItemCore();
            });
        }
        else CloseActiveItemCore();
    }

    object IHaveActiveItem.ActiveItem
    {
        get { return ActiveItem; }
        set { ActivateItem(value); }
    }

    public event EventHandler<ActivationProcessedEventArgs> ActivationProcessed = delegate { };

    public void ShowDialog(IScreen dialogModel) {
        ActivateItem(dialogModel);
    }

    public void ShowMessageBox(string message, string title = "Hello Screens", MessageBoxOptions options = MessageBoxOptions.Ok, Action<IMessageBox> callback = null) {
        var box = createMessageBox();

        box.DisplayName = title;
        box.Options = options;
        box.Message = message;

        if(callback != null)
            box.Deactivated += delegate { callback(box); };

        ActivateItem(box);
    }

    void CloseActiveItemCore() {
        var oldItem = ActiveItem;
        ActivateItem(null);
        oldItem.Deactivate(true);
    }
}

厳密に言えば、私は実際に(私は何にそれを構成いないよので)この作業を行うためにIConductorを実装する必要はありませんでした。しかし、私は、このクラスがシステム内で遊んでいた役割を表し、できるだけアーキテクチャ一貫として物事を保つためにこれを実行することにしました。実装自体は非常に単純です。主に、コンダクタは正しく、そのアイテムを非アクティブにし、適切ActiveItemプロパティを更新する/有効にすることを確認する必要があります。私はまた、ダイアログやIDialogManagerインタフェースを介して公開されたメッセージボックスを表示するための簡単な方法をいくつか作成しました。このクラスは、MEFと非共有として登録されるので、CustomerViewModelは、上述で示されたように、独自のインスタンスを取得しますローカルモーダルを表示して、自身の状態を維持できるようにしたいアプリケーションの各部分。

Custom ICloseStrategy

おそらく、このサンプルのクールな機能の一つは、我々は、アプリケーションのシャットダウンを制御する方法です。ISHELLがIGuardCloseを継承しているので、ブートストラップで私たちはIShell.CanCloseを呼び出すためにOnStartupとワイヤ_SilverlightのMainWindow.Closingイベントをオーバーライドします。

protected override void OnStartup(object sender, StartupEventArgs e) {
    base.OnStartup(sender, e);

    if(Application.IsRunningOutOfBrowser) {
        mainWindow = Application.MainWindow;
        mainWindow.Closing += MainWindowClosing;
    }
}

void MainWindowClosing(object sender, ClosingEventArgs e) {
    if (actuallyClosing)
        return;

    e.Cancel = true;

    Execute.OnUIThread(() => {
        var shell = IoC.Get<IShell>();

        shell.CanClose(result => {
            if(result) {
                actuallyClosing = true;
                mainWindow.Close();
            }
        });
    });
}

ShellViewModelは、その基本クラスConductor_IWorkspace.Collection.OneActiveを通じてこの機能を継承します。すべてのビルトインコンダクタはCloseStrategyを持っているので、我々はシャットダウンのためにコンダクタ特定のメカニズムを作成し、簡単にそれらを接続することができます。我々は、カスタムストラテジをプラグインする方法ここでは次のとおりです。

[Export(typeof(IShell))]
public class ShellViewModel : Conductor<IWorkspace>.Collection.OneActive, IShell
{
    readonly IDialogManager dialogs;

    [ImportingConstructor]
    public ShellViewModel(IDialogManager dialogs, [ImportMany]IEnumerable<IWorkspace> workspaces) {
        this.dialogs = dialogs;
        Items.AddRange(workspaces);
        CloseStrategy = new ApplicationCloseStrategy();
    }

    public IDialogManager Dialogs {
        get { return dialogs; }
    }
}

そして、ここではその戦略の実施は次のとおりです。

public class ApplicationCloseStrategy : ICloseStrategy<IWorkspace> {
    IEnumerator<IWorkspace> enumerator;
    bool finalResult;
    Action<bool, IEnumerable<IWorkspace>> callback;

    public void Execute(IEnumerable<IWorkspace> toClose, Action<bool, IEnumerable<IWorkspace>> callback) {
        enumerator = toClose.GetEnumerator();
        this.callback = callback;
        finalResult = true;

        Evaluate(finalResult);
    }

    void Evaluate(bool result)
    {
        finalResult = finalResult && result;

        if (!enumerator.MoveNext() || !result)
            callback(finalResult, new List<IWorkspace>());
        else
        {
            var current = enumerator.Current;
            var conductor = current as IConductor;
            if (conductor != null)
            {
                var tasks = conductor.GetChildren()
                    .OfType<IHaveShutdownTask>()
                    .Select(x => x.GetShutdownTask())
                    .Where(x => x != null);

                var sequential = new SequentialResult(tasks.GetEnumerator());
                sequential.Completed += (s, e) => {
                    if(!e.WasCancelled)
                    Evaluate(!e.WasCancelled);
                };
                sequential.Execute(new ActionExecutionContext());
            }
            else Evaluate(true);
        }
    }
}

私はここでやった興味深いのは、アプリケーションの非同期シャットダウンのIResultの機能を再利用することでした。カスタム戦略は、それを使用する方法をここでは次のとおりです。

  1. それはIConductorあるかどうかを確認するために、各IWorkspaceを確認してください。
  2. trueの場合、アプリケーション固有のインタフェースIHaveShutdownTaskを実装するすべての実施項目をつかみます。
  3. GetShutdownTaskを呼び出して、シャットダウンタスクを取得します。これは、タスクが存在しない場合はnullを返すので、それらをフィルタリングします。
  4. シャットダウンタスクがIResultあるので、SequentialResultにこれらのすべてに合格し、列挙を開始します。
  5. IResultは、アプリケーションのシャットダウンをキャンセルするには、trueにResultCompletionEventArgs.WasCanceledを設定することができます。
  6. 終了またはキャンセルが発生するまで、すべてのワークスペースを続行します。
  7. すべてIResultsが正常に完了した場合、アプリケーションが閉じるように許可されます。

ダーティデータがある場合CustomerViewModelとOrderViewModelはモーダルダイアログを表示するには、このメカニズムを使用します。しかし、あなたはまた、非同期タスクの任意の数のためにこれを使用することができます。たとえば、あなたは、アプリケーションの停止を防止するために望んでいたいくつかの長い実行中のプロセスを持っていたとします。これも、そのために非常にうまく動作します。