Visual Basic 6.0 テクニック
VB6対応

 

Visual Basic 中学校 > VB6 テクニック >

10.イベントの共有

 

みなさんは同じようなことをいろいろなイベントに書いた経験はありませんか?たとえば、テキストボックスを全選択状態にするには次のように書きますよね?

Text1.SelStart = 0

Text1.SelLength = Len(Text1.Text)

これをフォーカス取得時にやろうと思ったら、GotFocusイベントに書くことになるわけですが、テキストボックスが10個あったら、10個のGotFocusイベントにほとんど同じコードを書かなければいけないのでしょうか?

いいえ。もちろんそんなことはありません。今回はイベントプロシージャを共有する3つの方法を説明します。この方法によりあなたのプログラミングは飛躍的な効率アップを遂げるでしょう。

 

1.コントロール配列

 

複数のコントロールでイベントプロシージャを共有する一番簡単な方法はコントロール配列を使うことです。

コントロール配列になっているコントロールはすべて同じイベントプロシージャを使用します。

コントロールを配列にするには名前を同じにして、Indexプロパティに数字を設定します。Indexプロパティはそれぞれが違う番号になるように設定して下さい。逆を言えば「名前が同じで、Indexプロパティが違うコントロールの集まりをコントロール配列という」のですね。

ちなみに、デザイン画面でコントロールをコピーして張り付けると自動的にコントロール配列になるのでこっちのほうが楽です。

さて、Text1が10個あるとしましょう。ただ、Indexプロパティはそれぞれ0〜9が設定されているとします。

この10個のGetFocusイベントプロシージャはどれも一緒です。冒頭のようにフォーカスを得た瞬間にテキストを全選択状態にするには次のようにプログラムすることになります。

Private Sub Text1_GotFocus(Index As Integer)

    Text1(Index).SelStart = 0
    Text1(Index).SelLength = Len(Text1(Index).Text)

End Sub

これだけで、10個すべてのテキストボックスが機能します。これがイベントの共有の威力です。

コントロール配列についてもう少し説明しておきましょう。

コントロール配列にしたコントロールのイベントプロシージャには必ずIndex引数が追加されています。普通のコントロールのGotFocusイベントにはIndex引数がないということに気がついていましたか。

このIndex引数にはIndexプロパティの値が入ってきます。たとえば、Indexプロパティが4であるテキストボックスにフォーカスを移動した場合、GotFocusイベントのIndex引数は4になっています。このようにして、コントロール配列の中のどのコントロールに関するイベントが発生したかわかるわけです。

さらに、コントロール配列になっている個々のコントロールにはこのIndexプロパティの値を使ってアクセスします。

例を挙げると、Text1(2).Text = "Hello!" とすると、Indexプロパティが2であるText1にHello!という文字列が代入されます。

ちなみに、初心者は一度コントロール配列にしてしまうと元に戻す(コントロール配列じゃなくす)方法がわからなくて苦労するようですね。コントロール配列を解除するにはIndexプロパティを空にして、名前を変更するだけです。

ただし、この作業はコードからは行えません。デザイン時に行ってください。

 

2.クラスを使う

 

クラスを使ってもイベントの共有はできます。イベントの共有だけが目的の場合はコントロール配列のほうが便利ですがクラスを使うことによるメリットも利用したい場合には至れり尽くせりの感があります。

この「クラスを使うことによるメリット」の方は後のところで解説するとして、先にクラスを使ってイベントを共有する方法を説明します。

例として、テキストボックスを想定します。

まず、新しいクラスモジュールをプロジェクトに追加したら名前をCEventにして、次のように記述してください。


Public WithEvents ClassTextBox As TextBox
 

この1行が重要です。特にイベントを共有するためのWithEventsステートメントは今回の目玉といえるでしょう。

この1行を記述すると、コードを入力する画面の左上にあるコンボボックス(オブジェクトボックス。わからない人はこの画面のAのしるしの書いてあるところ)の一覧の中にClassTextBoxが現れます。実際に選択してみて下さい。

そうすると、ClassTextBoxのChangeイベントプロシージャが自動的に挿入されます。ほかのイベントプロシージャを利用したかったら、プロシージャボックス(わからない人はこの画面のBのしるしの書いてあるところ)の一覧から選択するだけでよいのです。

ここで生成されるイベントプロシージャが共有されるイベントプロシージャとなるわけです。

ここでは、Changeイベントプロシージャに次のように入力してください。

Private Sub ClassTextBox_Change()

    ClassTextBox.Parent.Cls
    ClassTextBox.Parent.Print Len(ClassTextBox.Text) & "文字入力されています。"

End Sub

この意味はテキストボックスの親コンテナ(この場合はForm1を想定)にテキストボックスに入力されている文字数を表示するというものです。

さっそくフォーム側にいくつかテキストボックスを貼り付けてどういう風になるか実験してみましょう。

今度は4つのテキストボックスを貼り付けることにしましょう。その際フォームの右上の部分には文字を表示するので見えるようにあけておいてください。また。テキストボックスをコントロール配列にしないでください。

そして、フォームに次のようにプログラムして下しさい。

Dim MyEvent(3) As New CEvent

Private Sub Form_Load()

    Set MyEvent(0).ClassTextBox = Text1
    Set MyEvent(1).ClassTextBox = Text2
    Set MyEvent(2).ClassTextBox = Text3
    Set MyEvent(3).ClassTextBox = Text4

End Sub

この意味は「クラス」の使い方がわからない人には難しいでしょう。Dim MyEvent(3) As New CEventで先ほどプログラムしたクラスを表す4つの変数を配列として宣言しています。別に配列にする必要はないのですが、配列にすると1行で書けるので見やすくなります。

そして、Loadイベントで4つの変数つまり4つのクラス(のインスタンス)のClassTextBoxプロパティにそれぞれのテキストボックスを代入するのです。

こうすることによりクラス内のClassTextBoxで代入されたテキストボックスを表すことができます。そして、クラスが4つあることに注意してください。プログラムしたクラスは1つですが、実行時には4つのクラスが生成されます。これはたとえば、Dim X(3) As Integer とした場合に4つの整数型の変数が生成されるのと似ています。マイクロソフトのVisual Basicを作った人はもちろん Integerの部分は1つしかプログラムしていません。プログラマーが4つの変数を作ったから、4つのInteger型の変数が生成されたまでです。

こうみてみると、クラスを作るという作業は型を作る作業と似ていますね。実際、クラスも型もどちらも As の後ろに記述します。

少し説明が長くなりましたが、プログラムはこれで完成です。

実行してテキストボックスに何か入力してみてください。ちゃんと文字数が表示されるでしょう。

それでは、次にクラスを使うことのメリットを少し解説しておきましょう。

クラスを使うことの最大のメリットはクラスに独自の「メソッド」や「プロパティ」を装備できることです。

今回の例では文字の色を表すForeColorプロパティを追加してみましょう。

クラスの宣言部に次のコードを追加してください。


Public ForeColor As OLE_COLOR
 

OLE_COLORとは見慣れないかもしれませんがVBで色を扱う変数の型です。とはいってもLongで宣言しても多分大丈夫です。

そして、クラスのClassTextBox_Changeイベントにも1行を追加します。

Private Sub ClassTextBox_Change()

    ClassTextBox.Parent.Cls
    ClassTextBox.Parent.ForeColor = ForeColor
    ClassTextBox.Parent.Print Len(ClassTextBox.Text) & "文字入力されています。"

End Sub

フォーム側にはそれぞれのテキストボックスに対してプログラムを追加するので全部で4行追加する必要があります。

Private Sub Form_Load()

    Set MyEvent(0).ClassTextBox = Text1
    Set MyEvent(1).ClassTextBox = Text2
    Set MyEvent(2).ClassTextBox = Text3
    Set MyEvent(3).ClassTextBox = Text4

    MyEvent(0).ForeColor = vbRed
    MyEvent(1).ForeColor = vbBlue
    MyEvent(2).ForeColor = vbGreen
    MyEvent(3).ForeColor = vbYellow

End Sub

これで実行してみると、文字数を表す文字の色がテキストボックスによって変わるのがわかりますね。しかも、結構シンプルでわかりやすいプログラムになりました。こういうことがやりたい場合にはコントロール配列ではなくクラスを用いる方がはるかにスマートです。

ちなみに、コントロール配列をクラスのWithEventsステートメントで取得することはできないので注意して下さい。

 

3.サブクラス化を使う

 

最後にサブクラス化を利用する方法を説明しましょう。「サブクラス」というと「クラス」に似たものかと思われるかもしれませんが名前が似ているだけで内容はまったく異なります。

また、サブクラス化を行っているときにエラーが発生するとWindowsやVBの調子がおかしくなったりするので注意が必要です。

サブクラス化の詳細について説明すると大変なので概要を知りたい方は中級講座 第9回 フック をご覧ください。

ここでは、サブクラス化を利用してイベントプロシージャを共有する方法にしぼって解説します。

サブクラス化してイベントを共有するメリットは2つあります。1つは「異なるイベントでも共有できる」点です。今まで解説してきた共有はイベントごとの共有でした。そのためChangeイベントはChangeイベントで共有、GotFocusイベントはGotFocusイベントで共有というようにイベント別でプログラムする必要がありました。ところが、なんとサブクラス化を利用すると複数のコントロールのすべてのイベントを一箇所にまとめることができるのです。まぁこれはデメリットでもあるというわけですが。

2つ目は「VBで標準でサポートされていないイベントも取得できる」点です。たとえば、テキストボックスに貼り付けを行った場合VBで発生するイベントは何でしょう?Changeイベントですが、これは「貼り付け」以外の場合でも発生しますよね。この点サブクラス化の手法を使うと「貼り付け」というイベントを認識することができます。もちろんその他のイベントは無数にあります(が、そのほとんどは使わないでしょう。よく使うイベントなら標準でサポートされているはずです)。

では、早速サブクラス化を行います。

サブクラス化を行うには標準モジュールに次のようにプログラムして下さい。このプログラムは中級講座 第9回 フック に掲載されているものとほとんど同じものです。「貼り付け」イベントを取得するために少し改造してあります。

Option Explicit

'□API関数
Public Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hWnd As Long, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Public Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As Long, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

'□SetWindowLongで使用
Private Const GWL_WNDPROC = -4

'□メッセージ
Private Const WM_PASTE = &H302 '貼り付け

'□コレクション すべてウィンドウハンドルがキー
Dim colDProc As Collection '現在サブクラス化されているコントロールの元のWindowsProcのアドレス
Dim colControl As Collection
'■WindowProc
'■機能:メッセージを横取りする。
'■備考:この関数はコールバック関数なので定義を変えてはいけない!

Public Function WindowProc(ByVal hWnd As Long, ByVal uMsg As Long, ByVal wParam As Long, ByVal lParam As Long) As Long

    Dim DefaultProc As Long
    Dim oControl As Control

    Select Case uMsg

        Case WM_PASTE
            Set oControl = colControl(CStr(hWnd))
            MsgBox oControl.Name & "に貼り付けが行われます!"

    End Select

CONTINUE:
    '引当のWindowProcへメッセージを回す。
    DefaultProc = colDProc(CStr(hWnd))
    WindowProc = CallWindowProc(DefaultProc, hWnd, uMsg, wParam, lParam)

End Function
'■BeginSubClass
'■機能:サブクラス化を開始する。

Public Sub BeginSubClass(oControl As Control)

    Static bAlready As Boolean
    Dim DefaultProc As Long

    If Not bAlready Then
        Set colDProc = New Collection
        Set colControl = New Collection
        bAlready = True
    End If

    'サブクラス化実行
    DefaultProc = SetWindowLong(oControl.hWnd, GWL_WNDPROC, AddressOf WindowProc)

    '元のWindowProcのアドレスを保存
    colDProc.Add DefaultProc, CStr(oControl.hWnd)
    '対象となるコントロールを保存
    colControl.Add oControl, CStr(oControl.hWnd)

End Sub
'■EndSubClass
'■機能:サブクラス化を終了します。

Public Sub EndSubClass(oControl As Control)

    Dim Ret As Long
    Dim DefaultProc As Long

    'WindowProcのアドレスを元に戻す。
    DefaultProc = colDProc(CStr(oControl.hWnd))
    Ret = SetWindowLong(oControl.hWnd, GWL_WNDPROC, DefaultProc)
    colDProc.Remove CStr(oControl.hWnd)
    colControl.Remove CStr(oControl.hWnd)

End Sub

ここではこのプログラムの詳細な解説はしません。ただ、注目してほしいのは、Aの部分です。このWM_PASTEは「貼り付け」をあらわしています。つまり、Aの部分こそが「貼り付けイベント」を処理するところなのです。

さらに、その下の Set oControl = colControl(CStr(hWnd)) の部分で「どのコントロールに貼り付けイベントが発生したか」を取得しています。

このコードを試すフォーム側のプログラムは次のようになります。

Private Sub Form_Load()

    Call BeginSubClass(Text1)
    Call BeginSubClass(Text2)

End Sub
Private Sub Form_Unload(Cancel As Integer)

    Call EndSubClass(Text1)
    Call EndSubClass(Text2)

End Sub

もちろん、フォームにText1とText2を貼り付けておくのを忘れないでください。できたら早速実験してみてください。なかなかの威力に感心していただけましたか?VB標準では検知できないはずの貼り付けイベントに見事に反応するでしょう。

さて、先ほど WM_PASTE が「貼り付け」をあらわすのだと書きましたが、それではChangeイベントやGotFocusイベントを表すのはなんでしょうか? それに標準でサポートされていないイベントにはWM_PASTE(「貼り付け」)の他にどんなものがあるのでしょうか。

ここでそれらを一覧表にしてお見せできればいいのですが、私の体力ではとても無理です。そこで、テキストボックスに限り使いそうなものだけをいくつかピックアップしてご紹介します。その他のものに関してはMSDNライブラリのWM_やEM_ではじまるキーワードを端っこから調べてみてください。きっと未知のさまざまなイベントに関するものが見つかるはずです。(MSDNライブラリにはリンクページから行ってください)。

定数 説明
WM_CHAR &H102 キー入力=KeyPress, wParamでキーコード取得
WM_CONTEXTMENU &H7B 右クリックメニュー表示時
WM_COPY &H301 「コピー」時
WM_CUT &H300 「切り取り」時
WM_KEYDOWN &H100 キー押下=KeyDown, wParamでキーコード取得
WM_KEYUP &H101 キー開放=KeyUp, wParamでキーコード取得
WM_KILLFOCUS &H8 フォーカス喪失=LostFocus
WM_MOUSEWHEEL &H210 マウスの中央ボタンでスクロール
WM_SETFOCUS &H7 フォーカス取得=GotFocus
WM_UNDO &H304 「元に戻す」発動

※1:太字になっている項目はVB標準のイベントではサポートされていないものです)

※2:WM_MOUSEWHEEL はWindows98以降または、Windows NT 4.0以降でのみサポートされます。

 

4.最後に

 

いかがでしたでしょうか。最後のサブクラス化はちょっと面白いのですがエラーが発生すると危険なので気をつけてください。

イベントの共有のほかにもプログラムを圧倒的に効率化する方法であなたが知らない方法があるかもしれません。もちろん私が知らない方法もあるかもしれません。情報収集が大切ということですね。

私の情報源はMSDNと各種雑誌、それにインターネットです。

それでは、また。