Visual Basic 初級講座
VB.NET 2002 対応 VB.NET 2003 対応 VB2005 対応

 

Visual Basic 中学校 > 初級講座 >

第20回 コントロールをまとめる方法

複数のコントロールに同じように命令したりプロパティを設定する場合、1つ1つのコントロールに対する命令をいちいち書かなければいけないわけではありません。今回はコントロールをグループ化して一括して命令する方法を紹介します。

この回の要約

・Controlsプロパティを使うとすべての子コントロールにアクセスできる。

・次のように書くと、すべての子コントロールに命令できる。

For Each oControl As Control In Me.Controls

    oControl.Visible = False

Next

・VB.NET2002, VB.NET2003でも、名前を使ってアクセスできるコントロールのコレクション化を紹介

・孫コントロール等、すべての子孫コントロールにアクセスできるコントロールのコレクション化を紹介

 

1.問題点

 

画面を表示するアプリケーション、つまりほとんどのアプリケーションではテキストボックやボタンなどさまざまなコントロールを使用します。

これらのコントロールを制御するプログラムは1つ1つのコントロールに個別に命令しなければいけないのでしょうか?もちろん、そんなことありません。

たとえば、10個のテキストボックスがあるフォームで、10個のテキストボックスすべてのEnabledFalseにしたい場合を考えて見ましょう。次のように書けば目的は達成できますが、手間がかかります。

VB.NET 2002 対応 VB.NET 2003 対応 VB2005 対応

TextBox1.Enabled = False
TextBox2.Enabled =
False
TextBox3.Enabled = False
TextBox4.Enabled = False
TextBox5.Enabled = False
TextBox6.Enabled = False
TextBox7.Enabled = False
TextBox8.Enabled = False
TextBox9.Enabled = False
TextBox10.Enabled = False

■リスト1

一方、次のようにするとフォーム上にあるテキストボックスにまとめて命令することができます。

VB.NET 2002 対応 VB.NET 2003 対応 VB2005 対応

Dim o As Control

For Each o In Me.Controls

    If o.GetType Is GetType(TextBox) Then

        o.Enabled = False

    End If

Next

■リスト2

この例にはまだ紹介していない手法が使われていますので、ごく簡単に補足します。

For EachNextはループの一種で、使い方もそれほど難しくはないので今はコードのサンプルの形をそのまま覚えていただければ十分です。

GetTypeメソッド(読み方:GetType = ゲットタイプ)はコントロールや変数の型を調べるときに良く使われます。たとえば、変数Xに対して、MsgBox(X.GetType)とすると、Xの型が表示できます。XがIntegerかどうか調べるには、If X.GetType Is GetType(Integer)とします。上記の例では変数oがTextBoxかどうかを調べています。

さて、今回はコントロール1つ1つを個別に制御するのではなく、この例のようにまとめて制御する手法を紹介します。

なお、コントロールを個別に制御すべきか、まとめて制御すべきかは状況により異なるということも忘れないで下さい。いつでもまとめて制御すればよいと言うものでもありません。

 

2.Controlsプロパティ

 

コントロールをまとめて制御するときに最もよく利用するのが、Controlsプロパティ(読み方:Controls = コントロールス)です。Controlsプロパティはすべてのコントロールが持っているプロパティで、Controlsプロパティを使うと そのコントロール上に配置されているすべてのコントロールにアクセスすることができます。

ただし、コントロールの中にパネルなどのコンテナがある場合、その中にあるコントロールにはアクセスできません。

たとえば次の画像を見てください。

■画像1

このフォームの中ではパネルの中にテキストボックスとボタンが配置されていますが、フォームのControlsプロパティではこれらのコントロールにアクセスできません。これらのコントロールにアクセスす るにはパネルのControlsプロパティを使用する必要があります。

■画像2

このフォームの構造を図解すると、上の図に通りになります。Controlsプロパティでは子コントロールにはアクセスできますが、孫コントロールにはアクセスできない点を覚えておいてください。つまり、FormControlsプロパティでは、TextBox3にはアクセスできませんが、Panel1ControlsプロパティはTextBox3にアクセスできます。

さて、 実際にControlsプロパティを使って個々のコントロールにアクセスするには引数にコントロール固有の数値、またはコントロール名を指定します。ただし、コントロール名を指定できるのはVB2005以降ですので、それ以前のVBでは数値しか指定できません。

この「コントロール固有の数値」はVBが自動的に個々のコントロールに割り当てた数値です。どのような数値が割り当てられるのかは実行するまでわかりません。そのため、具体的な数値を使ってControlsプロパティを使用する場面はあまりありません。

以上を簡単にまとめると次のとおりになります。

Controlsプロパティを使うと子コントロールにアクセスできる。

Controlsプロパティを使っても孫コントロールにはアクセスできない。

Controlsプロパティを使って個々のコントロールにアクセスするには、コントロール固有の数値か、コントロールの名前を指定する。

※ただし、VB.NET2002, VB.NET2003では名前を使ったアクセスはできない。

孫コントロールにアクセスしたり、VB.NET2002,VB.NET2003でも名前を使ってアクセスしたりする方法は一番最後に紹介します。

Controlsプロパティの具体的な使用例を紹介しましょう。いくつかのコントロールを適当に配置したフォームで、次の命令を実行してみてください。どれかのコントロールが非表示(Visible = False)になります。

VB6対応 VB.NET2002対応 VB.NET2003対応 VB2005対応


Me
.Controls(2).Visible = False
 

■リスト3

どのコントロールが非表示になるか、わからないのでこのままでは何の役にも立ちません。あくまでControlsプロパティの使い方を説明するため だけの例です。

しかし、次のようにするとすべてのコントロールを非表示にできます。このように「すべて」に対して命令できるのであればいろいろと使い道もでてきます。

VB6対応 VB.NET2002対応 VB.NET2003対応 VB2005対応

Dim oControl As Control

For Each oControl In Me.Controls

    oControl.Visible = False

Next

■リスト4

この例はFor EachNextではなく、通常のForNextを使って書くこともできます。その例は下の通りです。

VB6対応 VB.NET2002対応 VB.NET2003対応 VB2005対応

Dim K As Integer

For K = 0 To Me.Controls.Count - 1

    Me.Controls(K).Visible = False

Next

■リスト5

 

博士のワンポイントレッスン
V太:博士〜。Forループで使われている Control.Count -1 って言うのがよくわかりません。この -1 って何ですか?
博士:はは。そのことが気になるようならVBのことがわかりかけてきた証拠じゃのう。このループは要するにすべてのコントロールの数だけループしたいと言うことじゃ。それはわかるかな?
はい。でも、そしたら -1 ってやったら一個分ループの回数が少なくなっちゃうんじゃないですか?
そこで、もう一度ループをよく見るのじゃ。ループの最初は 0 となっておるじゃろう。コントロールが3個あったとして、0 〜 3 までループを回したら 0, 1, 2, 3の4回ループが回ることになってしまうのじゃ。このように 0 から始める場合はループの回数を -1 するのはよくあることなのじゃ。
B子:算数の授業でやった「植木算」にも似ているわね。10メートル間隔で電柱を3本立てると、端から端までは何メートルになるでしょう?
10メートル × 3本 だから、30メートルかな?

答えは20メートルじゃ!(よ!)

式 10メートル × (3 - 1)本

 

上の例を少し改造して次のようにすると、さきほど説明した「VBが自動的に個々のコントロールに割り当てた数値」を簡単に見ることもできます。

VB6対応 VB.NET2002対応 VB.NET2003対応 VB2005対応

Dim K As Integer

For K = 0 To Me.Controls.Count - 1

    Me.Controls(K).Text = K

Next

■リスト6

とはいえ、実際には本当に「すべて」のコントロールに一括して命令する場面はそれほど多くありません。最も需要が高いのは、「いくつかのコントロールをまとまりとして、そのまとまりに対して一括して命令する」という機能でしょう。これについては章を改めて 順番に説明します。

 

3.Controlsプロパティによるグループ化

 

Controlsプロパティによってコントロールをグループ化する主な方法を紹介しますが、先にも書いたようにControlsプロパティを使う方法では孫コントロールにはアクセスできない点に注意してください。

 

コンテナでグループ化する

いくつかのコントロールをひとまとまりにして命令する最も簡単な方法は、Controlsプロパティと「コンテナ」を合わせて使うことです。「コンテナ」は以前にも登場しましたが、フォームやパネルのようにコントロールを配置できるコントロールのことでした。最も手軽なコンテナはパネルです。

フォーム上にパネルを配置して、まとめたいコントロールをそのパネルの中に配置してください。

あとは、先ほど説明したのと同じ手法でパネル内のコントロールすべてを制御することができます。たとえば、パネル内のすべてのコントロールのTextを「こんにちは!」にするには次のようにします。

VB.NET2002対応 VB.NET2003対応 VB2005対応

Dim oControl As Control

For Each oControl In Panel1.Controls

    oControl.Text = "こんにちは!"

Next

■リスト7

この方法は手軽でわかりやすい方法ですが、コントロールの位置が離れている場合はつかいにくくなります。たとえば、フォームの端と端にあるコントロールをまとめたいが、中央にあるコントロールはまとめたくない場合に、無理に端のコントロールを一つのコンテナの中に配置するのは少々強引でしょう。そのような配置はレイアウトがわかりにくくなって後々混乱を招きます。

 

名前でグループ化する

あらかじめコントロールの名前に共通する特徴を持たせておけば、その特徴のあるコントロールをまとめて扱うことができます。

たとえば、何かの登録画面があって、いくつかの項目には必ずなにかを入力しなければいけないとしましょう。その「いくつかの項目」をまとまりとして扱いたい場合、それらの項目を表すテキストボックスに共通して、「txtNeed」から始める名前をつけておきます。「txtNeedName」や「txtNeedMailAddress」といった具合です。

これだけで、これら「txtNeed」から始まるコントロールをグループ化できます。

次の例では「txtNeed」から始まる名前のコントロールの背景色を薄い赤にします。

VB.NET2002対応 VB.NET2003対応 VB2005対応

Dim oControl As Control

For Each oControl In Me.Controls

    If Strings.Left(oControl.Name, 7) = "txtNeed" Then
       
oControl.BackColor = Color.LightCoral
    End
If

Next

■リスト8

この方法は手軽ですが、「共通の特徴を持った名前」という目に見えない規則に縛られることになります。この手の「目に見えない規則」は始めのうちは甘く見て手軽に使ったりもするのですが、無計画に使っていると後になって気が付いたときには規則が増えたり複雑化して手に終えなくなってしまう場合があります。そうなってから元のシンプルな状態に戻すのは結構しんどいです。あまり多用しないように注意が必要です。特にシンプルを愛している方は使わない方が良いかもしれません。

 

種類でグループ化する

冒頭の例でも紹介しましたが、コントロールの種類、つまり「型」でグループ化することもできます。同じ型のコントロールをまとめて制御すると言うわけです。

次の例はフォーム上のすべてのボタンを使用不可にします。

VB.NET2002対応 VB.NET2003対応 VB2005対応

Dim oControl As Control

For Each oControl In Me.Controls

    If oControl.GetType Is GetType(Button) Then
       
oControl.Enabled = False
   
End If

Next

■リスト9

この方法は違う種類のコントロールはグループ化できないので使える場面は限られることになります。また、この方法でプログラムしてしまうと、後になって「例外的にこのコントロールはグループ化しない」という修正が必要になった場合に、プログラムの修正が煩雑になってしまいます。

 

Tagプロパティでグループ化する

名前にしろ種類(型)にしろ、本来別の用途のものを転用してグループ化の根拠とするのはプログラムがわかりにくくなるのでやりたくないという方もいらっしゃるでしょう。

簡単にできるグループ化の最後の手段はTagプロパティ(読み方:Tag = タグ)によるグループ化です。Tagプロパティは何の機能もないプロパティで、まさにこういったユーザーが自由に何かを設定したい場合に使うプロパティです。

グループ化したいコントロールに共通の特徴を持つTagプロパティを設定しておけば、For Eachのループの中でそのコントロールだけに命令することができるわけです。

たとえば、ユーザーが管理者の時だけ使えるいくつかのコントロールのTagプロパティに"Admin"と設定しておきます。そうすると、以下のプログラムでは管理者用のすべてのコントロールのEnabledFalseにします。

VB6対応 VB.NET2002対応 VB.NET2003対応 VB2005対応

Dim oControl As Control

For Each oControl In Me.Controls

    If oControl.Tag = "Admin" Then
       
oControl.Enabled = False
   
End If

Next

■リスト10

この方法は非常に柔軟でほとんどの場合で有効です。Tagプロパティの文字列を工夫したり、Tagプロパティに独自のクラスをセットすることで応用範囲は広がります。(TagプロパティはObject型なので文字列でなくても何でも設定できます)。

 

4.自分でグループを定義する

 

Controlsプロパティに頼らないでグループ化を行うこともできます。自分でグループ化したいコントロールのコレクションまたは配列を定義する方法です。「コレクション」や「配列」は初級講座ではまだ登場していません。詳細は別の機会に解説する予定ですがどちらも似たようなものをまとめる機能です。実際、Controlsプロパティはコレクションの一種です。ここではこの辺りのことは特に気にしないで結構ですし、必要があれば以下のプログラムを真似することも簡単です。

さて、次のようにプログラムするとプログラムから自由な組み合わせでコントロールをグループ化することができます。

VB.NET2002対応 VB.NET2003対応 VB2005対応

Dim MyControls As New ArrayList

Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load

    MyControls.Add(TextBox2)
    MyControls.Add(TextBox3)
    MyControls.Add(Button1)

End Sub

■リスト11

この例では、TextBox2, TextBox3, Button1をグループ化しました。以降このグループについてはMyControlsを使うことによりControlsプロパティと似た方法でアクセスすることができます。

たとえば、このグループに属するすべてのコントロールのEnabledをFalseにするには次のように書きます。

VB.NET2002対応 VB.NET2003対応 VB2005対応

Dim oControl As Control

For Each oControl In MyControls

    oControl.Enabled = False

Next

■リスト12

この方法では非常に柔軟なグループ化を行うことができますが、プログラムでグループを定義しなければならないので作業が面倒ですし、ミスも心配です。しかしながら現在のところこの方法こそが定番のグループ化方法と言えるでしょう。

 

5.特別な方法

 

以上で通常の解説は終了です。ここまでの知識があればコントロールのグループ化に関してはまったく問題ないでしょう。

ここでは、もう少しステップアップして独自のコレクションを使ったより便利なグループ化の実例を紹介します。

コントロールのグループ化に関してよく言われる問題はControlsプロパティで番号ではなく名前でコントロールを区別したいと言うことと、コンテナの中のコントロールも含めて本当にすべてのコントロールにアクセスしたいと言うことです。

ちょっとした工夫でこの2点の問題は解決できますが、解決方法を紹介する前に「Controlsプロパティで名前を使ってコントロールを区別したい」というのはどういうことなのか簡単に説明します。

VB2005以降ではこの問題は改善されているので次のようなプログラムを書くことができます。

VB6対応 VB2005対応

Dim i As Integer
Dim ControlName As String

For i = 1 To 5
    ControlName = "ComboBox" & i
    Me.Controls(ControlName).Enabled =
False
Next

■リスト13

このプログラムではComboBox1 ComboBox5EnabledFalseになります。

VB.NET2002およびVB.NET2003ではControlsプロパティでは名前が使用できないのでこのような書き方をすることができません。

これらの問題点を解決するために自作のコレクションを作成します。以下で紹介するCreateControlCollection関数を呼び出すとフォーム上のコンテナ内部も含んだすべてのコントロールが名前でアクセス可能になります。

VB.NET2002対応 VB.NET2003対応 VB2005対応

Public Shared Function CreateControlCollection(ByVal Container As Control) As Hashtable

    Dim Hash As New Hashtable

    Call AppendControlCollection(Container, Hash)

    Return Hash

End Function

Private Shared Sub AppendControlCollection(ByVal Container As Control, ByVal Hash As Hashtable)

    For Each oControl As Control In Container.Controls

        If oControl.Controls.Count > 0 Then
           
Call AppendControlCollection(oControl, Hash)
        End
If

        Hash(oControl.Name) = oControl

    Next

End Sub

■リスト14

この関数はこれで完成していますので、必要な場合はここからそのままコピー貼り付けして使用してください。

この関数の使用例も紹介しておきます。

次の例では、コンテナの中のコントロールも含めてフォーム上のすべてのコントロールのTextをHello!にします。

VB.NET2002対応 VB.NET2003対応 VB2005対応

Dim MyControls As Hashtable = CreateControlCollection(Me)
Dim oControl As Control

For Each oControl In MyControls.Values

    oControl.Text = "Hello!"

Next

■リスト15

次の例では、コンテナの中のコントロールも含めてTextBox1TextBox12TextをHello!にします。

コントロール名を名前で指定していますが、VB.NET2002, VB.NET2003でも使用可能です。

VB.NET2002対応 VB.NET2003対応 VB2005対応

Dim MyControls As Hashtable = CreateControlCollection(Me)
Dim i As
Integer

For i = 1 To 12

    MyControls("TextBox" & i).text = "Hello!"

Next

■リスト16