ユーザーフォームへのコントロールの動的配置

コンボボックスを動的に追加する

フォームにコンボボックスを追加する

コンボボックスやリストボックスは、ユーザからの入力を受け付けるインターフェースとしては便利なんだが、チマチマ作ることを思うと結構メンドクサイ。

コンボボックスの追加がラクにできたらいいなあ、と思い、ちょっと実験してみた。

まずは、フォームを挿入する。今回はオブジェクト名を「FormTest02」としてある。

リスト1 フォームモジュール
Private srcRange_() As Range    '……(*)'
Private cmbBox_() As MSForms.ComboBox
Private numOfCombos_ As Integer

Public Sub createComboBox( _
             ByVal posTop As Double, _
             ByVal posLeft As Double, _
             ByVal meHight As Double, _
             ByVal meWidth As Double, _
             ByVal srcRange As Range)    '……(1)'
  numOfCombos_ = numOfCombos_ + 1    '……(2)'
  ReDim Preserve srcRange_(numOfCombos_ - 1)
  Set srcRange_(numOfCombos_ - 1) = srcRange
  ReDim Preserve cmbBox_(numOfCombos_ - 1)    '……(3)'
  Set cmbBox_(numOfCombos_ - 1) = _
      Controls.Add("Forms.ComboBox.1", "cmbBox" & numOfCombos_, True)    '……(4)'
  With cmbBox_(numOfCombos_ - 1)    '……(5)'
    .top = posTop    '……(6)'
    .left = posLeft
    .height = meHight
    .width = meWidth
    Dim i As Integer
    For i = 0 To srcRange_(numOfCombos_ - 1).Rows.Count - 1    '……(7)'
      .AddItem srcRange.Cells(i + 1, 1).Value
    Next
  End With
End Sub

まずは、宣言セクションの(*)からの3行

Private srcRange_() As Range
Private cmbBox_() As MSForms.ComboBox
Private numOfCombos_ As Integer

3つのPrivate変数を宣言。

srcRange_は、コンボボックスのデータソースになるセル範囲を入れておく変数。複数になることを想定して配列にしている。

cmbBox_()は、動的に生成したコンボボックスを格納しておく変数。これまた複数になることを想定して配列にしている。

numOfCombos_は生成されたコンボボックスの数を記録するための変数。

(1)では、コンボボックスを生成するためのcreateComboBoxメソッドを定義。

とりあえず5つの引数を受け取ってコンボボックスを作成することにする。

それぞれの引数の役割は、以下の通り。

  • ・posTopはフォーム上のタテ位置。
  • ・posLeftはフォーム上のヨコ位置。
  • ・meHeightはコンボボックスの高さ。
  • ・meWidthはコンボボックスの幅。
  • ・srcRangeはデータソースになるセル範囲。

実際にはもっと細かく指定しないと使い物にならないけれど、今回はとりあえずの実験なのでこのぐらいで勘弁してほしい。

(2)の

numOfCombos_ = numOfCombos_ + 1

では、このメソッドが呼び出されるごとにnumOfCombos_をインクリメント。こうすることで、生成されたコンボボックスを番号で指定できるようにする。

(3)の

ReDim Preserve cmbBox_(numOfCombos_ - 1)

で、生成したコンボボックスを格納するための配列変数をReDimする。前に格納していたコンボボックスが消えないように、Preserveしている。

(4)の

Set cmbBox_(numOfCombos_ - 1) = _
      Controls.Add("Forms.ComboBox.1", "cmbBox" & numOfCombos_, True)

では、ControlsコレクションのAddメソッドを用いて、配列変数cmbBox_()に新たに生成したコンボボックスを格納。

Newできたら分かりやすいのになあ。

(5)からは、

With cmbBox_(numOfCombos_ - 1)

このように、「cmbBox_(numOfCombos_ - 1)」をWithで括ることによって、新たに追加したコンボボックスへの設定を行う。

ただし、(6)からの4行

.top = posTop
.left = posLeft
.height = meHight
.width = meWidth

なんかヘンだと思いませんか?

実は、インテリセンスが働かず、

f:id:akashi_keirin:20171009182628j:plain

このように入力候補にも出てこないのです。

んで、改行しても頭文字が小文字のまま。メチャクチャ不安になりますな。

あとは、(7)からの3行

For i = 0 To srcRange_(numOfCombos_ - 1).Rows.Count - 1
  .AddItem srcRange.Cells(i + 1, 1).Value
Next

AddItemメソッドでドロップダウンリストにsrcRangeからデータを追加している。

ホントは、Listプロパティを設定して複数列リストに対応しないといけないんだけれど、今回は実験ということで1列で勘弁してほしい。

実行

標準モジュールに次のコードを書いて実行する。

スト2 標準モジュール
Public Sub exhibition()
  Dim frm As TestForm02    '……(1)'
  Set frm = New TestForm02
  With frm
    .createComboBox 10, 30, 20, 80, ActiveSheet.Range("B2:B7")    '……(2)'
    .createComboBox 10, 130, 20, 80, ActiveSheet.Range("C2:C7")
    .Show    '……(3)'
  End With
End Sub

まず、(1)からの2行

Dim frm As TestForm02
Set frm = New TestForm02

TestForm02型の変数frmにTestForm02のインスタンスを格納。

(2)からの2行は、WithでまとめているのでいづれもTestForm02のインスタンスに対する処理。

.createComboBox 10, 30, 20, 80, ActiveSheet.Range("B2:B7")
.createComboBox 10, 130, 20, 80, ActiveSheet.Range("C2:C7")

createComboBoxメソッドを、引数を変えて2回呼び出している。

で、(3)の

.Show

でフォームを表示しておしまい。

実行結果

f:id:akashi_keirin:20171009182641j:plain

このように2つのコンボボックスが設置されたフォームが表示され、

f:id:akashi_keirin:20171009182651j:plain

左側のコンボボックスにはB列のデータが、

f:id:akashi_keirin:20171009182704j:plain

右側のコンボボックスにはC列のデータが入っている。

おわりに

これだけではまるで使い道がないし、使い物になるコンボボックスにするためには設定すべきプロパティがメチャクチャたくさんあるので、普通にメソッドに引数を渡すような実行方法は現実的でないと思う。

たとえば、ワークシートにコンボボックスの細かい仕様を表す表を作成しておいて、そこから値を読み込んでプロパティを設定するというやり方になりそうだ。

あと、イベントを検知できなければ使い物にならないので、WithEventsキーワードの使い方についてもこれから研究していく必要がある。

けっこう面白いかも知れないなあ。

【参考】

akashi-keirin.hatenablog.com