VBA ユーザーフォームをオブジェクト化するメリット

VBA

今回はユーザーフォームをオブジェクトとして扱うメリットについて、具体例を用いて紹介します。

まず、「ユーザーフォームをオブジェクトとして扱う」というのは、ユーザーフォームを変数に格納して扱うことを指します。これを本記事ではユーザーフォームを「オブジェクト化する」と表現することにします。

小規模なシステムや個人しか使わない場合など、オブジェクト化の必要を感じないケースはありますが、ユーザーフォーム標準モジュール間値を渡したい場合にどうしてもグローバル(Public)変数を使うことになってくるので、バグの温床となりがちです。

ここで紹介する方法を使うことで、可読性が向上し保守もしやすくなるため、ある程度実用的な機能を持たせたい場合には、ぜひ活用してみてほしいと思います。

①今回の題材

下記のようなユーザーフォームを作ります。コンボボックス2個、ラベル1個、ボタン1個を使ったシンプルな構成にしています。

実現する機能は下記のとおりです。

  1. カテゴリーを選択すると項目のリストが更新される
  2. 選択された項目が下のラベルに表示される
  3. 決定ボタンをクリックするかフォームを閉じるとラベルの値を返す

上記のカテゴリーと項目の組み合わせを複数用意し、連続して項目を選択させて、その結果を結合して、最後にメッセージボックスに表示するようにします。

複数の組み合わせですが、多いと見づらいので今回は下記の2パターンのみにします。

例えば、1回目のフォームで表1を扱い、野菜カテゴリーを選び、項目としてピーマンを選んで決定し、2回目のフォームで表2を扱い、文房具カテゴリーを選び、項目としてノートを選んで決定すると、結果として「ピーマン and ノート」のようにメッセージボックスを表示するという流れです。

これを普通に実装しようとすると下記の2パターンが主な方法になるかと思います。

  1. 同じ形のユーザーフォームを2つ用意して、それぞれのユーザーフォーム内で各表を取得して、選択結果はグローバル変数に格納
  2. 単一のユーザーフォームにグローバル変数で各表の範囲を渡し、選択結果もグローバル変数に格納

Aパターンは表が2つの場合にはそこまで大きな問題はないように見えますが、3つ以上になると保守が大変になりそうです。

Bパターンはコードも短く実装は楽ですが、グローバル変数を複数用意することになるのと、どこでどのように使われるかがわかりにくくなり、可読性が低下します。

次章ではまず、上記のBパターンで一般的と思われる実装を見てみます。

②一般的な(と筆者が思う)実装

まずは、ユーザーフォームの実装から見ていきましょう。

②-1 ユーザーフォーム側~項目コンボボックス~

カテゴリー選択が変更される度に、項目コンボボックスの値を更新する処理です。(パッと見で理解できる方は読み飛ばしてください)

graphRngは標準モジュール側でグローバル変数として定義され、表のセル範囲(Range)オブジェクトを格納します。ユーザーフォーム側から見るとブラックボックスとなります。
表1を例にすると野菜カテゴリーは2列目なので、ListIndexとしては1(果物が0)となります。この列を固定して行数分ループさせてコンボボックスにAddItemしているという処理です。

②-2 ユーザーフォーム側~固有イベント~

下記はユーザーフォーム固有のClickイベントやChangeイベントなどの処理です。今回はユーザーフォームが閉じられるか決定ボタンをクリックすることで値を確定する仕様なのでQueryCloseイベントを使っています。

確定した値は、標準モジュール側でグローバル変数として定義しているoutputMsgに追記されます。

②-3 ユーザーフォーム側~コンボボックス初期化~

下記はユーザーフォームを表示する直前に実行される固有の初期化処理です。2つのコンボボックスのアイテムを設定するのみです。

②-4 標準モジュール側

次に標準モジュール側の実装を見てみましょう。ここが重要なポイントになります。

グローバル変数が2つ定義されています。

処理内容はとてもシンプルなのですが、表1表2の違いはformProcessプロシージャの引数におけるRangeプロパティに与える文字列(GRAPH12か)のみとなるため、コメントが無いと何をしているのか判別が難しくなります。

また、ここだけ見るとoutputMsgはandという文字列を追記しただけで出力しているように見えるので、標準モジュールからだとoutputMsgがどこで更新されるか予測が難しそうです。graphRngについても同様で、これだけではどこで使われている変数なのか読み取れません。

とは言え、この規模なら少し検索をかければ解読は難しくないので、まだ弊害は少ないように感じます。

ただし、規模が大きくなってくるといかがでしょうか?
ここでは少し機能を追加してみることにしましょう。

②-5 機能追加した場合

さらに値段を入力させ、それぞれの価格と合計金額もメッセージボックスで出力することにしてみます。(値段入力部分の実装の説明は冗長なので省略)

まず、グローバル変数が増えます。さらにユーザーフォームのブラックボックス感が増した感じになりました。

このまま規模が大きくなるとグローバル変数も増え続け、どこで値が更新されるのか覚えておくのも困難になっていきます。これに設計者以外の人が機能追加する必要が出てきたときには、もう全部作り直した方が早い、なんてことにもなり兼ねません。

そこで、こんな事態に陥らないために、次はユーザーフォームをオブジェクト化した場合について見ていきます。

③ユーザーフォームをオブジェクト化する

③-1 標準モジュール側

今度は標準モジュール側の実装から見ていきましょう。

さっそく先頭のグローバル変数がなくなっています。

まず、mainProcessですが、コメントがなかったとしても、fm1を使っているのは表1fm2表2だということが読み取れると思います。また、outputMsgにもfmのreturn値が追記されているのがプロパティ名から読み取れるようになっています。

加えて、mainProcessは出力値を操作する役割で、formProcessでユーザーフォームを操作していることも明確になったと思います。

graphRngも②ではグローバル変数だったため、どこで使われているかわかりづらかったと思いますが、ここではfmオブジェクトのプロパティということが明示されているのでユーザーフォーム内で使うということがわかります。

次に初期化した後ユーザーフォームを表示します。ここでユーザー入力を受け付け、決定ボタンをクリックしてフォームを閉じてもらうのですが1点注意が必要です。この時点でUnloadしてしまうとユーザーフォームオブジェクトが解放され、格納した値が取得できなくなってしまいます。これを回避するために、この時点では閉じずにHideメソッドで隠しておきます。値を取り出した後は忘れずに明示的にUnloadしオブジェクト解放しておきましょう。

このようにコード自体は少し長くなりますが、可読性は高まります。
また、次章のようにPropertyとして管理することで、自動メンバー表示機能が有効となるため、コーディングしやすくなります。
※ドット(.)を入力すると出てくる下の画像のようなやつです

③-2 ユーザーフォーム~Propertyの取得と設定~

ここからはユーザーフォーム側の実装を見ていきます。

これ、実は私も最近まで知らなかったのですが、ユーザーフォーム内でもクラスモジュールのようにPropertyの取得と設定ができるんです。今回、returnMsgとgraphRngはこれを使っています。
今回の場合は、2つとも一度しか値を受け取らないので、初回のみ値の更新を受け付けるようにしています。(詳しい説明は割愛します)

③-3 ユーザーフォーム~コンボボックス初期化と更新~

次にユーザーフォームの初期化と更新部分です。setContentComboBoxは②と同じで、initSampleFormはUserForm_Initializeの代わりですね。

③-4 ユーザーフォーム~固有イベント~

最後にユーザーフォーム固有イベントの処理です。②からの変化点は下記3点です。

  • UnloadではなくHideメソッドを使用
  • QueryCloseで閉じらないようにCancelを1に設定
  • グローバル変数outputMsgの代わりにreturnMsgプロパティを使用

③-5 機能追加した場合

②と同様の機能追加してみます。

増えた変数を管理するために構造体(Type)を追加しています。

ユーザーフォームから値を取得する部分出力結果をまとめる部分でプロシージャを分けることでmainProcessの役割がわかりやすくなるようにしています。

細かい処理内容の説明はこれまでと大きく変わらないので割愛します。

このように、オブジェクト(変数)として扱うことで、表1と表2どちらを扱っているかを明示しやすくなり、ユーザーフォームと標準モジュール間での値の受け渡しがどこで実施されているか読み取りやすくなりました。

多少下準備が必要なため、少しコードが長くなってしまうことと、慣れていないとコーディングが難しく感じる部分もあるかもしれませんが、総合的に見ると③の方がメリットが大きいと思いますので、ぜひご活用いただけたらと思います。

④おわりに

最後に今回のまとめです。

  • 小規模なシステムの場合は、しなくても特にユーザーフォームのオブジェクト化をしなくても不都合はなく、コードも短くなる
  • ユーザーフォームをオブジェクト化することで、何をしたいのか、何を受け渡ししているのかを明確にできるため可読性が向上する
  • ユーザーフォームをオブジェクト化することで、グローバル変数を削減できるため、バグの少ない信頼性の高いコードを書くことにつながる。

以上、長くなりましたが、最後まで読んでいただき、ありがとうございました。

前の記事

コメント