web-dev-qa-db-ja.com

共通の機能を共有するWindowsフォームに最適なデザイン

以前は、アプリケーションでWindowsフォームの拡張を許可するために継承を使用していました。すべてのフォームに共通のコントロール、アートワーク、および機能がある場合、共通のコントロールと機能を実装する基本フォームを作成し、他のコントロールがその基本フォームから継承できるようにします。しかし、私はそのデザインでいくつかの問題に遭遇しました。

  1. コントロールは一度に1つのコンテナーにしか入れることができないため、静的コントロールは扱いにくいものになります。例:このクラスの他のすべての(派生した)インスタンスが同じTreeViewを変更および表示できるように、保護および静的にするTreeViewを含むBaseFormという基本フォームがあるとします。そのTreeViewは一度に1つのコンテナにしか存在できないため、これはBaseFormから継承する複数のクラスでは機能しません。初期化された最後のフォームにある可能性があります。すべてのインスタンスがコントロールを編集できますが、一度に1つだけ表示されます。もちろん、回避策はありますが、すべて醜いです。 (これは私には本当に悪いデザインのようです。なぜ複数のコンテナが同じオブジェクトへのポインタを格納できないのですか?とにかく、それはそれが何であるかです。)

  2. フォーム間の状態、つまり、ボタンの状態、ラベルテキストなど。グローバル変数を使用して、ロード時に状態をリセットする必要があります。

  3. これは、Visual Studioのデザイナーではあまりサポートされていません。

使いやすく、なおかつ保守が容易なデザインはありますか?それともフォームの継承はまだ最善のアプローチですか?

更新 MVC、MVP、オブザーバーパターン、イベントパターンの順に検討しました。ここで私が今考えていることは、批評してください:

私のBaseFormクラスには、コントロールと、それらのコントロールに接続されたイベントのみが含まれます。それらを処理するために何らかのロジックが必要なすべてのイベントは、すぐにBaseFormPresenterクラスに渡されます。このクラスは、UIからのデータを処理し、論理演算を実行してから、BaseFormModelを更新します。モデルは、状態の変化時に発生するイベントを、サブスクライブ(または監視)するPresenterクラスに公開します。 Presenterがイベント通知を受信すると、ロジックを実行し、Presenterはそれに応じてビューを変更します。

メモリ内の各Modelクラスは1つだけですが、BaseFormの多くのインスタンス、したがってBaseFormPresenterが存在する可能性があります。これは、BaseFormの各インスタンスを同じデータモデルに同期するという私の問題を解決します。

質問:

フォーム間で(CSSメニューのように)ユーザーが強調表示できるように、最後に押されたボタンなどを格納するレイヤーはどれですか。

このデザインを批判してください。ご協力いただきありがとうございます!

20
Jonathan Henson
  1. なぜ静的コントロールが必要なのかわかりません。多分あなたは私が知らないことを知っています。私は多くの視覚的継承を使用しましたが、静的コントロールが必要であるとは一度も見たことがありません。共通のツリービューコントロールがある場合は、すべてのフォームインスタンスにコントロールの独自のインスタンスを持たせ、ツリービューにバインドされたdataの単一のインスタンスを共有します。

  2. フォーム間で(データではなく)制御状態を共有することも珍しい要件です。 FormBがFormAのボタンの状態について本当に知っている必要があると確信していますか?MVPまたはMVCの設計を検討してください。各フォームは、他のビューやアプリケーション自体についてさえ何も知らない、ダムの「ビュー」と考えてください。スマートなプレゼンター/コントローラーで各ビューを監視します。理にかなっている場合は、1人のプレゼンターが複数のビューを監視できます。状態オブジェクトを各ビューに関連付けます。ビュー間で共有する必要があるいくつかの状態がある場合は、プレゼンターがこれを仲介するようにします(データバインドを検討します-以下を参照)。

  3. 同意すると、Visual Studioは頭痛の種になるでしょう。フォームまたはユーザーコントロールの継承を検討するときは、フォームデザイナの苛立たしい癖や制限と格闘することの潜在的な(そしてありそうな)コストに対する利点を慎重に比較する必要があります。フォームの継承を最小限に抑えることをお勧めします-ペイオフが高い場合にのみ使用してください。サブクラス化の代わりに、一般的な「ベース」フォームを作成し、「子」となる各インスタンスに対してインスタンス化するだけで、その場でカスタマイズできることに注意してください。これは、フォームの各バージョン間の違いが、共有される側面と比較して小さい場合に意味があります。 (IOW:複雑な基本フォーム、やや複雑な子フォームのみ)

UI開発の大幅な重複を防ぐのに役立つ場合は、ユーザーコントロールを使用してください。ユーザーコントロールの継承を考慮しますが、フォームの継承と同じ考慮事項を適用します。

私が提供できる最も重要なアドバイスは、ビュー/コントローラーパターンを現在採用していない場合は、そうすることを強くお勧めします。それはあなたにルーズクーピングと層分離の利点を学び、認めるように強制します。

更新への応答

どのレイヤーに最後に押されたボタンのようなものを保存する必要があるので、ユーザーのために強調表示できます...

プレゼンターとそのビューの間で状態を共有するのと同じように、ビュー間で状態を共有できます。特別なクラスSharedViewStateを作成します。簡単にするために、それをシングルトンにするか、メインプレゼンターでインスタンス化して、そこから(プレゼンター経由で)すべてのビューに渡すことができます。状態がコントロールに関連付けられている場合、可能な場合はデータバインディングを使用します。ほとんどのControlプロパティはデータにバインドできます。たとえば、ButtonのBackColorプロパティをSharedViewStateクラスのプロパティにバインドできます。同一のボタンを持つすべてのフォームでこのバインディングを行うと、SharedViewState.Button1BackColor = someColorを設定するだけで、すべてのフォームでButton1を強調表示できます。

WinFormsのデータバインディングに慣れていない場合は、MSDNにアクセスして読んでください。難しいことではありません。 INotifyPropertyChangedについて学び、途中で終了です。

例として、Button1BackColorプロパティを持つビューステートクラスの一般的な実装を次に示します。

public class SharedViewState : INotifyPropertyChanged
{
    // boilerplate INotifyPropertyChanged stuff
    public event PropertyChangedEventHandler PropertyChanged;
    protected void NotifyPropertyChanged(string info)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(info));
        }
    }

    // example of a property for data-binding
    private Color button1BackColor;
    public Color Button1BackColor
    {
        get { return button1BackColor; }
        set
        {
            if (value != button1BackColor)
            {
                button1BackColor = value;
                NotifyPropertyChanged("Button1BackColor");
            }
        }
    }
}
6
Igby Largeman

MVVMパターンと pdate Controls に基づいて、新しいWinForms/WPFアプリケーションを維持しています。 WinFormsから始まり、見栄えの良いUIのマーケティングの重要性からWPFバージョンを作成しました。同じバックエンドコード(ビューモデルとモデル)で2つの完全に異なるUIテクノロジをサポートするアプリケーションを維持することは、興味深い設計上の制約になると思いました。このアプローチには非常に満足しています。

私のアプリでは、UIのパーツ間で機能を共有する必要があるときはいつでも、カスタムコントロールまたはユーザーコントロール(WPF UserControlまたはWinForms UserControlから派生したクラス)を使用します。 WinFormsバージョンでは、TabPageの派生クラス内の別のUserControl内にUserControlがあり、最終的にはメインフォームのタブコントロール内にあります。 WPFバージョンには、実際にはUserControlsが1レベル深くネストされています。カスタムコントロールを使用すると、以前に作成したUserControlsから新しいUIを簡単に作成できます。

私はMVVMパターンを使用しているので、できるだけ多くのプログラムロジックをViewModel( Presentation/Navigation Model を含む)またはModel(コードがユーザーインターフェイスに関連しているかどうかに応じて)に入れました)、ただし同じViewModelがWinFormsビューとWPFビューの両方で使用されるため、ViewModelには、WinFormsまたはWPF用に設計されたコード、またはUIと直接対話するコードを含めることはできません。このようなコードはビューに入れる必要があります。

また、MVVMパターンでは、UIオブジェクトは互いに相互作用しないようにする必要があります。代わりに、ビューモデルと対話します。たとえば、TextBoxは近くのListBoxにどの項目が選択されているかを尋ねません。代わりに、リストボックスは現在選択されているアイテムへの参照をビューモデルレイヤーのどこかに保存し、テキストボックスはビューモデルレイヤーにクエリを実行して、現在何が選択されているかを調べます。これにより、あるフォームで押されたボタンが別のフォーム内から検出されるという問題が解決します。 2つのフォーム間で(アプリケーションのビューモデルレイヤーの一部である)ナビゲーションモデルオブジェクトを共有し、押されたボタンを表すプロパティをそのオブジェクトに配置します。

WinForms自体はMVVMパターンをあまりサポートしていませんが、 pdate Controls は、WinFormsの上にあるライブラリとして独自のアプローチMVVMを提供しています。

私の意見では、このプログラム設計へのアプローチは非常にうまく機能しており、今後のプロジェクトで使用する予定です。うまく機能する理由は、(1)Update Controlsが依存関係を自動的に管理すること、および(2)通常、コードの構造化方法が非常に明確であることです。UIオブジェクトとやり取りするすべてのコードはViewに属し、すべてのコードはUIに関連していますが、UIオブジェクトと対話する必要はありませんが、ViewModelに属しています。多くの場合、コードを2つの部分に分割します。1つはView用、もう1つはViewModel用です。システムでコンテキストメニューを設計するのは困難でしたが、最終的にはそのための設計も思い付きました。

私も ブログの更新コントロールについて もあります。ただし、このアプローチにはかなりの慣れが必要です。また、大規模なアプリ(リストボックスに数千のアイテムが含まれる場合など)では、自動依存関係管理の現在の制限により、パフォーマンスの問題が発生する可能性があります。

5
Qwertie

あなたはすでに答えを受け入れていますが、私はこれに答えます。

他の人が指摘したように、なぜ静的なものを使用しなければならなかったのかはわかりません。これは、あなたが何か間違ったことをしているようです。

とにかく、私はあなたと同じ問題を抱えていました。私のWinFormsアプリケーションには、いくつかの機能といくつかのコントロールを共有するいくつかのフォームがあります。また、すべてのフォームは、ベースフォーム(「MyForm」と呼ぶことにします)から既に派生しています。これにより、(アプリケーションに関係なく)フレームワークレベルの機能が追加されます。VisualStudioのフォームデザイナは、他のフォームから継承するフォームの編集をサポートしています練習は、フォームが「Hello、world!-OK-Cancel」以外に何も行わない場合にのみ機能します。

結局のところ、これは非常に複雑な共通の基本クラス「MyForm」を保持し、アプリケーションのすべてのフォームをそこから派生させ続けています。ただし、これらのフォームではまったく何もしないので、VSフォームデザイナでフォームを編集しても問題はありません。これらのフォームは、フォームデザイナがフォーム用に生成するコードのみで構成されています。次に、「Surrogates」と呼ぶオブジェクトの個別の並列階層があり、これには、コントロールの初期化、フォームとコントロールによって生成されたイベントの処理など、アプリケーション固有のすべての機能が含まれています。1対1のアプリケーションのサロゲートクラスとダイアログの間の対応:「MyForm」に対応する基本サロゲートクラスがあり、「MyApplicationForm」に対応する別のサロゲートクラスが派生し、その後、対応する他のサロゲートクラスの束全体が派生します。アプリケーションが表示するすべての異なるフォームに。

各サロゲートは、特定のタイプのフォームを構築時パラメーターとして受け入れ、そのイベントに登録することにより、自身にアタッチします。また、「MyForm」を受け入れる「MySurrogate」に至るまで、ベースに委任します。そのサロゲートはフォームの「Disposed」イベントに登録されるため、フォームが破棄されると、サロゲートがオーバーライド可能オブジェクトを呼び出し、そのサロゲートとそのすべての子孫がクリーンアップを実行できるようにします。 (イベント等からの退会)

これまでのところ問題なく動作しています。

3
Mike Nakis