2023/01/27

コンボボックスのテキストボックス部に複数列を表示




コンボボックスに複数列のデータをリスト化することは可能ですが、リストを選択した後のテキストボックス部には「指定した単列(指定しない場合は、既定の1列目)のみのデータ」が表示されます。
しかし、コンボボックスにフォーカスが無い時にも「複数列のデータ」が見えていて欲しい場合もありますので、今回は「コンボボックスのテキストボックス部に複数列のデータを表示」させる方法について紹介します。

1.概要

今回紹介する「テキストボックス部に複数列表示するコンボボックス」は、以下の2種類です。
隠しリスト方式リストの非表示列に表示列のデータを連結させて書き込み、テキストボックス部には非表示列を指定。
リストボックス上乗せ方式テキストボックス部にリストボックスを重ねて配置し、選択したデータをリストボックスに表示。

添付ファイル」では、標準のコンボボックス+上記2方式を確認できるように、図1のようなものを作成しました。
システム概要
図1


ワークシート上のボタンをクリックするとユーザーフォームが起動します。フォーム上には3つのコンボボックスがあります。
各コンボボックスのリストは、ワークシート「A1:B4」のセル範囲の2列データを元にしています。
コンボボックスは、左側から「標準①」「隠しリスト方式②」「リストボックス上乗せ方式③」となっています。リストを選択した後のテキストボックス部は、標準①は1列のみですが、②③は複数列(今回は2列)が表示されます。

また、左下の「選択クリア④」ボタンは、3つのコンボボックスを未選択状態にするものです。
なおフォームを閉じるためのボタンは設けていませんので、右上×印で閉じて下さい。

なお今回は「ユーザーフォーム上」にコンボボックスを配置しましたが、シート上に配置するコンボボックスでもほぼ同じコードで「複数列を表示」させることが出来ます。

2.システム補助関係

2ー1.ワークシート(Sheet1)

今回は図2のように、データをワークシート上に置き、またユーザーフォームを起動するボタンをシート上に配置しています。
ワークシート上の設定
図2


データの範囲は「A1~B4セル」としています。またフォームコントロールで作るボタンには、標準モジュールにある「UFstartプロシージャ」をマクロ登録しています。

2ー2.標準モジュール(Module1)

シート上のボタンから呼び出されるのが、図3の「UFstartプロシージャ」です。
  1. '========== ⇩(1) ユーザーフォームの起動 ============
  2. Sub UFstart()
  3.  UserForm1.Show
  4. End Sub
図3


02行目「UserForm1.Show」で、フォームを起動しています。パラメータ無しなので「モーダル」で起動されますが、「モードレス」でも問題ありません。

2ー3.ユーザーフォーム(UserForm1)

2ー3ー1.フォームレイアウト

図3の02行目から起動されるのが、UserForm1です。そのフォーム上のレイアウトは図4のようにしました。
フォーム上のコントロールのレイアウト
図4


フォーム上には、コンボボックスを3つ並べます。
また「リストボックス上乗せ方式」で使用するリストボックスを「適当な場所」「適当なサイズ」で配置します。適当でもOKなのは、マクロ側から「コンボボックスに丁度重なるように位置・サイズを調整」している為です。

最後に「コンボボックスを未選択状態」にする為のボタンを左下に配置します。またコンボボックスの上側には、方式を表すLabelを配置します。ボタンとLabelの文字列は、コントロール配置時にCaptionプロパティを手動で変更しています。

2ー3ー2.フォームモジュール(メイン)

ユーザーフォームの起動時・表示時に呼び出されるのが図5の「Initializeイベント」→「Activateイベント」です。
  1. '========== ⇩(2) フォーム起動時設定 ============
  2. Private Sub UserForm_Initialize()
  3.  Call CB1_Ini
  4.  Call CB2_Ini
  5.  Call CB3_Ini
  6. End Sub
  7. '========== ⇩(3) フォーム表示時設定 ============
  8. Private Sub UserForm_Activate()
  9.  Call CB1_Act
  10.  Call CB2_Act
  11.  Call CB3_Act
  12. End Sub
図5


本来は「Initializeイベント」及び「Activateイベント」の中で「コンボボックス等の設定」を行います。しかし今回は「3つのコンボボックスを比較」するのが目的ですので、Initialize内・Activate内では各々のコンボボックスの設定(CB〇〇_Iniプロシージャ、CB〇〇_Actプロシージャ)を呼び出して実行させています。
CB〇〇_Iniプロシージャ、CB〇〇_Actプロシージャについては、各方式の中で説明します。

「選択クリア」ボタンをクリックした時に呼び出されるのが図6です。
  1. '========== ⇩(4) 「選択クリア」ボタンの動作 ============
  2. Private Sub CommandButton1_Click()
  3.  Me.ComboBox1.ListIndex = -1
  4.  Me.ComboBox2.ListIndex = -1
  5.  Me.ComboBox3.ListIndex = -1
  6. End Sub
図6


32行目「Me.ComboBox1.ListIndex = -1」は、「標準」のコンボボックスを未選択状態にしています。
同様に33行目は「隠しリスト方式」を、34行目は「リストボックス上乗せ方式」を未選択状態にしています。

3.「標準」コンボボックス

3ー1.設定(フォームモジュール)

図5のInitializeイベント内の12行目から呼び出されるのが図7です。
  1. '========== ⇩(5) フォーム起動時処理 ============
  2. Private Sub CB1_Ini()
  3.  Me.ComboBox1.ColumnCount = 2
  4.  Me.ComboBox1.ColumnWidths = "50;50"
  5.  Me.ComboBox1.TextColumn = 1
  6. End Sub
図7


今回は2列のデータをリストにしますので、42行目「Me.ComboBox1.ColumnCount = 2」で、コンボボックスのリストを2列仕様にします。
43行目「Me.ComboBox1.ColumnWidths = "50;50"」では、各列の幅(ポイント数)を設定しています。フォーム上に配置したコンボボックスの幅(Width値)が120ポイントでしたので、「横スクロールバーを出さないリスト」に従って列幅を決めています。

44行目「Me.ComboBox1.TextColumn = 1」は、コンボボックスのテキストボックス部に表示する値の「列番号」を「第1列目」に指定しています。つまりワークシートのA列のデータが表示される事になります。
なお既定値は「1」ですので、省略してもOKです。

図5のActivateイベント内の19行目から呼び出されるのが図8です。
  1. '========== ⇩(6) フォーム表示時処理 ============
  2. Private Sub CB1_Act()
  3.  Dim Rng As Variant   '←セル範囲のデータを入れる配列
  4.  Rng = Sheets("sheet1").Range("A1:B4").Value
  5.  Me.ComboBox1.List = Rng
  6. End Sub
図8


54行目「Rng = Sheets("sheet1").Range("A1:B4").Value」では、ワークシートのA1~B4のセル範囲の値を取得し、配列の形で変数Rngに代入しています。
55行目「Me.ComboBox1.List = Rng」では、その配列の値をコンボボックスのリストにしています。

リスト化する別な方法として、図9のように「リストを1行作ったらデータを入れる」方式でもOKです。
  1. '========== ⇩(7) フォーム表示時処理2 ============
  2. Private Sub CB1_Act2()
  3.  Dim Rng As Variant   '←セル範囲のデータを入れる配列
  4.  Dim i As Long   '←リストの行数
  5.  Rng = Sheets("sheet1").Range("A1:B4").Value
  6.  For i = 1 To UBound(Rng, 1)
  7.   Me.ComboBox1.AddItem ""
  8.   Me.ComboBox1.List(i - 1, 0) = Rng(i, 1)
  9.   Me.ComboBox1.List(i - 1, 1) = Rng(i, 2)
  10.  Next i
  11. End Sub
図9


65行目は、図8の54行目と同じです。
67行目「For i = 1 To UBound(Rng, 1)」で、カウンタ変数iをデータの行数分だけ回します。
68行目「Me.ComboBox1.AddItem ""」で、新たな空のリスト行を作成します。
69行目「Me.ComboBox1.List(i - 1, 0) = Rng(i, 1)」で、新たに作成したリスト行の1列目(ゼロ始まりのため、ゼロ列)に、配列の1列目(配列データはセル範囲から取得している為、1始まり)のデータを書き込みます。
70行目「Me.ComboBox1.List(i - 1, 1) = Rng(i, 2)」も同様に、リストの2列目を作成しています。

また、コンボボックスの「RowSource」プロパティを使用した図10のような方法も考えられます。
  1. '========== ⇩(8) フォーム表示時処理3 ============
  2. Private Sub CB1_Act3()
  3.  Me.ComboBox1.RowSource = Sheets("sheet1").Range("A1:B4").Address(External:=True)
  4. End Sub
図10


82行目「Me.ComboBox1.RowSource = Sheets("sheet1").Range("A1:B4").Address(External:=True)」は、RowSourceプロパティに、データ元である「ワークシートのセル範囲」を設定しています。セル範囲はString型で指定しなければならないため、Addressプロパティで文字列にしています。
なお、シート名を指定するために「External:=True」とパラメータ指定をしていますが、アクティブなシートのセル範囲であればExternalパラメータは指定しなくてもOKです。

3ー2.動作結果

標準状態のコンボボックスでリスト選択した場合には、図11のようにテキストボックス部には「1つの列のデータのみ」が表示されることになります。
標準状態でコンボボックスのリストを選択した状態
図11


なおテキストボックス部の文字列が「青反転」している事からも分かるように、標準状態ではテキストボックス部に入力した値で「リスト検索」が可能です。但し検索する範囲は「TextColumnプロパティ」で設定した列(既定では1列目)のみになります。

4.「隠しリスト方式」コンボボックス

「隠しリスト方式」は、以下の様に設定します。
 ・リストの列数は、データの列数よりも+1多くする。
 ・その列は、列幅をゼロにする(=隠し列にする)。
 ・隠し列には、本来のデータ列の値を「文字列として連結したもの」を入れる。
 ・テキストボックス部に表示する列(TextColumn値)は、隠し列を指定する。
絵で表すと、図12のようになります。
隠しリスト方式の考え方
図12


4ー1.設定(フォームモジュール)

図5のInitializeイベント内の13行目から呼び出されるのが図13です。
  1. '========== ⇩(9) フォーム起動時処理 ============
  2. Private Sub CB2_Ini()
  3.  Me.ComboBox2.ColumnCount = 3
  4.  Me.ComboBox2.ColumnWidths = "50;50;0"
  5.  Me.ComboBox2.TextColumn = 3
  6. End Sub
図13


92行目「Me.ComboBox2.ColumnCount = 3」ではリスト列数を、データの列数より1つ多い「3列」に設定します。
93行目「Me.ComboBox2.ColumnWidths = "50;50;0"」では、3列目の列幅をゼロにします。
94行目「Me.ComboBox2.TextColumn = 3」では、テキストボックス部に表示する列を「3列目」に設定します。

図5のActivateイベント内の20行目から呼び出されるのが図14です。
  1. '========== ⇩(10) フォーム表示時処理 ============
  2. Private Sub CB2_Act()
  3.  Dim Rng As Variant   '←セル範囲のデータを入れる配列
  4.  Dim i As Long   '←リストの行数
  5.  Rng = Sheets("sheet1").Range("A1:B4").Value
  6.  ReDim Preserve Rng(1 To UBound(Rng, 1), 1 To UBound(Rng, 2) + 1)
  7.  For i = 1 To UBound(Rng, 1)
  8.   Rng(i, UBound(Rng, 2)) = Rng(i, 1) & Space(14) & Rng(i, 2)
  9.  Next i
  10.  Me.ComboBox2.List = Rng
  11. End Sub
図14


105行目「Rng = Sheets("sheet1").Range("A1:B4").Value」では、ワークシートのA1~B4のセル範囲の値を配列の形で取得します。
106行目「ReDim Preserve Rng(1 To UBound(Rng, 1), 1 To UBound(Rng, 2) + 1)」では、105行目で取得した配列Rng(今回は 4行×2列)を「列方向に1つ増やした配列(4行×3列)」に拡張しています。なお元のデータは消えないように「Preserveキーワード」を付けてReDimをしています。つまり「3列目が空の配列」を作ったことになります。

108~110行目では、配列の3列目のデータを作っています。
108行目「For i = 1 To UBound(Rng, 1)」では、カウンタ変数iをデータの行数分(UBound(Rng, 1))回します。
109行目「Rng(i, UBound(Rng, 2)) = Rng(i, 1) & Space(14) & Rng(i, 2)」では、「1列目のデータ」+「2列目のデータ」を連結し、その結果を配列の最終列(ここでは3列目)に書き込みます。

なお、「1列目のデータ」+「2列目のデータ」をそのままつなげると、例えば1行目は「1」+「A」なので「1A」となり「2列分の文字列」とは見えないので、間に「スペース」を適当に入れます。
今回はリストの列幅が50ポイントなので「スペースを13個」入れていますが、これは計算できるものでは無いのでTry & Errorで入れる数を調整しました。
尚このスペースを入れる方法だと、1列目の文字数やFont種類・Fontサイズなどで、入れる数量を調整する必要が出てきてしまいます。他には「TAB印」で区切る方法(右辺=Rng(i, 1) & vbTab & Rng(i, 2))もありますので、扱うデータにより試してみて下さい。

配列の最終列に連結データを入れたら、112行目「Me.ComboBox2.List = Rng」でコンボボックスのリストにします。

4ー2.動作結果

「隠しリスト方式」のコンボボックスでリスト選択した場合は、図15のようになります。
隠しリスト方式でコンボボックスのリストを選択した状態
図15


あたかもテキストボックス部には2列分のデータが並んでいるように見えますが、データは1つのみで「2列の値が結合された形」で表示されています。
1列目と2列目の文字列の間を一応スペースで空けて「リスト部と同じ間隔でデータが並んでいる」ように見せてはいますが、完全に揃えるのは難しいと思います。

またテキストボックス部ではリストの検索が可能ですが、その対象範囲は「TextColumnプロパティ」で設定した列(今回は隠し列)です。また検索は基本的に「前方一致」ですので、今回の場合「検索は1列目に対して実施」されることになります。

5.「リストボックス上乗せ方式」コンボボックス

「リストボックス上乗せ方式」は、図16のように「コンボボックスのテキストボックス部に重ねるようにリストボックスを配置」する方法です。
リストボックス上乗せ方式の考え方
図16


図16で分かるように、コンボボックス自体は標準のものです。コンボボックスのリストを選択すると、そのデータは「リストボックス」に転送されるようにしています。リストボックスは「テキストボックス部に重ねるように乗せている」ため、コンボボックスのテキストボックス部が「複数列表示」になったように見える という訳です。

5ー1.設定(フォームモジュール)

図5のInitializeイベント内の14行目から呼び出されるのが図17です。
  1. '========== ⇩(11) フォーム起動時処理 ============
  2. Private Sub CB3_Ini()
  3.  Me.ComboBox3.ColumnCount = 2
  4.  Me.ComboBox3.ColumnWidths = "50;50"
  5.  Me.ComboBox3.Style = fmStyleDropDownList
  6.  Me.ListBox1.ColumnCount = 2
  7.  Me.ListBox1.ColumnWidths = "50;35"
  8.  Me.ListBox1.Left = Me.ComboBox3.Left
  9.  Me.ListBox1.Top = Me.ComboBox3.Top
  10.  Me.ListBox1.Height = Me.ComboBox3.Height
  11.  Me.ListBox1.Width = Me.ComboBox3.Width - Me.ListBox1.Height
  12.  DoEvents: DoEvents
  13.  Me.ListBox1.Enabled = False
  14. End Sub
図17


122行目「Me.ComboBox3.ColumnCount = 2」では、コンボボックスのリストを2列仕様にします。
123行目「Me.ComboBox3.ColumnWidths = "50;50"」では、各列の幅(ポイント数)を設定しています。
124行目「Me.ComboBox3.Style = fmStyleDropDownList」では、コンボボックスのテキストボックス部を「入力不可」の状態にしています。
Styleプロパティには図18のような2種類の設定が可能です。
定数内容
fmStyleDropDownCombo0(既定値)テキストボックス部入力+リスト選択可
fmStyleDropDownList2テキストボックス部入力不可+リスト選択可
テキストボックス部をクリックするとリストが開く
図18


今回はコンボボックスの上にリストボックスを乗せているので、コンボボックスのテキストボックス部は「編集出来ない」状態です。また、リストボックスはもともと「編集不可」です。
通常のコンボボックスのテキストボックス部をクリックすると、Styleプロパティの設定により「編集可」または「編集不可→リストがドロップダウン」のどちらかの状態になるはずですが、既定値(=fmStyleDropDownCombo)のままだと、テキストボックス部(=リストボックス)をクリックしても「何も起こらない」状態になってしまいます。
ですので、今回のStyle設定(テキストボックス部の入力不可)にして、リストボックス部をクリックすると、リストがドロップダウンするようにしています。

126行目「Me.ListBox1.ColumnCount = 2」は、上に乗せるリストボックスを2列仕様にしています。
127行目「Me.ListBox1.ColumnWidths = "50;35"」は2列の列幅を設定しています。コンボボックス側(123行目)は「50ポイント、50ポイント」ですが、リストボックスは「50ポイント、35ポイント」と2列目を少し狭くしているのは、コンボボックス右端の下矢印ボタンの幅を引き算し、「リストボックスに横スクロールバーが出ない」ようにしているためです。

129~133行目で、コンボボックスの上にリストボックスの乗せています。
129行目「Me.ListBox1.Left = Me.ComboBox3.Left」で、リストボックスの左右位置をコンボボックスに合わせています。
130行目「Me.ListBox1.Top = Me.ComboBox3.Top」で、上下位置をコンボボックスに合わせています。
131行目「Me.ListBox1.Height = Me.ComboBox3.Height」で、高さをコンボボックスに合わせています。

132行目「Me.ListBox1.Width = Me.ComboBox3.Width - Me.ListBox1.Height」では、幅を合わせています。
但し、幅をコンボボックスと完全に同じサイズにしてしまうと「コンボボックスのドロップボタン」も隠れてしまうため、図19のように「ボタンの高さ(≒幅)」の分だけ引き算をしています。
リストボックスの幅の考え方
図19


寄り道
コントロールを重ねる場合、通常はフォーム上で「後から作成したコントロールが上に乗る」ことになります。今回はコンボボックスの上にリストボックスを乗せるので、リストボックスを後から作る必要がありそうですが、試してみるとコンボボックスを後から作っても「リストボックスが上に乗る」ことになります。
他のコントロールも含めて検証してみると、なぜか「ListBoxだけは、どのコントロールよりも上に来る」ことが分かりました。
(標準でツールボックスに表示される中での確認結果です。なおFrameコントロールだけは別で、ListBoxの上になりました)

ListBox以外のコントロールは、後から作ったものが上に乗るので、どうもListBoxだけが特殊のようです。もしListBoxでは無く他のコントロールを上に乗せる場合は、フォーム上で後から作成する必要があります。

133行目「DoEvents: DoEvents」は、「リストボックスのサイズ変更(特に高さと幅)をフォームに反映」させる為に入れています。コード的には、この様な配慮は不要なはずなのですが、なぜか高さと幅が反映されません(Left方向とTop方向はDoEventsが無くても揃えてくれます)。
なおDoEventsの代わりに「Me.Repaint」を実行させてもダメでした。原因は良く分かりませんが、Excelのバージョン等によっては無くてもOKかもしれません。

135行目「Me.ListBox1.Enabled = False」は、リストボックスを操作不可にしています。この処理が無いと、コンボボックスの項目選択後にリストボックスのリストが選択(項目が青反転する)できてしまい、「コンボボックスの動作とは明らかに違う」感じになってしまいます。またこの処理により、リストボックスをクリックすると(リストボックスが無視されて)コンボボックスのテキスト部をクリックした事になり、ドロップダウンリストが開き「コンボボックスらしい動き」となります。

図5のActivateイベント内の21行目から呼び出されるのが図20です。内容は標準のActivateイベント時(図8)と同じです。
  1. '========== ⇩(12) フォーム表示時処理 ============
  2. Private Sub CB3_Act()
  3.  Dim Rng As Variant   '←セル範囲のデータを入れる配列
  4.  Rng = Sheets("sheet1").Range("A1:B4").Value
  5.  Me.ComboBox3.List = Rng
  6. End Sub
図20


144行目「Rng = Sheets("sheet1").Range("A1:B4").Value」では、ワークシートのA1~B4のセル範囲の値を取得し、配列の形で変数Rngに代入しています。
145行目「Me.ComboBox3.List = Rng」では、その配列の値をコンボボックスのリストにしています。

コンボボックスのリストを選択した時に呼び出されるイベントプロシージャが図21です。また「選択クリア」ボタンをクリック(図6)した時、コンボボックスが選択状態だった場合にも呼び出されます。
内容的には、コンボボックスのリストの選択されたデータをリストボックスに書き込んでいます。
  1. '========== ⇩(13) リストボックスへのデータ書き込み ============
  2. Private Sub ComboBox3_Change()
  3.  Me.ListBox1.Clear
  4.  If Me.ComboBox3.ListIndex = -1 Then Exit Sub
  5.  Me.ListBox1.AddItem ""
  6.  Me.ListBox1.List(0, 0) = Me.ComboBox3.List(Me.ComboBox3.ListIndex, 0)
  7.  Me.ListBox1.List(0, 1) = Me.ComboBox3.List(Me.ComboBox3.ListIndex, 1)
  8. End Sub
図21


152行目「Me.ListBox1.Clear」では、リストボックスを空にしています。
154行目「If Me.ComboBox3.ListIndex = -1 Then Exit Sub」は、「選択クリア」ボタンをクリック(ListIndex = -1 を指示)された時は、ここで処理を終了します。リストボックスは空の状態で表示されます。

コンボボックスのリストを選択した際には、156~158行目を実行します。
156行目「Me.ListBox1.AddItem ""」では、リストボックスに新しいリスト行を追加します。
157行目「Me.ListBox1.List(0, 0) = Me.ComboBox3.List(Me.ComboBox3.ListIndex, 0)」では、選択した項目の1列目のデータをリストボックスの1列目に書き込み、158行目「Me.ListBox1.List(0, 1) = Me.ComboBox3.List(Me.ComboBox3.ListIndex, 1)」で、2列目のデータをリストボックスの2列目に書き込みます。

これにより、コンボボックスの選択されたリスト(青反転されている行)のデータが、コンボボックスのテキストボックス部の上に乗っているリストボックスで表示されることになります。

5ー2.動作結果

「リストボックス上乗せ方式」のコンボボックスでリスト選択した場合は、図22のようになります。
リストボックス上乗せ方式の動作結果
図22


リストを選択した場合は、テキストボックス部の上に重ねているリストボックスに「2つの列のデータ」が表示されます。
また、その重ねているリストボックスを(コンボボックスのテキストボックス部と思って)クリックすると、EnabledプロパティをFalseにしている為に、その下のコンボボックスのテキストボックス部が反応します。コンボボックスは、テキストボックス部への入力を不可状態にしていますので、リストがドロップダウンする という動作になります。
デメリットとしては、テキストボックス部でのリスト検索は出来ないことになります。

なお、リストボックスの幅を無理矢理コンボボックスに合わせていますので、リストボックスの右端がちょっとだけ見えてしまいます。しかし「コンボボックスのリスト」と「リストボックスのリスト」の各列幅を合わせることで、列揃えはピッタリと合っているように見えるのが、「隠しリスト方式」との違いです。

アプリ実例

共有資料の登録と閲覧ができるサーバーシステム

サンプルファイル

コンボボックスのテキストボックス部に複数列を表示(its-026.xlsm)
セキュリティ向上を目的として「インターネット経由でダウンロードしたOfficeファイル(Excel等)のマクロは、既定でブロック」されるようにOfficeアプリケーションの既定動作が変更になりました。(2022年4月より切替開始)
解除の方法については「ダウンロードファイルのブロック解除方法」を参照下さい。