7

私は次の基本的な考え方(Visual Studioの基本的なルックアンドフィールをモデルにしたもの)を持つGUIを設計しています。

  1. ファイルナビゲーション
  2. コントロールセレクター(エディターコンポーネントに表示するものを選択するため)
  3. 編集者
  4. ロガー(エラー、警告、確認など)

今のところ、ファイルナビゲーションにはTreeViewを使用し、エディターに表示するコントロールを選択するにはListViewを使用し、ロガーにはRichTextBoxを使用します。エディターには、TreeViewで選択されているものに応じて、2種類の編集モードがあります。エディターは、ファイル内のテキストを手動で編集するためのRichTextBoxになるか、このパネルで編集するためのドラッグアンドドロップDataGridViewsとサブTextBoxを備えたパネルになります。

モデルをビューから完全に分離するために、パッシブビューのデザインパターンに従おうとしています。このプロジェクトの性質上、追加するコンポーネントはすべて編集/削除される可能性があります。そういうものとして、私は与えられたコントロールから次のコントロールへの独立のためにそこにいる必要があります。今日はファイルナビゲーションにTreeViewを使用していますが、明日は別のものを使用するように言われた場合、比較的簡単に新しいコントロールを実装したいと思います。

プログラムの構成方法がわかりません。コントロールごとに1つのプレゼンターを理解していますが、ビュー(プログラムのGUI全体)とコントロール(サブビュー)を使用して、ビュー全体と個別のビューを交換できるようにする方法がわかりません。私のモデルを反映するコントロール。

パッシブビュー標準では軽量であると想定されているメインビューで、サブビューを個別に実装しますか?もしそうなら、私がナビゲーターオブジェクトの役割を抽象化するためのインターフェースINavigatorを持っているとしましょう。ナビゲーターには、ナビゲータービューとメインビューの間で動作するプレゼンターとモデルが必要です。どこかでデザインパターンの専門用語に迷い込んでいるような気がします。

最も類似した質問はここにありますが、それは私の質問に十分に詳細に答えていません。

このプログラムを「構成」する方法を理解するのを手伝ってくれる人はいますか?助けていただければ幸いです。

ありがとう、

ダニエル

4

2 に答える 2

23

抽象化は良いですが、ある時点で何かが1つか2つのことを知っている必要があることを覚えておくことが重要です。そうしないと、うまく抽象化されたレゴの山が組み立てられるのではなく、床に置かれることになります。家。

制御の反転/依存性注入/flippy-dippy-upside-down-whatever-私たちが今週呼んでいるものは何でも、Autofacのようなコンテナはこれをすべてまとめるのに本当に役立ちます。

WinFormsアプリケーションをまとめると、通常、繰り返しパターンが発生します。

Program.csAutofacコンテナーを構成するファイルから始めて、MainFormそこからインスタンスをフェッチし、を表示しMainFormます。これをシェル、ワークスペース、またはデスクトップと呼ぶ人もいますが、いずれにせよ、メニューバーがあり、子ウィンドウまたは子ユーザーコントロールのいずれかを表示する「フォーム」であり、閉じるとアプリケーションが終了します。

次は前述MainFormです。Visual Studioビジュアルデザイナーでドラッグアンドドロップなどの基本的な作業を行ってからSplitContainersMenuBarコードに夢中になり始めます。特定のキーインターフェイスをMainFormコンストラクターに「挿入」して、それらを利用できるので、私のMainFormは、それらについてそれほど多くを知る必要なしに、子コントロールを調整できます。

たとえば、さまざまなコンポーネントがまたはのような「イベント」を公開またはサブスクライブできるようにするインターフェイスIEventBrokerがあるとします。これにより、アプリケーションの一部は、従来の.NETイベントの配線に依存することなく、緩く結合された方法でイベントに応答できます。たとえば、それは私の言うことができ、そのイベントのサブスクライバーのリストをチェックして、コールバックを呼び出します。たとえば、はそのイベントをリッスンし、動的に更新することができますBarcodeScannedProductSavedEditProductPresenterEditProductUserControlthis.eventBroker.Fire("ProductSaved", new EventArgs<Product>(blah))IEventBrokerListProductsPresenterListProductsUserControlそれが取り付けられていること。最終的な結果として、ユーザーが1つのユーザーコントロールに製品を保存すると、別のユーザーコントロールのプレゼンターは、コントロールがお互いの存在を認識したりMainForm、オーケストレーションしたりすることなく、開いている場合に反応して更新できます。そのイベント。

MDIアプリケーションを設計している場合は、メソッドを持つインターフェイスをMainForm実装している可能性があります。そのインターフェイスをさまざまなプレゼンターに挿入して、プレゼンターが直接気付かないうちに追加のウィンドウを開いたり閉じたりできるようにすることができます。たとえば、ユーザーがのデータグリッドの行をダブルクリックしたときに、に対応するを開きたい場合があります。これは、実際にはを参照できますが、それを知る必要はありません。呼び出して、コントロールがアプリケーションの適切な場所に表示されていると想定します。(実装は、おそらく、コントロールをどこかのパネルのビューにスワップします。)IWindowWorkspaceOpen()Close()MainFormListProductsPresenterEditProductPresenterEditProductUserControlListProductsUserControlIWindowWorkspaceMainFormOpen(newInstanceOfAnEditControl)MainForm

しかし、どうやってそのインスタンスをListProductsPresenter 作成するEditProductUserControlのでしょうか?Autofacのデリゲートファクトリは、プレゼンターにデリゲートを挿入するだけで、Autofacがファクトリーであるかのように自動的に接続するため、ここでは真の喜びです(擬似コードは次のとおりです)。


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知る必要はありません。機能セット。代わりに、Autofacが提供するデリゲートに依存して、それらの依存関係をすべて解決します。Edit

一般的に、私は「プレゼンター/ビューモデル/監視コントローラー」(結局のところ、それらはすべて非常に似ているので、違いにあまり追いつかないようにしましょう)と「UserControl/ Form"。はUserControl、コンストラクターでプレゼンター/ビューモデル/コントローラーを受け入れ、必要に応じてそれ自体をデータバインドし、可能な限りプレゼンターに延期します。一部の人々はUserControl、のようなインターフェースを介してプレゼンターからを非表示にしますIEditProductView。これは、ビューが完全にパッシブでない場合に役立ちます。私はすべてにデータバインディングを使用する傾向があるので、通信は経由で行われ、INotifyPropertyChanged気になりません。

しかし、プレゼンターが恥知らずにビューに結び付けられている場合、あなたはあなたの人生をはるかに楽にするでしょう。オブジェクトモデルのプロパティがデータバインディングとメッシュされていませんか?新しいプロパティを公開します。EditProductPresenterとを1つのレイアウトで使用することは決してなくEditProductUserControl、同じプレゼンターで機能する新しいバージョンのユーザーコントロールを作成する必要があります。両方を編集するだけです。これらはすべての目的と目的のために1つのユニット、1つの機能であり、プレゼンターはユニットのテストが容易で、ユーザーコントロールがないため、存在するだけです。

機能を置き換え可能にする場合は、機能全体をそのように抽象化する必要があります。だからあなたはあなたが話しかけるINavigationFeatureインターフェースを持っているかもしれません。を実装し、によって消費されるをMainForm持つことができます。また、を実装し、によって消費されるものがあるかもしれません。ユーザーコントロールとプレゼンターは引き続き連携しますが、ツリーベースのビューとカルーセルベースのビューのどちらとやり取りするかを気にする必要はなく、賢くなくてもそれらを交換できます。TreeBasedNavigationPresenterINavigationFeatureTreeBasedUserControlCarouselBasedNavigationPresenterINavigationFeatureCarouselBasedUserControlMainFormMainForm

最後に、混乱しがちです。誰もが衒学者であり、わずかに異なる用語を使用して、類似したアーキテクチャパターン間の微妙な(そして多くの場合重要ではない)違いを伝えます。私の謙虚な意見では、依存性注入は、結合が抑えられているため、構成可能で拡張可能なアプリケーションを構築するのに不思議に思います。機能を「プレゼンター/ビューモデル/コントローラー」と「ビュー/ユーザーコントロール/フォーム」に分離すると、ほとんどのロジックが前者に引き込まれ、ユニットテストが容易になるため、品質に驚かされます。そして、2つの原則を組み合わせることは本当にあなたが探しているもののようです、あなたはただ用語で混乱しているだけです。

または、私はそれでいっぱいになる可能性があります。幸運を!

于 2010-12-02T01:00:05.650 に答える
4

この質問は2年近く前のものですが、非常によく似た状況にあります。あなたのように、私はDAYSのためにインターネットを調べましたが、私のニーズに合った具体的な例は見つかりませんでした-検索すればするほど、同じサイトに何度も何度も戻ってきて、約10ページの紫色になりましたGoogleのリンク!

とにかく、あなたが問題の満足のいく解決策を思いついたことがありますか?先週読んだ内容に基づいて、これまでの経緯を概説します。

私の目的は次のとおりです。パッシブフォーム、プレゼンターファースト(プレゼンターはフォームをインスタンス化するため、フォームはプレゼンターを認識しません)フォームでイベントを発生させることにより、プレゼンターのメソッドを呼び出します(表示)

アプリケーションには、2つのユーザーコントロールを含む単一のFormMainがあります。

ControlsView(3つのボタンがあります)DocumentView(サードパーティの画像サムネイルビューア)

「メインフォーム」には、通常のファイル保存などのツールバーがあります。「ControlsView」ユーザーコントロールを使用すると、ユーザーは「ドキュメントのスキャン」をクリックできます。また、ドキュメントとページの階層を表示するツリービューコントロールも含まれています。「DocumentView」には、スキャンしたドキュメントのサムネイルが表示されます。

各コントロールには、メインフォームだけでなく独自のMVPトライアドも必要だと実感しましたが、すべて同じモデルを参照するようにしたかったのです。コントロール間の通信を調整する方法がわかりませんでした。

たとえば、ユーザーが[スキャン]をクリックすると、ControlsPresenterがスキャナーからの画像の取得を担当し、スキャナーから返される各ページとしてツリービューにページを追加したかったのですが、問題ありませんが、サムネイルも必要でした。 DocumentsViewに同時に表示されます(プレゼンターがお互いを知らないための問題)。

私の解決策は、ControlsPresenterがモデル内のメソッドを呼び出して、新しいページをビジネスオブジェクトに追加してから、モデル内で「PageAdded」イベントを発生させることでした。

次に、ControlsPresenterとDocumentPresenterの両方がこのイベントを「リッスン」して、ControlsPesenterが新しいページをツリービューに追加するようにビューに指示し、DocumentPresenterが新しいサムネイルを追加するようにビューに指示するようにします。

要約すると:

コントロールビュー-イベント「ScanButtonClicked」を発生させます

Controls Presenter-イベントを聞き、次のようにScannerクラスを呼び出してAcquireImagesを呼び出します。

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                
}

各ページがスキャンされると、スキャンループは「yieldreturn new Page(PageID)」を呼び出します。上記のメソッドはm_DocumentModel.AddPage(page)を呼び出します。新しいページがモデルに追加され、イベントが発生します。コントロールプレゼンターとドキュメントプレゼンターの両方がイベントを「聞き」、それに応じてアイテムを追加します。

私が「確信が持てない」のは、すべてのプレゼンターの初期化です。これは、Program.cs内で次のように実行しています。

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年前の質問に対するなんと大きな投稿ですが、フィードバックを得るのは良いことです...

于 2012-09-16T18:11:07.540 に答える