C#WinForms Model-View-Presenter(Passive View)プログラムを構成する方法は?
-
29-09-2019 - |
質問
私は、次の基本的なアイデアを持っているGUIを設計しています(Visual Studioの基本的な外観をモデル化しています):
- ファイルナビゲーション
- 制御セレクター(エディターコンポーネントに表示するものを選択するため)
- 編集者
- ロガー(エラー、警告、確認など)
今のところ、ファイルナビゲーションにTreeViewを使用します。これは、エディターに表示されるコントロールを選択するためのリストビュー、ロガー用のRichTextBoxを使用します。編集者には、ツリービューで選択されているものに応じて、2種類の編集モードがあります。エディターは、ファイル内のテキストを手動で編集するためのrichTextBoxであるか、このパネルで編集するためのドラッグ/ドロップデータグリッドビューとサブテキストボックスを備えたパネルになります。
私は、モデルをビューから完全に分離するために、その逆を完全に分離するために、パッシブビュー設計パターンに従うことを試みています。このプロジェクトの性質は、私が追加するコンポーネントが編集/削除の対象となることです。そのため、特定のコントロールから次のコントロールまで独立する必要があります。今日、ファイルナビゲーションにツリービューを使用しているが、明日は他の何かを使用するように言われている場合は、比較的簡単に新しいコントロールを実装したいと思います。
私は単にプログラムの構築方法を理解していません。私はコントロールごとに1人のプレゼンターを理解していますが、コントロール(サブビュー)を備えたビュー(サブビュー)を備えたビュー(プログラムのGUI全体)を持っているように、それを機能させる方法を知りません。私のモデルを反映するコントロール。
パッシブビュー標準によって軽量であるはずのメインビューでは、サブビューを個別に実装しますか?もしそうなら、ナビゲーターオブジェクトの役割を抽象化するためのインターフェイスイナビゲーターがあるとします。ナビゲーターには、ナビゲータービューとメインビューの間で行動するプレゼンターとモデルが必要です。私はどこかでデザインパターンの専門用語で迷子になっているように感じます。
最も同様に関連する質問が見つかります ここ, 、しかし、それは私の質問に十分な詳細に答えません。
誰かが私がこのプログラムを「構築」する方法を理解するのを手伝ってくれますか?どんな助けにも感謝しています。
ありがとう、
ダニエル
解決
抽象化は良いですが、ある時点でそれを覚えておくことが重要です なにか 1つか2つのことについて1つか2つのことを知る必要があります。さもなければ、家に組み立てられる代わりに、床に座っているきれいに抽象化されたレゴの山があります。
反転of-of-of-of-Control/依存関係インジェクション/フリッピーなディッピーアップサイドダウン - どんなものであれ、この週のコンテナのようなコンテナ Autofac これをすべて一緒につなぐのに本当に役立ちます。
WinFormsアプリケーションを一緒に投げると、通常、繰り返しパターンになります。
から始めます Program.cs
AutoFacコンテナを構成し、次のインスタンスを取得するファイル MainForm
それから、そしてそれを示します MainForm
. 。一部の人々はこれをシェルまたはワークスペース、またはデスクトップと呼びますが、とにかくメニューバーを持ち、子のウィンドウまたはチャイルドユーザーコントロールのいずれかを表示するのは「フォーム」です。
次は前述のものです MainForm
. 。私はいくつかのドラッグアンドドロップのような基本的なことをします SplitContainers
と MenuBar
sなどのビジュアルスタジオビジュアルデザイナーでは、コードで空想を得始めます。特定のキーインターフェイスが「注入」されます。 MainForm
私がそれらを利用できるように、私のメインフォームがそれらについてそれほど多くを知る必要なく、子のコントロールを調整できるように、コンストラクターのコンストラクター。
たとえば、私は持っているかもしれません IEventBroker
さまざまなコンポーネントが「イベント」を公開または購読できるインターフェイス BarcodeScanned
また ProductSaved
. 。これにより、アプリケーションの一部が、従来の.NETイベントの配線に依存することなく、ゆるく結合した方法でイベントに応答することができます。たとえば、 EditProductPresenter
それは私と一緒になります EditProductUserControl
いうことができた this.eventBroker.Fire("ProductSaved", new EventArgs<Product>(blah))
そしてその IEventBroker
そのイベントの購読者のリストを確認し、コールバックを呼び出します。たとえば、 ListProductsPresenter
そのイベントを聞いて、動的に更新できます ListProductsUserControl
それが取り付けられていること。最終的な結果は、ユーザーが1つのユーザーコントロールに製品を保存すると、別のユーザーコントロールのプレゼンターが、互いの存在を認識しなければならず、どちらのコントロールも、それがオープンである場合、オープンで、それがオープンである場合、自分自身を更新することができるということです。 MainForm
そのイベントを調整する必要があります。
MDIアプリケーションを設計している場合、 MainForm
実装します IWindowWorkspace
持っているインターフェイス Open()
と Close()
方法。私はそのインターフェースをさまざまなプレゼンターに注入して、彼らが彼らに気づかずに追加のウィンドウを開閉できるようにすることができました MainForm
直接。たとえば、 ListProductsPresenter
開きたいかもしれません EditProductPresenter
そして対応する EditProductUserControl
ユーザーがデータグリッド内の行をダブルクリックしたとき ListProductsUserControl
. 。参照することができます IWindowWorkspace
- 実際には MainForm
, 、しかし、それを知る必要はありません - そして電話してください Open(newInstanceOfAnEditControl)
そして、コントロールがアプリケーションの適切な場所に何らかの形で示されたと仮定します。 ( MainForm
実装は、おそらく、どこかでパネルのビューにコントロールを視聴するでしょう。)
しかし、地獄はどうなるでしょうか ListProductsPresenter
作成 のそのインスタンス EditProductUserControl
? Autofacの代表工場 ここでは本当の喜びです。なぜなら、あなたはプレゼンターに代表者を注入することができ、AutoFacはそれが工場であるかのように自動的にそれを配線するからです(Pseudocodeフォロー):
public class EditProductUserControl : UserControl
{
public EditProductUserControl(EditProductPresenter presenter)
{
// initialize databindings based on properties of the presenter
}
}
public class EditProductPresenter
{
// Autofac will do some magic when it sees this injected anywhere
public delegate EditProductPresenter Factory(int productId);
public EditProductPresenter(
ISession session, // The NHibernate session reference
IEventBroker eventBroker,
int productId) // An optional product identifier
{
// do stuff....
}
public void Save()
{
// do stuff...
this.eventBroker.Publish("ProductSaved", new EventArgs(this.product));
}
}
public class ListProductsPresenter
{
private IEventBroker eventBroker;
private EditProductsPresenter.Factory factory;
private IWindowWorkspace workspace;
public ListProductsPresenter(
IEventBroker eventBroker,
EditProductsPresenter.Factory factory,
IWindowWorkspace workspace)
{
this.eventBroker = eventBroker;
this.factory = factory;
this.workspace = workspace;
this.eventBroker.Subscribe("ProductSaved", this.WhenProductSaved);
}
public void WhenDataGridRowDoubleClicked(int productId)
{
var editPresenter = this.factory(productId);
var editControl = new EditProductUserControl(editPresenter);
this.workspace.Open(editControl);
}
public void WhenProductSaved(object sender, EventArgs e)
{
// refresh the data grid, etc.
}
}
だから ListProductsPresenter
について知っています Edit
機能セット(つまり、編集プレゼンター、編集ユーザーコントロール) - これはまったく問題ありません。 依存関係 の Edit
機能セットは、代わりにAutoFacが提供する代表者に依存して、これらの依存関係をすべて解決します。
一般的に、私は「プレゼンター/ビューモデル/監督コントローラー」との間に1対1の対応があることがわかります(一日の終わりには、それらはすべて非常に似ているので、違いにあまり巻き込まれないようにしましょう)。UserControl
/Form
" UserControl
コンストラクターのプレゼンター/ビューモデル/コントローラーを受け入れ、データビンスは適切なとおりにそれ自体を、可能な限りプレゼンターに延期します。一部の人々はそれを隠します UserControl
インターフェイスを介してプレゼンターから IEditProductView
, 、ビューが完全に受動的ではない場合に役立ちます。私はすべてのためにデータビンディングを使用する傾向があるので、通信は介して行われます INotifyPropertyChanged
気にしないでください。
しかし、プレゼンターが恥知らずに見解に縛られている場合、あなたはあなたの人生をずっと楽にします。オブジェクトモデルのプロパティは、データビンディングとメッシュしませんか?新しいプロパティを公開するようにします。あなたは決して持つつもりはありません EditProductPresenter
と EditProductUserControl
1つのレイアウトを使用して、同じプレゼンターで動作するユーザーコントロールの新しいバージョンを作成したいと考えています。両方を編集するだけで、それらはすべての意図と目的1ユニット、1つの機能、プレゼンターは簡単にテスト可能であり、ユーザーコントロールがそうではないため、存在するプレゼンターのみです。
機能を交換可能にしたい場合は、機能全体を抽象化する必要があります。だからあなたは持っているかもしれません INavigationFeature
あなたのインターフェース MainForm
話します。あなたは持っていることができます TreeBasedNavigationPresenter
それは実装しています INavigationFeature
aによって消費されます TreeBasedUserControl
. 。そして、あなたは持っているかもしれません CarouselBasedNavigationPresenter
それも実装します INavigationFeature
aによって消費されます CarouselBasedUserControl
. 。ユーザーはコントロールし、プレゼンターはまだ手をつないで行きますが、あなたの MainForm
木ベースのビューやカルーセルベースのビューと対話している場合、気にする必要はありません。 MainForm
賢い。
最後に、自分自身を混乱させるのは簡単です。誰もが習慣的であり、わずかに異なる用語を使用して、類似したアーキテクチャパターンの間に微妙な(そして多くの場合重要でない)違いを伝えます。私の謙虚な意見では、カップリングが抑制されているため、依存噴射は合成可能で拡張可能なアプリケーションを構築するのに不思議です。 「プレゼンター/ビューモデル/コントローラー」および「ビュー/ユーザーコントロール/フォーム」への機能の分離は、ほとんどのロジックが前者に引き込まれ、簡単にテストできるようにするため、品質に不思議になります。そして、2つの原則を組み合わせることで、あなたが探しているものであるように思われますが、あなたはちょうど用語で混乱しています。
または、私はそれでいっぱいかもしれません。幸運を!
他のヒント
この質問はほぼ2年前のことを知っていますが、非常によく似た状況にいることに気づきます。あなたのように、私は何日もインターネットを精査しましたが、私のニーズに合った具体的な例を見つけていませんでした - 私が検索したほど、私は同じサイトに何度も戻ってきて、約10ページの紫色のポイントまで何度も何度も戻ってきましたGoogleのリンク!
とにかく、私はあなたが問題に対する満足のいく解決策を思いついたかどうか疑問に思っていましたか?先週、私が読んだものに基づいて、これまでのところこれについてどのように行ったかを概説します。
私の目的は次のとおりです。パッシブフォーム、プレゼンターファースト(プレゼンターはフォームをインスタンス化して、フォームがプレゼンターの知識を持たない)フォームでイベントを提起することで発表者にメソッドを呼び出します(ビュー)
アプリケーションには、2つのユーザーコントロールを含む単一のフォームメインがあります。
ControlSView(3つのボタンを持っています)DocumentView(サードパーティの画像サムネイルビューアー)
「メインフォーム」には、通常のファイルの保存などのツールバーがあります。 「controlsview」ユーザーコントロールを使用すると、ユーザーは「ドキュメント」をクリックすることができます「ドキュメント「スキャン」も含まれています。これには、ドキュメントの階層を表示するツリービューコントロールも含まれています。
各コントロールには、メインフォームと同様に独自のMVPトライアドが必要だと本当に感じましたが、すべて同じモデルを参照してほしいと思いました。コントロール間のコミュニケーションを調整する方法を解決できませんでした。
たとえば、ユーザーが「スキャン」をクリックすると、ControlSpresenterはスキャナーから画像の取得を担当し、各ページがスキャナーから返されたときにページをツリービューに追加したいと思いました - 問題ありません - しかし、サムネイルも欲しかったDocumentSviewに同時に表示するには(プレゼンターがお互いを知らないため、問題)。
私の解決策は、ControlSpresenterがモデルのメソッドを呼び出して新しいページをビジネスオブジェクトに追加することであり、モデルで「塗装された」イベントを提起しました。
次に、ControlSpresenterとDocumentPresenterの両方をこのイベントに「聞いている」ため、ControlSpesenterはTreeViewに新しいページを追加するようにビューを伝え、DocumentPresenterは新しいサムネイルを追加するビューを伝えます。
要約すると:
コントロールビュー - イベントを上げる「ScanButtonClicked」
コントロールプレゼンター - イベントを聞き、スキャナークラスに電話をかけてactireImagesを次のように呼び出します。
GDPictureScanning scanner = new GDPictureScanning();
IEnumerable<Page> pages = scanner.AquireImages();
foreach (Page page in pages)
{
m_DocumentModel.AddPage(page);
//The view gets notified of new pages via events raised by the model
//The events are subscribed to by the various presenters so they can
//update views accordingly
}
各ページがスキャンされると、スキャンループは「ターゲットリターン新しいページ(pageID)」を呼び出します。上記の方法は、m_documentModel.addpage(ページ)を呼び出します。新しいページがモデルに追加され、イベントが発生します。両方のコントロールプレゼンターとドキュメントプレゼンターがイベントを「聞く」こととそれに応じてアイテムを追加します。
私が「確信が持てない」ことは、すべてのプレゼンターの初期化です - 私はこれをプログラム内で行っています。
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
IDocumotiveCaptureView view = new DocumotiveCaptureView();
IDocumentModel model = new DocumentModel();
IDocumotiveCapturePresenter Presenter = new DocumotiveCapturePresenter(view, model);
IControlsPresenter ControlsPresenter = new ControlsPresenter(view.ControlsView, model);
IDocumentPresenter DocumentPresenter = new DocumentPresenter(view.DocumentView, model);
Application.Run((Form)view);
}
これが良いのか、悪いのか、無関心なのかはわかりません!
とにかく、2年前の質問に関するなんて大きな投稿でしょう - フィードバックを得るのは良いことです...