共通の機能を共有するWindowsフォームに最適な設計


20

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

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

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

  3. これは、Visual Studioのデザイナーによって実際にサポートされていません。

使用するのに優れた、それでも簡単に保守可能な設計はありますか?または、フォームの継承が依然として最善のアプローチですか?

更新 MVCからMVPに、オブザーバーパターンからイベントパターンに移動しました。ここに私が今考えているものがあります、批評してください:

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

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

質問:

どのレイヤーが最後に押されたボタンのようなものを保存する必要があるので、フォーム間でユーザーのために強調表示し続けることができます(CSSメニューのように)?

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


グローバル変数を使用する必要がある理由はわかりませんが、もしそうなら、確かにより良いアプローチが必要です。継承の代わりに構成と組み合わされた共通コンポーネントを作成する工場かもしれませんか?
stijn

デザイン全体に欠陥があります。実行したいことを既に理解している場合、Visual Studioではサポートされていません。
ラムハウンド

1
@Ramhound Visual Studioによってサポートされていますが、うまくありません。これは、Microsoftが行うように指示する方法です。A. msdn.microsoft.com/en-us/library/aa983613(v=vs.71).aspxの痛みであることがわかりました。とにかく、より良いアイデアがあれば、私はすべての目です。
ジョナサンヘンソン

@stijn各ベースフォームに、制御状態を別のインスタンスにロードするメソッドがあると思います。すなわち、LoadStatesToNewInstance(BaseFormインスタンス)。その基本型の新しいフォームを表示したいときにいつでも呼び出すことができます。form_I_am_about_to_hide.LoadStatesToNewInstance(this);
ジョナサンヘンソン

私は.net開発者ではありませんが、ポイント1で拡張できますか?私は解決策があるかもしれません
イムランオマールブフシュ

回答:


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

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

  3. Visual Studioが頭痛の種になります。フォームまたはユーザーコントロールの継承を検討する場合は、フォームデザイナーのイライラする癖と制限を考慮して、レスリングの潜在的な(および可能性のある)コストと利益を慎重に比較検討する必要があります。フォームの継承を最小限に抑えることをお勧めします-ペイオフが高い場合にのみ使用してください。サブクラス化の代替として、共通の「ベース」フォームを作成し、「子」になるごとに一度インスタンス化してから、オンザフライでカスタマイズできることに留意してください。これは、フォームの各バージョン間の違いが共有の側面と比較して小さい場合に意味があります。(IOW:複雑な基本フォーム、少しだけ複雑な子フォーム)

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

私が提供できる最も重要なアドバイスは、現在何らかの形式のビュー/コントローラーパターンを使用していない場合、そうすることを強くお勧めすることです。ルーズクーピングとレイヤー分離の利点を学び、評価する必要があります。

アップデートへの対応

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

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

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");
            }
        }
    }
}

あなたの思慮深い答えをありがとう。ビュー/コントローラーパターンへの適切なリファレンスを知っていますか?
ジョナサンヘンソン

1
私はこれがだと思っ覚えていない、非常に良いイントロ:codebetter.com/jeremymiller/2007/05/22/...
Igby Largeman

私のアップデートをご覧ください。
ジョナサンヘンソン

@JonathanHenson:回答が更新されました。
イグビーラージマン

5

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

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

MVVMパターンを使用しているため、可能な限り多くのプログラムロジックをViewModel(プレゼンテーション/ナビゲーションモデルを含む)またはモデル(コードがユーザーインターフェイスに関連しているかどうかに応じて)に入れますが、同じViewModel WinFormsビューとWPFビューの両方で使用される場合、ViewModelには、WinFormsまたはWPF用に設計されたコード、またはUIと直接対話するコードを含めることはできません。そのようなコードはビューに入れなければなりません。

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

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

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

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


3

あなたがすでに答えを受け入れたとしても、私はこれに答えるつもりです。

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

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

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

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

これまでのところ順調に機能しています。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.