【VB】フォームをコンポーネントのように扱う【.Net】

Windows Formアプリケーションで、複数の画面で同じように使う機能(外見的な部分というよりは内部ロジック)がある場合、機能をまとめたフォームをコンポーネントとして機能を使い回すと便利です。
この記事ではそのやり方をまとめます。サンプルプロジェクトはGitHubをご覧ください。
github.com

メリット/デメリット

メリット

これをやる最大のメリットは、MVCで言うところのモデル部のコードが共通化でき、作成/保守が容易になる点です。
クラスライブラリとして分割すればプロジェクト毎に使い回すことも可能です。
VisibleやEnabledといったパラメータをいじることでまとめて有効/無効化できる点も、場合によっては便利です。

デメリット

フォームをコンポーネントとして扱う場合、それをデザイナーで表示することができないため、位置などの調整が面倒になります。
Designerを弄ることでデザイナーにも表示可能でした。後で記事を書き直します。
デザイン用のプロジェクトを用意してそこで作成し、実際の作成時にやるのが一番手っ取り早いでしょうか。
Formを継承するクラスは抽象クラス化出来ないのも地味に面倒だったりします。

やること

デザイン的な部分でやることは大きく2つです。

  1. コンポーネントとして扱うフォームを準備する
  2. 呼び出し側のフォームを準備する
  3. (ベースとして作ったフォームを継承し、使い回す)
コンポーネントとして扱うフォームを準備する

まずFormBorderStyleをNoneに設定します。これによってFormの枠が消えます。
f:id:wrongwrongwrongwrong163377:20180816020350p:plain
次にコンポーネントのデザインを設定します。今回は背景色を変えてボタン1つのみ置いた状態としました。
f:id:wrongwrongwrongwrong163377:20180816020234p:plain
最後にコンストラクタに必要な内容を記述します。重要なのはWith句内と、その後に続くparent.Controls.Add(Me)です。

Public Class CompornentBase
    Public Sub New(ByRef parent As Form, ByVal location As Point, Optional ByVal txt As String = "loaded")
        ' この呼び出しはデザイナーで必要です。
        InitializeComponent()
        ' InitializeComponent() 呼び出しの後で初期化を追加します。

        '別のフォームに設定するための準備
        With Me
            .TopLevel = False
            .Visible = True
            .Location = location
        End With

        parent.Controls.Add(Me) '親コンポーネントに自分を加える

        Button1.Text = txt
    End Sub
End Class
呼び出し側のフォームを準備する

テキトーにフォームのデザインを用意します。
f:id:wrongwrongwrongwrong163377:20180816020805p:plain
これのコードで、以下のようにコンポーネントを呼び出します。

Public Class Parent
    Private Sub Parent_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        Dim compo1 = New CompornentBase(Me, New Point(0, 0))
        Dim compo2 = New CompornentBase(Me, New Point(0, 200), "compo2")
    End Sub
End Class

実行すると、以下のようになります。
f:id:wrongwrongwrongwrong163377:20180816021128p:plain
フォームをコンポーネントのように扱うためにやることとしては以上です。
冒頭で述べたとおり、これは本来外見ではなくロジックの部分を使い回すためのハックなので、実際は「コンポーネントとして扱うフォームを準備する」部分のコードにロジックを書き、それを継承したフォームでデザインを書くというのが正しいです。

(ベースとして作ったフォームを継承し、使い回す)

この部分は本来フォームをコンポーネントのように扱うことの本質的な内容ですが、例を作りきれなかったので時間があればやります。
一応、自分がやる上で工夫していたやり方は以下の2点です。

  • (ボタンなど)イベントで受け取れるsenderをDictionaryの引数にしたり、senderのタグに必要な情報を持たせるなどする
  • ボタンは配列として扱い、AddHandlerなどを用いて挙動の設定を行う
    • 設定する挙動の部分は関数として継承先が実装する形にするのもよい