WinFormsのモデルビュープレゼンター


90

WinFormsを使用して、MVPメソッドを初めて実装しようとしています。

各層の機能を理解しようとしています。

私のプログラムには、クリックするとopenfiledialogウィンドウを開くGUIボタンがあります。

したがって、MVPを使用して、GUIはボタンクリックイベントを処理し、presenter.openfile();を呼び出します。

presenter.openfile()内で、ファイルのオープンをモデルレイヤーに委任する必要がありますか、または処理するデータやロジックがないので、リクエストに基づいて単純にopenfiledialogウィンドウを開く必要がありますか?

更新: 私はこれについてさらに支援が必要だと感じたときに報奨金を提供することを決定しました。できれば、以下の特定のポイントに合わせて調整し、状況を把握できるようにします。

さて、MVPについて読んだ後、パッシブビューを実装することにしました。事実上、プレゼンターによって処理されるWinform上の一連のコントロールと、モデルに委任されたタスクを用意します。私の具体的なポイントは以下のとおりです。

  1. Winformが読み込まれると、ツリービューを取得する必要があります。ビューはしたがってpresenter.gettree()などのメソッドを呼び出す必要があると私は思っていますか?これは、モデルに委譲し、ツリービューのデータを取得し、作成して構成し、それをプレゼンターは、ビューに渡され、ビューに単純に割り当てられます。

  2. これはWingridのデータコントロールでも同じですか?私もdatagridviewを持っているのですか?

  3. 私のアプリには、同じアセンブリを持つ多数のモデルクラスがあります。また、起動時にロードする必要があるプラグインを備えたプラグインアーキテクチャもサポートしています。ビューは単にプレゼンターメソッドを呼び出し、プラグインをロードしてビューに情報を表示するメソッドを呼び出しますか?その後、どの層がプラグイン参照を制御します。ビューは彼らまたは発表者への参照を保持しますか?

  4. ツリービューのノードの色からデータグリッドのサイズなど、ビューはプレゼンテーションに関するすべてのことを処理するべきだと私は思っていますか?

それらが私の最大の関心事であると私は思います、そしてこれらの流れがどうあるべきかを理解していれば、私は大丈夫だと思います。


このリンクlostechies.com/derekgreer/2008/11/23/…は、MVPのいくつかのスタイルを説明しています。ヨハンの優れた答えに加えて、それは役に立つかもしれません。
ak3nat0n

回答:


123

これは、MVPとあなたの特定の問題に対する私の謙虚な見方です。

まず、ユーザーが操作したり、表示したりできるものはすべてビューですです。このようなビューの法則、動作、および特性は、インターフェースによって記述されます。そのインターフェースは、WinForms UI、コンソールUI、Web UIを使用して実装することも、UIをまったく使用しないこともできます(通常、プレゼンターをテストする場合)。ビューインターフェースの法則に従う限り、具体的な実装は問題ではありません。 。

次に、ビューは常にプレゼンターによって制御さます。このようなプレゼンターの法律、行動、特性は、インターフェースます。そのインターフェースは、そのビューインターフェースの法則に従う限り、具体的なビューの実装には関係ありません。

第三に、プレゼンターはビューを制御するため、依存関係を最小限に抑えるために、ビューにプレゼンターについての知識をまったく持たせても、実際には利益はありません。プレゼンターとビューの間で合意された契約があり、それはビューインターフェースによって示されます。

サードの影響は次のとおりです。

  • プレゼンターには、ビューが呼び出すことができるメソッドはありませんが、ビューには、プレゼンターがサブスクライブできるイベントがあります。
  • プレゼンターはその見方を知っています。具体的なプレゼンターでコンストラクターインジェクションを使用してこれを実現することを好みます。
  • ビューには、どのプレゼンターがそれを制御しているかはわかりません。プレゼンターに提供されることはありません。

あなたの問題では、上記は多少簡略化されたコードでは次のようになります:

interface IConfigurationView
{
    event EventHandler SelectConfigurationFile;

    void SetConfigurationFile(string fullPath);
    void Show();
}

class ConfigurationView : IConfigurationView
{
    Form form;
    Button selectConfigurationFileButton;
    Label fullPathLabel;

    public event EventHandler SelectConfigurationFile;

    public ConfigurationView()
    {
        // UI initialization.

        this.selectConfigurationFileButton.Click += delegate
        {
            var Handler = this.SelectConfigurationFile;

            if (Handler != null)
            {
                Handler(this, EventArgs.Empty);
            }
        };
    }

    public void SetConfigurationFile(string fullPath)
    {
        this.fullPathLabel.Text = fullPath;
    }

    public void Show()
    {
        this.form.ShowDialog();        
    }
}

interface IConfigurationPresenter
{
    void ShowView();
}

class ConfigurationPresenter : IConfigurationPresenter
{
    Configuration configuration = new Configuration();
    IConfigurationView view;

    public ConfigurationPresenter(IConfigurationView view)
    {
        this.view = view;            
        this.view.SelectConfigurationFile += delegate
        {
            // The ISelectFilePresenter and ISelectFileView behaviors
            // are implicit here, but in a WinForms case, a call to
            // OpenFileDialog wouldn't be too far fetched...
            var selectFilePresenter = Gimme.The<ISelectFilePresenter>();
            selectFilePresenter.ShowView();
            this.configuration.FullPath = selectFilePresenter.FullPath;
            this.view.SetConfigurationFile(this.configuration.FullPath);
        };
    }

    public void ShowView()
    {
        this.view.SetConfigurationFile(this.configuration.FullPath);
        this.view.Show();
    }
}

上記に加えて、私は通常ベースを持っています IViewShow()、自分のビューが通常恩恵を受ける所有者ビューまたはビュータイトルを隠しインターフェースをています。

あなたの質問に:

1. winformが読み込まれると、ツリービューを取得する必要があります。ビューはしたがってpresenter.gettree()などのメソッドを呼び出す必要があると私は考えていますか?これは、モデルに委譲され、ツリービューのデータを取得し、作成して構成し、それをプレゼンターは、ビューに渡され、ビューに単純に割り当てられます。

私は呼ぶだろうIConfigurationView.SetTreeData(...)からIConfigurationPresenter.ShowView()右への呼び出しの前に、IConfigurationView.Show()

2. データグリッドビューも持っているので、これはWinformのどのデータコントロールでも同じですか?

はい、電話します IConfigurationView.SetTableData(...)。与えられたデータをフォーマットするのはビュー次第です。プレゼンターは、表形式のデータが必要であるというビューの規約に従います。

3. 私のアプリには、同じアセンブリを持つモデルクラスがいくつかあります。また、起動時にロードする必要があるプラグインを備えたプラグインアーキテクチャもサポートしています。ビューは単にプレゼンターメソッドを呼び出し、プラグインをロードしてビューに情報を表示するメソッドを呼び出しますか?その後、どの層がプラグイン参照を制御します。ビューは彼らまたはプレゼンターへの参照を保持しますか?

プラグインがビューに関連している場合、ビューはそれらを認識している必要がありますが、プレゼンターは認識していません。それらがすべてデータとモデルに関するものである場合、ビューはそれらとは何の関係もないはずです。

4. ビューは、treeviewノードの色からデータグリッドのサイズなど、プレゼンテーションに関するすべてのことを処理するべきだと考えるのは正しいですか?

はい。これは、データを説明するXMLと、データを取得してCSSスタイルシートを適用するビューを提供するプレゼンターと考えることができます。具体的には、発表者が電話をかけるIRoadMapView.SetRoadCondition(RoadCondition.Slippery)と、ビューが道路を赤色で表示します。

クリックされたノードのデータはどうですか?

5. ツリーノードをクリックしたときに、特定のノードをプレゼンターに渡し、そこからプレゼンターが必要なデータを算出し、モデルにそのデータを要求してから、ビューに戻す必要がある場合はどうすればよいですか。

できれば、ツリーをビューに表示するために必要なすべてのデータを一度に渡します。しかし、一部のデータが最初から渡すには大きすぎる場合、またはその性質が動的であり、モデルから(プレゼンターを介して)「最新のスナップショット」が必要な場合event LoadNodeDetailsEventHandler LoadNodeDetailsは、ビューインターフェイスに次のようなものを追加して、プレゼンターはそれにサブスクライブしLoadNodeDetailsEventArgs.Node、モデルからノードの詳細を(おそらくそのIDを介して)フェッチできるため、イベントハンドラーデリゲートが戻るときにビューが表示されたノードの詳細を更新できます。ユーザーエクスペリエンスを向上させるためにデータのフェッチが遅すぎる場合は、この非同期パターンが必要になる場合があることに注意してください。


3
ビューとプレゼンターを切り離す必要は必ずしもないと思います。私は通常、モデルとプレゼンターを切り離し、プレゼンターにモデルのイベントを聞いてもらい、それに応じて行動します(ビューを更新します)。プレゼンターがビューにいると、ビューとプレゼンター間のコミュニケーションが容易になります。
kasperhj

11
@lejon:プレゼンターがビューにいるとビューとプレゼンター間のコミュニケーションが容易になると言いますが、私は強く反対します。私の立場はこれです。ビューがプレゼンターについて知っている場合、ビューイベントごとに、ビューはどのプレゼンターメソッドを呼び出すのが適切であるかを決定する必要があります。ビューはどのプレゼンターメソッドに対応するビューイベントを実際に認識していないため、これは「2点の複雑さ」です。契約はそれを指定していません。
Johann Gerell、2011

5
@lejon:一方、ビューが実際のイベントのみを公開している場合は、プレゼンター自体(ビューイベントが発生したときに何をしたいかを知っている)がサブスクライブして正しいことを行います。それは「1ポイントの複雑さ」だけで、私の本では「2ポイントの複雑さ」の2倍です。一般的に言えば、カップリングが少ないほど、プロジェクトの実行にかかるメンテナンスコストが少なくなります。
Johann Gerell、2011

9
このリンクで説明されているように、私もカプセル化されたプレゼンターを使用する傾向がありますlostechies.com/derekgreer/2008/11/23/…ビューはプレゼンターの唯一の所有者です。
ak3nat0n

3
@ ak3nat0n:提供されたリンクで説明されているMVPの3つのスタイルに関して、Johannによるこの回答は、Observing Presenter Styleという名前の3番目のスタイルと最も密接に一致していると思います:
DavidRR 2015

11

すべてを含むプレゼンター ビューのロジック @ JochemKempeが言うように、クリックされるボタンに応答する必要があります。実際には、ボタンクリックイベントハンドラー呼び出しpresenter.OpenFile()。その後、発表者は何をすべきかを決定することができます。

ユーザーがファイルを選択する必要があると判断した場合は(ビューインターフェイスを介して)ビューにコールバックし、すべてのUI技術を含むビューに、OpenFileDialogます。これは、プレゼンターが使用中のUIテクノロジに関連付けられた操作を実行することを許可されるべきではないという点で、非常に重要な違いです。

選択したファイルは発表者に返され、発表者はそのロジックを続行します。これには、ファイルの処理を処理する必要があるモデルまたはサービスが含まれる場合があります。

MVPパターンを使用する主な理由は、imoがUIテクノロジーをビューロジックから分離することです。したがって、プレゼンターはすべてのロジックを調整し、ビューはそれをUIロジックから分離します。これには、プレゼンターを完全にユニットテスト可能にするという非常に優れた副作用があります。

更新:プレゼンターは1つの特定のビューにあるロジックの実施形態であるため、ビューとプレゼンターの関係はIMOの1対1の関係です。そしてすべての実用的な目的のために、1つのビューインスタンス(フォームなど)は1つのプレゼンターインスタンスと対話し、1つのプレゼンターインスタンスは1つのビューインスタンスのみと対話します。

つまり、WinFormsを使用したMVPの実装では、プレゼンターは常に、ビューのUI機能を表すインターフェイスを介してビューと対話します。どのビューがこのインターフェイスを実装するかについての制限はありません。したがって、異なる「ウィジェット」が同じビューインターフェイスを実装し、プレゼンタークラスを再利用する場合があります。


ありがとう。したがって、presenter.OpenFile()メソッドでは、openfiledialogを表示するコードを含めないでください。代わりに、そのウィンドウを表示するためにビューに戻る必要がありますか?
Darren Young

4
そうです、私はプレゼンターに直接ダイアログボックスを開かせないでください。テストを壊してしまうからです。これをビューにオフロードするか、いくつかのシナリオで行ったように、実際のダイアログ操作を処理する別の「FileOpenService」クラスを用意します。そうすれば、テスト中にファイルを開くサービスを偽ることができます。このようなコードを別のサービスに配置すると、再利用性に
優れた副次

2

プレゼンターは、リクエストに基づいて行動し、提案どおりにopenfiledialogウィンドウを表示します。モデルからのデータは必要ないため、プレゼンターはリクエストを処理でき、また処理する必要があります。

モデルにエンティティを作成するためにデータが必要だと仮定しましょう。ストリームからエンティティを作成するメソッドがあるアクセスレイヤーにストリームトラフを渡すことができますが、プレゼンターでファイルの解析を処理し、モデルのエンティティごとにコンストラクターまたはCreateメソッドを使用することをお勧めします。


1
ご返信ありがとうございます。また、ビューのプレゼンターは1人ですか?そして、そのプレゼンターは要求を処理するか、またはデータが必要な場合は、特定の要求に基づいて動作する任意の数のモデルクラスに委任しますか?それは正しい方法ですか?再度、感謝します。
Darren Young

3
ビューには1人のプレゼンターがいますが、プレゼンターは複数のビューを持つことができます。
JochemKempe
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.