Visual Basic 6.0 初級講座 |
第29回 イベントを作る
今回はイベントを自作する方法を説明します。普段はあらかじめ用意されているイベントを利用するのがほとんどでしょうがVBにはイベントを自分で作る機能もあるのです。ここではクラスを使ってイベントを作る方法を説明します。
この回の要約 ・イベントはPublic Event EventName のように宣言する。 ・イベントを発生させるにはRaiseEventを使う。 ・イベントを受け取る側では、イベントを発生させるクラスを宣言するときにWithEventsを使用する。 |
1.自作の数値型オブジェクト
VBでは数値を扱う方として整数型(Integer)、長整数型(Long)、倍精度浮動小数点型(Double)などが用意されていますね。これらは桁数が違うというのがもっとも単純な認識ですが、つっこんでみると他にもいろいろと違いがあるものです。
ここで新たに便利な機能をもった数値型を作りましょう。その数値型は仮にAlarmInteger型(アラームインテジャー)と呼ぶことにします。桁数は整数型(Integer)と同じなのですが、あらかじめ設定しておいた最大値を超える値が代入されるとイベントを発生させて通知してくることにします。
たとえば、 Dim X As AlarmInteger のように宣言して、X.Max = 100 などとプログラム中で最大値を指定します。そして、どこかで X = 200 のような代入を行うと、 X の Overイベント(オーバー)が発生するというのが私の頭の中にある設計図です。
ですが、残念ながら上記のようなことをそのまま実現することはできません。 機能的には実現可能なのですが、VBの言語仕様上少しばかり無理があるのです。VBの言語仕様では自作のクラスは必ずどこかでNewキーワードを使って作成する必要がありますし、自作のイベントを発生させるためにはWithEventsキーワードを使う必要もあります。
このあたりのことを考慮に入れると最終的にどのような形になるのか、次から説明しましょう。
2.クラスを作る
まず、大本となるクラスAlarmIntegerを作ります。新しいクラスをプロジェクトに挿入したらクラスの名前を AlarmIntegerに変更してください。クラスをプロジェクトに追加するには[プロジェクト]メニューから[クラスモジュールの追加...]を選択して、ダイアログの中からクラスモジュールを選んでOKをクリックするのでしたね。
クラスには最初Class1という名前がついているので変更の必要があるわけです。名前を変更するにはプロジェクトエクスプローラでクラスをクリックしてプロパティウィンドウの (オブジェクト名) の部分を変更すればよいのです。普通のコマンドボタンやテキストボックスの名前を変更するのと同じですね。
肝心なプログラムですが、まず値を格納するプロパティが必要です。このプロパティをCurrentValueという名前にしましょう。それから最大値を記録するプロパティが必要ですね。こちらはMaxとしましょう。今回のメインとなるイベントはちょっとおいておいて後で付け加えることにしましょう。
以上のことを加味するとクラスのプログラムは次のようにするのがよいでしょう。
Public Max
As Integer Dim m_CurrentValue As Integer |
Public Property Get CurrentValue()
As Integer CurrentValue = m_CurrentValue End Property |
Public Property Let CurrentValue(NewValue
As Integer) m_CurrentValue = NewValue End Property |
■リスト1:現段階でのクラスAlarmIntegerのプログラム。
CurrentValueプロパティは値の代入と取得ができるようにしてありますがその他の処理は何もしていないのでただ単に Public CurrentValue As Integerとすればいいように思えるかもしれません 。しかし、最終的にはこの部分でMaxで指定された値を超えるかどうか判断してイベントを発生させるなどの処理を記述す る必要があるので、とりあえず上記のようにしてあります。
この段階でこのクラスを使ったプログラムをフォーム側に記述してみましょう。コマンドボタンを1つとテキストボックス1つをフォームに配置して簡単なテストができるようにします。
フォームのプログラムは次のように書いてください。
'この例では、エラーが発生します。 Private Sub Command1_Click() Dim X As New AlarmInteger X = Text1.Text MsgBox X End Sub |
■リスト2:現段階でのフォーム側のプログラム。クラスAlarmIntegerで既定のプロパティを設定していなければエラーになる。
実行するときにはテキストボックスに数値を入力してから実行しましょう。文字を入力した状態で実行すると確実にエラーです。今回は数値型を作成するのですからこれは当然でしょう。
ところが数値を入力して実行してもエラーが発生します。これはどういうことなのか説明しましょう。X を数値型のような存在にするというのはわれわれが勝手に想定していることであってVBにとってXは単なるオブジェクトなのです。そうすると、 X = Text1.Text という文はXの既定のプロパティに Text1.Text を代入するという意味になります。そして、Xには既定のプロパティが存在しないのです。
「既定のプロパティ」というものをご存知ない方のためにここで少し説明しましょう。たとえば、テキストボックスに文字を表示させる場合
Text1.Text = "Hello" '@
とするのが正式なやり方ですが、これを
Text1 = "Hello" 'A
としても大丈夫です。Aの式はプロパティ名を省略しているものと解釈されます。プロパティ名を省略された場合、VBは既定のプロパティが指定されたものと認識します。そして、テキストボックスの既定のプロパティはTextプロパティなので結果として@とAは同じ意味になるのです。
他のオブジェクトを見てみるとLabelの既定のプロパティはCaptionプロパティですね。CommandButtonの既定のプロパティは何かわかりますか。これはあまり使わないのですがValueプロパティです。
既定のプロパティの必要性が理解できればあとは簡単です。AlaramIntegerの既定のプロパティをCurrentValueにしてしまいましょう。CurrentValueを既定のプロパティにするにはクラスのプログラムを表示して、[ツール]メニューの[プロシージャ属性...]を選択します。
■画像1:プロシージャ属性ダイアログ
そうすると「プロシージャ属性」ダイアログが表示されるので名前のところにCurrentValueを指定しましょう。そして「詳細」ボタンをクリックして、「プロシージャID」のところに「(規定値)」を選択します。あとはOKをクリックしてみてください。
これでCurrentValueプロパティは既定のプロパティになりました。この状態でもう一度テストを実行すると今度はエラーにならずに実行できることがわかりますね。
3.イベントの実装
次はいよいよいOverイベントプログラムします。もう一度確認しておきますと Overイベントは Maxプロパティで指定した値を超える値をCurrentValueプロパティに代入しようとしたときに発生します。
イベントの引数は代入しようとした値を示すOverValueと、代入を無効にするかユーザーが指定できるCancel引数にしましょう。
そうすると、このイベントの宣言とイベント発生させる処理を追加して新たなAlarmIntegerのプログラムは次のようになります。
Public Event Over(ByVal OverValue
As Integer, Cancel
As Boolean) '@ Public Max As Integer Dim m_CurrentValue As Integer |
Public Property Get CurrentValue()
As Integer CurrentValue = m_CurrentValue End Property |
Public Property Let CurrentValue(NewValue
As Integer) Dim Cancel As Boolean If NewValue > Max Then 'A RaiseEvent Over(ByVal NewValue, Cancel) 'B If Cancel Then Exit Property 'C End If m_CurrentValue = NewValue End Property |
■リスト3:クラスAlarmIntegerの完成形。(既定のプロパティを設定することを忘れないように)
色を付けて強調している部分が新たに追加された箇所を示しています。
まず、イベントを持つために変数や関数と同様イベントの宣言をする必要があります。それが@の部分です。イベントの宣言は関数の宣言と同じですが関数と違って内部でする処理はイベントを受け取る側(今回の例ではフォーム)で記述するためクラスの側では宣言だけします。見ればわかるのですが宣言はPublic Eventから始まるのが通常です。
イベントを発生させている箇所はBです。イベントを発生させるときには RaiseEvent(読み方:RaiseEvent = レイズイベント)ステートメントを使用します。第2引数に変数Cancelを渡しています。Cの箇所でCancelがTrueの場合には何もせずに終了するように記述していますからイベントを受け取る側で第2引数をTrueを代入した場合はCurrentValueの更新は行われないことになるわけです。
大丈夫でしょうか? 書いているとなんだか説明力の不足を感じるのですが・・・。まぁ実際に使っていただければわかるとおもうのですけど。Cancel引数に関してはフォームのUnloadイベントの引数と同じ役割をするということです。
それから、イベントはいくらクラス側で発生させても受け取る側で何か記述していないとなにもおこりません。たとえば、フォームにはたくさんのイベントがあるわけですがプログラムしなければどのイベントも無意味です。Loadイベントが発生していてもLoadイベントプロシージャに何も書いていなければ無意味ですよね。それと同じです。
それで、このOverイベントを利用するためにはフォームの方のプログラムも何点か変えなければなりません。次のようになります。
Dim WithEvents X As AlarmInteger '@ |
Private Sub Command1_Click() X = Text1.Text MsgBox X End Sub |
Private Sub Form_Load() Set X = New AlarmInteger 'A X.Max = 100 End Sub |
Private Sub X_Over(ByVal OverValue
As Integer, Cancel
As Boolean)
'B Dim Answer As VbMsgBoxResult Answer = MsgBox(OverValue & "を代入しようとしました。キャンセルしますか。", vbQuestion Or vbYesNo) If Answer = vbYes Then Cancel = True 'C End Sub |
■リスト4:フォーム側のプログラムの完成形。
まず、オブジェクトからイベントを受け取りたいので変数XはWithEventsキーワードをつけて宣言部で宣言する必要があります。それが@の部分です。そして、WithEventsを使うとなぜかNewキーワードをつけることができないのでフォームのLoadイベントでSet Newを使います(A)。
肝心のOverイベントですがこれはコマンドボタンなどと一緒で雛形は自動的に生成されます。どうやるのかというとXをWithEventsつきで宣言するとオブジェクトボックスにXが表示されるのでそれを選ぶだけです。もし、Xに複数のイベントがあるのならさらにプロシージャボックスでイベントを選択することもできます。
「オブジェクトボックス」「イベントボックス」がわからない方は基礎講座2をご覧ください。
Overイベントの内容は自由に書いてもいいのですが今回はテストということで制限を越える値の代入を許可するかキャンセルするかユーザーが選択できるようにしました。
これで実行してみると全体の動きがわかると思います。
あと一言書いておきますが、値の最大値であるMaxプロパティを設定するコードはForm_Loadに書いておきましたが、もし、この記述をどこにも書かなかったとするとプログラムではMaxは初期値である 0 となっています。
また、初期値を自分で0以外の値に設定することもできます。それにはクラス側でInitializeイベントを使います。クラスはNewで生成されたときに必ずInitializeイベントが発生するので初期値の設定などクラス自身の初期化処理はこのInitializeイベントに記述するのが普通です。
4.メリット
さて、「代入しようとした値が制限値を超えているか?」という判断をするだけならたった1つのIf文ですむはずで何もわざわざクラスを導入したりイベントで通知したりという技を使う必要はないはずです。
実際、今回紹介した例でもクラスとイベントを使うよりはIf文1つで書いたほうがはるかに楽だし効率もよいです。そうすると今回のようなイベントを使うメリットはどこにあるのでしょうか?
これはプロパティのところでも似たようなことを書いたのですが似たような処理が散在する場合にその処理を一箇所で集中管理するのに非常に適しているのです。けれど、似たような処理が散在するサンプルをここで作るのはもう大変なので説得力のない(その代わりにシンプルで説明しやすい)サンプルで 使用している次第なのです。
たとえば、IDのような数値を考えてみてください。IDはものによって個別の番号を表しているのが普通ですが、さらにIDの桁数に意味を持たせている場合もありますよね。たとえば、大学生などの学生IDを見て見ましょう。L02519602BというIDの生徒がいたとします。実はこの学校では先頭の文字は学部を表しており、次の2文字は 入学した年を表しています。最後の1文字はチェックディジットとです。
そうするとこの、L02519602Bを見ただけでこの生徒は文学部(この学校ではLは文学部を表すものとします)で2002年に入学したのだなとわかるわけです。
この大学に導入されているソフトで学生IDのチェックを行うことを考えましょう。おそらく学生IDの入力はいろいろな場所で必要になるでしょう。そうすると学生IDが正しいものかチェックするコードもいろいろな場所に必要になるわけです。これは「似たような処理が散在する」状況です。もし、今回作ったサンプルのように学生ID型の変数を表すクラスを1つ用意しておけばこのようなチェックはすべてクラスのたった一箇所で管理できることになり大変楽です。将来もし、入力した年度を西暦ではなく和暦にしたいなどの変更があった場合にもほんのちょっとの手直しですむことになります。
このように、プログラムの構築するうえで「散在する処理をいかに効率的に一箇所に集中させるか」ということはとても重要なのです。
なお、例にあげた学生IDのように、番号自体に意味を持たせることはプログラムの設計上は好ましいことではありません。けれども、見た目でわかるメリットのために実際の現場ではしばしば使われる手法です。
5.その他のイベント
以上見てきたように自分でイベントを作ることができるということはおわかりいただけたでしょう。そして、どのようなときにイベントを発生させるかももちろん自分でしていできるわけですよね。
ここで、今回の例とは違ってVBに備わっていないイベントをプログラムする方法について簡単に示唆しておきましょう。たとえば、「USBデバイスが装着された」、「日付が変わった」などのイベントです。これらはVBの普通の方法では取得することができないのでどんなにイベントを自作しても無理なように思われるのですがそんなことはありません。
フックやサブクラス化という手法をつかってこれらの通知をWindowsから受け取ることはVBでも可能です。そして、それらの通知を受け取ったときに今回の要領でイベントを発生させればよいわけです。
しかしながら、この技はやや高度のものなので中級講座で説明します。初級講座もここまで読み進められている方は十分中級で通用すると思います。
6.最後に
ここ数回で説明したクラスとイベントとプロパティを使えばVBの効率性は大きくアップします。実際、これらを知らないで作っているプログラムを見ると私などはもうそれだけで、「あぁこのプログラマは初級レベルだな」と判断してしまいます。
次回は、さらにクラスを使ってする強力な技をご紹介します。今回とは逆でクラスの側でイベントをトラップしようというものです。
それでは、それまで失礼します。