🚀The world's best VBA AI has evolved. ExcelMaster is now an autonomous Agent.Read more →
Back to Blog

Excel VBA の Controls コレクション — For Each でのループと、名前によるコントロール参照

|

Excel VBA の Controls コレクション — For Each でのループと、名前によるコントロール参照

TL;DR — UserForm 上のすべてのコントロールは一箇所に住んでいます。フォームの Controls コレクションです。 Me.Controls として到達し、For Each でループし、どのコントロールにも 名前を文字列として 指定してアクセス できます — Me.Controls("txtName")。このコレクションのおかげで、二十項目のフォームも短く書けます。二十行の名 前付き参照の代わりに一つのループで済むのです。コレクションにはラベルや Frame、ボタンも入っているので、目当て のコントロールだけに TypeName や TypeOf で絞り込みましょう。

Private Sub cmdClear_Click()
    Dim ctl As MSForms.Control
    ' フォーム上のすべてのテキストボックスを一つのループで空にする:
    For Each ctl In Me.Controls
        If TypeOf ctl Is MSForms.TextBox Then ctl.Value = ""
    Next ctl
End Sub

一つのコントロールには、名前をそのまま書いて到達します — txtName.Value、lstItems.Clear。これはコードを書く 時点でそのコントロールがわかっている場合の話です。Controls コレクションが要るのは、わからない場合 — すべて の テキストボックスが欲しい、あるいは実行時に名前を組み立てるコントロールが欲しい、というときです。ここは UserForm コントロール一家のハブであり、ここにある三つの記事でその一家が完成します。Controls コレクションはコ ントロールをまとめて扱う方法、TextBox は常に String を返すので入力された数値も変換する まで文字列のまま、そして Frame はオプションボタンの一組にそれぞれのグループを与えるコン テナーです。コレクション、文字列、コンテナーを押さえれば、個々のコントロール — ListBox、ComboBox、CheckBox — もす べて同じ読み方をします。コントロールとは、フォームを通して到達し、プロパティを通して話しかけるオブジェクトなの です。

この記事で学べること

  • Me.Controls とは何か、そしてフォームが画面ではなくコレクションである理由
  • For Each ですべてのコントロールをループし、目当てのものだけに作用させる方法
  • TypeName と TypeOf で型を絞り込み、ラベルや Frame を読み飛ばす方法
  • データから組み立てたフォームのために、名前を文字列としてコントロールに到達する方法
  • フォームの実行中にコントロールを追加する方法
  • 判断のしどころ:ループが二十個の名前付き参照に勝るのはいつか

考え方の軸:フォームはコントロールオブジェクトのコレクション

UserForm は画面のように見えますが、コードから見れば コレクション です。Me.Controls がそのコレクションで す — フォームに置いたすべてのテキストボックス、ラベル、ボタン、Frame、リストボックスが、数えたりループしたり できるオブジェクトとして入っています。Me.Controls.Count でいくつあるかがわかり、Me.Controls(0) が最初の一 つです(コレクションは 0 始まりです)。Me はコードが走っているフォーム自身を指すので、UserForm 自身のモジュー ルの中では Me.Controls と Controls は同じ意味になります。

MsgBox "This form has " & Me.Controls.Count & " controls"

フォームをコレクションとして見えるようになると、フォームコードの繰り返し部分は消えていきます。txt1、txt2、 txt3 と書く代わりに、ループを書くようになるのです。

For Each ですべてのコントロールをループする

For Each はコレクションを一つずつ歩きます。コントロール変数の型は MSForms.Control — すべてのコントロールが 共有する汎用の型です。

Dim ctl As MSForms.Control
For Each ctl In Me.Controls
    Debug.Print ctl.Name
Next ctl

これが「フォームをクリアする」「すべての値を集める」「作業中は全部を無効にする」の裏にあるパターンです。しかし コレクションには すべての コントロールが入っています — 意図していないものも含めて — ですから ctl.Value を書 くだけの素朴なループは、最初のラベルでクラッシュします。ラベルには Value がないからです。それが次の問題です。

TypeName と TypeOf で型を絞り込む

すべてのコントロールに欲しいプロパティがあるとは限りません。Label に .Value はなく、CommandButton に .Text はありません。間違ったものに触れると実行時エラー 438、Object doesn't support this property or method になります。ですから先に絞り込みましょう。TypeName(ctl) は型を文字列として返し、TypeOf ctl Is は直 接それを調べます。

For Each ctl In Me.Controls
    If TypeOf ctl Is MSForms.TextBox Then
        ctl.Value = ""                 ' 安全:この行に到達するのはテキストボックスだけ
    End If
Next ctl

If の中では TypeOf ... Is を使いましょう — それが定石の調べ方です。型名をメッセージに出したい、あるいは複数 の型にわたる Select Case を書きたいときは TypeName(ctl) に手を伸ばします。ルールはこうです:Controls に対 する For Each の中でプロパティを呼ぶ前に、そのコントロールがそれを持つ型だと先に証明すること。

名前でコントロールに到達する

ここでコレクションが報われます。名前の文字列 でインデックスでき、しかもその文字列は実行時に組み立てられま す。

Dim i As Long
For i = 1 To 5
    Me.Controls("txtDay" & i).Value = ""   ' txtDay1 ... txtDay5
Next i

txtDay1 から txtDay5 まで名前を付けたフィールドを持つフォームには、五行ではなく一つのループで済みます。これ がデータ駆動のフォームの仕組みです — 名前は規則に従い、コードがそれを組み立てます。落とし穴は、リフレクション一 家にある Application.Run や CallByName の罠と同じです — 名前は 文字列 なので、コンパイラはチェックできま せん。"txtDya1" のような打ち間違いは赤い波線にはならず、その行が実行されたときの実行時エラーになります。名前 はわかっているパターンから組み立て、どこかがデータ由来なら On Error でガードしまし ょう。

フォームの実行中にコントロールを追加する

コレクションは固定されていません。Controls.Add はフォームが開いている間にコントロールを作成します。これが、 データに応じてサイズが決まるフォーム — レコードごとに一行のフィールド — を組み立てる方法です。

Dim box As MSForms.TextBox
Set box = Me.Controls.Add("Forms.TextBox.1", "txtDynamic", True)
box.Top = 10: box.Left = 10: box.Width = 120

最初の引数はコントロールのクラス、二番目はその名前、三番目は表示するかどうかです。動的に追加したコントロール には、WithEvents クラスで配線しない限りコードビハインドのイベントはありません — 実在するテクニックですが、固 定レイアウトでは本当にどうにもならないときだけ手を伸ばすべきものです。

判断のしどころ:ループが二十個の名前付き参照に勝るのはいつか

Controls コレクションは強力で、だからこそ使いすぎやすいのです。正直なルールはこうです。名前がわかっているコ ントロールが数個なら、txtName.Value のほうが Me.Controls("txtName").Value より明快で、コンパイラもチェッ クしてくれます。ループが場を得るのは、コントロールが 数多く、しかも均一 なとき — 二十個の日付フィールド、グ リッド状の入力欄、フォーム上のすべてのテキストボックス — か、名前が本当にデータで決まるときです。三個のコント ロールをループするのは見せびらかしですが、二十個をループするのはそれが唯一まともな書き方です。実際に繰り返し を取り除くときだけコレクションを選び、それができると証明するためだけには選ばないでください。

ExcelMaster の活用

ここでの失敗は静かで、遅れてやってきます。忘れていた一つのラベルでクラッシュする For Each、もう一致しなくな ったデータから組み立てたコントロール名、間違った型に対して呼ばれたプロパティ。そのどれもが、完成したように見え たフォームの実行時に、同じエラー 438 や「見つかりません」として表面化します。

ExcelMaster には、フォームの振る舞いを 説明するだけで構いません — 「すべてのテキストボックスをクリアする」「すべての入力を一行に読み込む」「一覧から 五つの日付フィールドを組み立てる」 — すると型フィルターと名前ガードをすでに組み込んだ Controls ループを書い てくれます。フォームとコードはあなたの手元に残り、実行時のバグ探しをせずに済みます。

よくある質問

VBA の Controls コレクションとは何ですか?

UserForm 上のすべてのコントロールのコレクションで、Me.Controls として到達します。各項目はコントロールオブジ ェクトです — テキストボックス、ラベル、ボタン、Frame、リストボックスなど。.Count で数え、For Each でルー プし、インデックスまたは名前の文字列で一つに到達できます。一つずつ名前を挙げる代わりに、多くのコントロールへ一 度に作用させる標準的な方法です。

UserForm 上のすべてのコントロールをループするには?

変数を As MSForms.Control で宣言し、For Each ctl In Me.Controls を使います。ループの中では、プロパティに 触れる前に TypeOf ctl Is MSForms.TextBox で型を調べましょう。コレクションにはそのプロパティを持たず、エラー 438 を発生させるラベルやボタンも入っているからです。

文字列に入っている名前でコントロールを参照するには?

その文字列でコレクションをインデックスします:Me.Controls("txtName")。名前は Me.Controls("txtDay" & i) の ように実行時に組み立てることもできます。名前は文字列なので、打ち間違いはその行が実行されるまで捕まりません — 「見つかりません」というエラーになります — ですから、わかっているパターンから名前を組み立て、ガードしましょう。

Controls に対する For Each ループがエラー 438 を発生させるのはなぜですか?

今のコントロールが持っていないプロパティを呼んだからです — もっとも多いのは Label、Frame、CommandButton に対する .Value や .Text です。Controls コレクションにはすべての型が入っているので、そのプロパティを読み 書きする前に TypeOf ctl Is MSForms.TextBox(または目当ての型)で絞り込みましょう。

フォームの実行中にコントロールを追加できますか?

できます。Me.Controls.Add("Forms.TextBox.1", "txtNew", True) が実行時に一つ作成します。これが、データに合わ せたサイズのフォームを組み立てる方法です。そのあとで Top、Left、Width を設定します。動的に追加したコント ロールには、WithEvents クラスでつながない限りイベントハンドラーがありません。

検証環境

検証環境: Excel 365 (Windows 11)、VBA 7.1 — 最終確認 2026-09-28。

関連ガイド: VBA TextBox · VBA Frame · VBA UserForm · VBA ListBox · VBA ComboBox · VBA CheckBox · VBA On Error