Visual Basic 初級講座 |
Visual Basic 中学校 > 初級講座 >
クラスでイベントを定義・発生させる方法を説明し、イベントの利用方法を具体例を挙げて説明します。
概要 ・イベントを定義するにはEventを使う。
・イベントを発生させるにはRaiseEventを使う。
・変数のイベントを使うには変数を宣言するときにWithEventsを使う。
・イベントを使って双方向で情報をやりとりする方法 |
イベントはクラス間の連携手段の1つです。通常のプログラムでもButtonのClickイベントやTextBoxのKeyPressイベントなどさまざまなイベントを 使ってFormとButtonクラスやTextBoxクラスとの連携を行っています。
イベントというとこのようなコントロールのイベントが真っ先に頭に浮かんでしまいますが、通常のクラスにもイベントを実装することができます。コントロールではないクラスにイベントを実装して何に使うかは後で説明することにしてまずはイベントを自由自在に扱えるようにしましょう。
なお、コントロールもクラスの一種ですから将来コントロールを作成するときにはここで説明しているイベントに関する事柄を活用することになります。コントロールの作成については中級講座で扱います。
イベントもメソッドやプロパティと同様に宣言しなければなりません。また宣言とは別にイベントを発生させるコードを書く必要もあります。
宣言はEventキーワード(読み方:Event = イベント)を使って次のようにします。
Public
Class Tester Public Event Pop(ByVal sender As Object, ByVal e As EventArgs) End Class |
■リスト1:イベントの定義
この例ではTesterというクラスにPopというイベントを宣言しています。プロパティやメソッドと違って内容の記述はありません。イベントが発生したときにどのような動作を行うかはイベントの受け側(たとえばForm)に記述するからです。
また、 上記のコードはイベントが宣言されているだけなので自動的にイベントが発生するようなことはありません。イベントを発生させるにはRaiseEventステートメント(読み方:RaiseEvent = レイズイベント)を使用します。
Public
Class Tester Public Event Pop(ByVal sender As Object, ByVal e As EventArgs) Public Sub DoAnything() RaiseEvent Pop(Me, New EventArgs) End Sub End Class |
■リスト2:イベントを定義・発生させる最もシンプルな例
これでイベントを発生させる側は完成です。TesterクラスのDoAnythingメソッドを呼び出すとPopイベントが発生します。この構造ではイベントが何の役にも立たないのですが、イベントを発生させるシンプルな例として記憶に留めておくとよいでしょう。
このTesterクラスのPopイベントを受け取るコードも見てみましょう。どのようなクラスでもイベントを受け取ることができますが、見慣れているフォームの例で説明します。フォームにButtonを貼り付けて次の通りに記述して下さい。
Dim WithEvents Tester1 As New Tester |
Private
Sub
Button1_Click(ByVal
sender As
System.Object, ByVal
e As
System.EventArgs) Handles
Button1.Click |
Private
Sub
Tester1_Pop(ByVal
sender As Object,
ByVal e
As
System.EventArgs) Handles
Tester1.Pop |
■リスト3:フォーム側のプログラム。Testerクラスのテスト。
Button1をクリックするとTester1のDoAnythingメソッドが呼ばれます。そして、上述のようにDoAnythingメソッドは特に何もしないでPopイベントを発生させるので画面には「Pop!」というメッセージボックスが表示されます。
ポイントはTester型の変数が単なるWithEvents(読み方:WithEvents = ウィズイベンツ)付きで宣言されていることです。イベントを受け取るためにはこのWithEventsを使って変数を宣言 するのが一般的です。イベントをトラップするのにHanldesではなく、AddHandlerを使う場合はWithEventsで宣言されていなくても問題ありません。
WithEventsを使っていればButtonやTextBoxと同じようにイベントプロシージャを生成してイベントを受け取ることができます。
それではもう少し詳細にイベントの定義を眺めてみましょう。イベントプロシージャには必ず2つの引数がありますからイベントを定義するときも2つの引数を指定しなければなりません。
このことは第10回 イベントプロシージャで既に説明していますが簡単に復習してみます。
イベントプロシージャの第1引数はObject型で、イベントを発生させたクラスを表すことになっています。ですからイベントを発生させるクラスの側から見るとほとんどの場合第1引数にはMeをセットすることになります。
第2引数はイベントの追加情報でEventArgs型またはEventArgsクラスを継承したクラスを指定することになっています。追加情報が特に必要ない場合は単純にEventArgsクラスの新しいインスタンスを指定するだけで構いません。
イベントプロシージャの引数 | セットすべき内容 |
sender As Object | イベントを発生させるクラス側ではほとんどの場合Meを設定する。 |
e As EventArgs | 追加情報が必要なければEventArgsクラスの新しいインスタンスを指定する。追加情報が必要な場合はEventArgsクラスを継承したクラスを指定する。 |
■表1:イベントプロシージャの引数
以上はほとんどのプログラマが従っているルールですが、絶対にこのようにしなければならないというルールではありません。たとえば第1引数の名前をsender以外のものにして良いですし、各引数の型を変更しても構いません。それどころか3つ以上の引数を指定することも可能です。
けれどわざわざルールから逸脱しないで、他のみなさんと同じ作りにしておいてください。1人だけ違う作りにしているとかなり使いにくいプログラムができあがってしまいます。
ルールを守って作ると先ほどのプログラムと同じような定義と呼び出しになります。
定義
Public Event Pop(ByVal sender As Object, ByVal e As EventArgs) |
■リスト4:イベントの定義
呼び出し
RaiseEvent Pop(Me, New EventArgs) |
■リスト5:イベントの発生
次に追加情報が必要なイベントを説明しましょう。
追加情報の例としてはフォームなどのMouseMoveイベントでは第2引数を使ってマウスの座標を取得することができますし、KeyPressイベントでは押されたキーを取得することができます。このような仕組みを自作のクラスにも組み込んでみます。
ここではサンプルとしてあらかじめ最大値を設定できる数値型であるAlarmIntegerクラスを作成します。
たとえば最大値を100と決めておけば、100を超える値をセットしようとした瞬間にイベントが発生するようにします。
このイベントをOverイベントと名付けこれからいろいろと試してみましょう。手始めにOverイベントでは追加情報としてセットしようとしている値と現在の値を取得できるようにします。
イベントの追加情報以外の部分はここまでの説明を組み合わせれば作成可能なはずですからまずは自力で作成してみてください。
まずは追加情報は抜きにして最大値を超える値をセットするときにOverイベントを発生させる部分までの全コードを示します。
Public
Class AlarmInteger Private m_Value As Integer Private m_Max As Integer Public Event Over(ByVal sender As Object, ByVal e As EventArgs) |
Public
Sub
New(ByVal
max As Integer) |
'■Value ''' <summary>整数値を取得または設定します。</summary> Public Property Value() As Integer Get Return m_Value End Get Set(ByVal value As Integer) If value > Me.Max Then 'セットしようとしている値が最大値を超える場合にOverイベントを発生させる。 RaiseEvent Over(Me, New EventArgs) End If m_Value = value End Set End Property |
'■Max ''' <summary>Valueプロパティの最大値を取得または設定します。</summary> Public Property Max() As Integer Get Return m_Max End Get Set(ByVal value As Integer) m_Max = value End Set End Property End Class |
■リスト6:最大値を超えるとOverイベントが発生するAlarmIntegerクラス。
これを利用する側の例も示します。ここでは試しやすいようにフォームで使用する例を作成しました。
AlarmIntegerクラスのインスタンスの名前はiで、最大値はコンストラクタを使って100にしました。Button1をクリックするごとにValueは34ずつ増加しまから、3回目にButton1をクリックした時点でOverイベントが発生します。
Dim WithEvents i As New AlarmInteger(100) '最大値に100を指定 |
Private
Sub
Button1_Click(ByVal
sender As
System.Object, ByVal
e As
System.EventArgs) Handles
Button1.Click i.Value += 34 End Sub |
Private
Sub i_Over(ByVal
sender As Object,
ByVal e
As
System.EventArgs) Handles
i.Over MsgBox("OVER!") End Sub |
■リスト7:フォーム側のプログラム。AlarmIntegerクラスのテスト。
この構造を図にしてみましょう。
■画像1:イベント発生の仕組み
この図ではプログラムの中を値がどのように飛び交っているかを示しています。この後でどんどん複雑な例が登場しますが混乱してきたらこの図に戻って値の流れを確認して下さい。
フォーム側でi.Value += 34が実行されると、ただちにAlarmIntegerクラス側のValueプロパティのSet節が実行されます。このときに引数valueにはセットしようとした値、つまり34が渡されます。もちろん34ずつ足していくのですから、ボタンをもう1回クリックすると68、さらにクリックすると102が渡されることになります。ここまではイベントではなくプロパティの話です。
なお、図の都合上i.Valueの値がSet節のvalue引数に渡るように表現されていますが、実際にはi.Valueにセットしようとしている値がvalue引数に渡されるだけです。この時点ではValueプロパティの値はまだ変更されていません。それからプロパティの名前と引数の名前が同じなのは偶然です。
このあとSet節でRaiseEventを使ってOverイベントを発生させます。ただし、Ifで条件がついているので値が100以下の場合は発生しません。イベントが発生すると直ちにフォーム側のOverイベントプロシージャに制御が移りますが、このとき通常のメソッドの設定した引数も同時に渡されます。それでこの引数を使って情報の受け渡しができるわけです。
構造について理解していただいたところで、このコードを改造してOverイベントの追加情報として現在の値と変更後の値を取得できるようにします。
まず、追加情報を格納するためのクラスを作成します。このクラスがイベントの第2引数となるわけです。追加情報としては現在の値と変更後の値の2つの情報が必要です。
以上のことを考え合わせると作成すべきクラスはEventArgsクラスに2つのプロパティを追加したクラスと言うことになります。
ここでは「継承」という方法を使ってEventArgsクラスの機能を取り込んでしまいます。継承については中級講座で扱いますが、とりあえずクラスを宣言するときにInheritsキーワード(読み方:Inherits = インヘリツ)を使うと既に存在するクラスの機能がプログラムなしでそのまま使えるようになることだけを覚えておいてください。これが継承と言う方法です。
継承を使えば細かい部分を自分で書く必要がないので(書いたとしてもこの場合はたかが知れていますが)、実際に記述するのは2つのプロパティだけです。このクラスは次のようになります。
Public
Class
AlarmOverEventArgs Inherits EventArgs 'この1行でEventArgsのすべての機能を実装します。これが「継承」です。 Dim m_CurrentValue As Integer |
''' <summary>現在の値を取得します。 </summary>Public Property CurrentValue() As Integer Get Return m_CurrentValue End Get Set(ByVal value As Integer) m_CurrentValue = value End Set End Property |
Dim
m_NewValue As
Integer |
■リスト8:AlarmIntegerクラスのOverイベントの第2引数となるAlarmOverEventArgsクラス
ところで、このクラスは別にInherits EventArgsの行がなくても正常に動作しますが、イベントプロシージャの第2引数はどんな場合でもEventArgsを継承するのが決まり というの全世界のプログラマの総意となっているのでここでは決まりに従って記述しました。
この決まりは人間用の決まりなのでInherits EventArgsがなくてもVBがエラーになったりすることはありません。
さて、AlarmInteger側ではイベントの宣言部分とイベントを発生させる部分のコードを改造してこの新しいAlarmOverEventArgsクラスを使用するように改造します。
Public Class
AlarmInteger Private m_Value As Integer Private m_Max As Integer Public Event Over(ByVal sender As Object, ByVal e As AlarmOverEventArgs) |
Public Sub New(ByVal
max As Integer) Me.Max = max End Sub |
'■Value ''' <summary>整数値を取得または設定します。</summary> Public Property Value() As Integer Get Return m_Value End Get Set(ByVal value As Integer) If value > Me.Max Then 'セットしようとしている値が最大値を超える場合にOverイベントを発生させる。 Dim e As New AlarmOverEventArgs e.CurrentValue = Me.Value '現在の値をセット e.NewValue = value '新しい値をセット RaiseEvent Over(Me, e) End If m_Value = value End Set End Property |
'■Max ''' <summary>Valueプロパティの最大値を取得または設定します。</summary> Public Property Max() As Integer Get Return m_Max End Get Set(ByVal value As Integer) m_Max = value End Set End Property End Class |
■リスト9:クラス側のプログラム。Overイベント発生時に追加情報を渡す。
さきほどのプログラムからの変更部分には色が付けてあります。
まずイベントの宣言の第2引数の型をEventArgs型からAlarmOverEventArgs型に変更しています。
それからイベントを発生させるときにイベントの発生直前でAlarmOverEventArgs型の変数に情報をセットしてその変数をイベントの第2引数として渡しています。このようにすることでイベントを受け取る側、つまりフォームは第2引数を経由してさまざまな情報を受け取ることができるようになるのです。
以上の変更に加えてフォーム側でも変更を行う必要があります。Overイベントプロシージャの第2引数の型を変更しなければなりません。
メモ -
指定されているシグネチャが異なります。 フォーム側を修正する前の状態では、フォーム側のOverイベントの部分でエラーが発生し、「(前略)…。シグネチャが異なります。」というメッセージが表示されます。 これはVBがメソッドやプロパティを「名前」と「引数の型」と「引数の順番」の3つの要素で区別していることと関係があります。シグネチャとはこの3つの要素の組み合わせのことです。 Overイベントプロシージャの最初の状態ではシグネチャは「名前=i_Over, 引数の型と順番=Object, EventArgs」でした。このことをよくi_Over(Object, EventArgs)のように記述します。 ところが、後でAlarmIntegerクラスを修正したのでOverイベントのシグネチャはi_Over(Object, AlarmOverEventArgs)でなければなりません。それで「シグネチャが異なります」というエラーが発生するわけです。 なお、i_Overはイベントプロシージャなのでこの場合は名前(i_Over)は重要ではありません。後ろのHandles i.Overの方が本命です。このあたりはイベントプロシージャが他のメソッドやプロパティとちょっと違う点です。 |
本当に情報が正しく渡されてくるかを判断するためのコードも追加すると、フォーム側は次のようになります。
Dim WithEvents i As New AlarmInteger(100) '最大値にを指定 |
Private Sub
Button1_Click(ByVal sender
As System.Object,
ByVal e As System.EventArgs)
Handles Button1.Click i.Value += 34 End Sub |
Private
Sub i_Over(ByVal
sender As Object,
ByVal e As
AlarmOverEventArgs)
Handles i.Over MsgBox("Overです! 現在の値は" & e.CurrentValue & ", 新しい値は" & e.NewValue) End Sub |
■リスト10:フォーム側のプログラム。AlarmIntegerクラスのテスト。
これで第2引数を使って自由に情報を渡すことができるようになりました。
さらに第2引数を拡張して今度はキャンセルができるようにします。つまり最大値を超える値を設定された場合プログラマがその値を受け入れるか拒否するかイベントプロシージャを使って指定できるようにします。
このようなキャンセルの例としてはFormClosingイベントがあります。ただしこのイベントはVB2005で新たに追加されたもので す。VB.NET2002およびVB.NET2003では同じようなClosingイベント を使うとキャンセルを行うことができます。
自分でキャンセル機能を組み込む前にキャンセルの使い方を確認しておきましょう。
フォームのFormClosingイベント(またはClosingイベント)に次のようにコードを記述して実行してみてください。
Private
Sub
Form1_FormClosing(ByVal
sender As Object,
ByVal e
As
System.Windows.Forms.FormClosingEventArgs)
Handles
Me.FormClosing Dim Answer As MsgBoxResult Answer = MsgBox("フォームを閉じますか?", MsgBoxStyle.Question Or MsgBoxStyle.YesNo Or MsgBoxStyle.DefaultButton2) If Answer = MsgBoxResult.No Then e.Cancel = True End If End Sub |
■リスト11:フォームのFormClosingイベントを使ったキャンセルの例
この例ではフォームを閉じるタイミングで「フォームを閉じますか?」と言うメッセージを表示し、ユーザーが[いいえ]を選択した場合にはフォームが閉じるのをキャンセルします。
実際のキャンセルはe.Cancel = Trueという1行に示されています。
このプログラムでもe、つまりイベントプロシージャの第2引数は単なる情報に過ぎずe.Cancel = True自体にフォームの閉鎖をキャンセルする機能があるわけではありません。イベントを発生させた側がイベント発生後にe.Cancelの値を調べて、もし値がTrueであるならばフォームの閉鎖を中止するようにプログラムされているのです。
この仕組みを先ほどのAlarmIntegerクラスの実装して、Overイベントが発生したときに値のセットをキャンセルできるようにしましょう。
実はいままでの説明を組み合わせればこのような動作は実現できるので意欲的な方はサンプルをご覧になる前に自分で試されてみてはいかがでしょうか?
まず、やるべきことはAlarmOverEventArgsクラスにCancelプロパティを追加することです。これは最早書くまでもないと思いますが一応どのようになるかコードを載せておきます。
Public
Class
AlarmOverEventArgs Inherits EventArgs 'この1行でEventArgsのすべての機能を実装します。これが「継承」です。 Dim m_Cancel As Boolean |
Public Property Cancel() As BooleanGet Return m_Cancel End Get Set(ByVal value As Boolean) m_Cancel = value End Set End Property |
Dim m_CurrentValue As Integer''' <summary>現在の値を取得します。</summary> Public Property CurrentValue() As Integer Get Return m_CurrentValue End Get Set(ByVal value As Integer) m_CurrentValue = value End Set End Property |
Dim m_NewValue As Integer''' <summary>新しい値を取得します。</summary> Public Property NewValue() As Integer Get Return m_NewValue End Get Set(ByVal value As Integer) m_NewValue = value End Set End Property End Class |
■リスト12:AlarmOverEventArgsクラスへCancelプロパティを追加
この例ではなんの工夫もなくCancelプロパティを追加しましたが、実はこのキャンセルの仕組みはよく使うのであらかじめVBに用意されています。InheritsのところでEventArgsではなく、CancelEventArgsを指定するとCancelプロパティを自分で書かなくてもすぐに使えるようになります。このCancelEventArgsクラスはSystem.Component名前空間に属しています。
ともあれ今は自分でCancelプロパティを記述しておきましょう。実際のイベントを発生させる側とイベントを処理する側の連携の仕組みを自分の目で見ていただきたいからです。
次にAlarmIntegerクラスのコードです。
Public Class
AlarmInteger Private m_Value As Integer Private m_Max As Integer Public Event Over(ByVal sender As Object, ByVal e As AlarmOverEventArgs) |
Public Sub New(ByVal
max As Integer) Me.Max = max End Sub |
'■Value ''' <summary>整数値を取得または設定します。</summary> Public Property Value() As Integer Get Return m_Value End Get Set(ByVal value As Integer) If value > Me.Max Then 'セットしようとしている値が最大値を超える場合にOverイベントを発生させる。 Dim e As New AlarmOverEventArgs e.CurrentValue = Me.Value '現在の値をセット e.NewValue = value '新しい値をセット RaiseEvent Over(Me, e) If e.Cancel = True Then Return End If End If m_Value = value End Set End Property |
'■Max ''' <summary>Valueプロパティの最大値を取得または設定します。</summary> Public Property Max() As Integer Get Return m_Max End Get Set(ByVal value As Integer) m_Max = value End Set End Property End Class |
■リスト13:クラス側のプログラム。キャンセルを実装。
見るとわかるようにイベント発生後にCancelプロパティの値をチェックし、Trueであれば何もしないで処理を終了するようにしています。当たり前のことですがこのチェックがイベント終了後であることが重要です。イベントの終了後であるからこそイベントプロシージャでフォーム側のプログラムがe.Cacnelに何をセットしたかがわかるのです。
フォーム側のプログラムはケースバイケースになりますがとりあえずオーバーする値は単純に拒否するようにしておきましょう。
値のセットが拒否されていることがわかるようにラベルに現在の値を表示するコードも追加してみました。
Dim WithEvents i As New AlarmInteger(100) '最大値に100を指定 |
Private
Sub
Button1_Click(ByVal
sender As
System.Object, ByVal
e As
System.EventArgs) Handles
Button1.Click |
Private
Sub i_Over(ByVal
sender As Object,
ByVal e
As
AlarmOverEventArgs) Handles
i.Over |
■リスト14:フォーム側のプログラム。AlarmIntegerクラスのテスト。
これで一応まとまった機能のあるクラスが完成しましたが、もうちょっと機能強化してみましょう。
以下の2つの機能を実装して見ます。
・最大値を超える値はセットできないようにする。
最大値を超える値をセットしようとしたときには値は自動的に最大値になり、Overイベントは発生しません。
たとえば、最大値が100の時に120をセットしようとすると値は100になります。
・Overイベントでセットする値を自由に指定できるようにする。
たとえば、最大値が100の時に120をセットしようとしたときにどうするかはプログラムがその都度決めます。100にすることもできますし、120やその他の値にすることもできます。
まずは、最大値を超える値をセットしようとしたら必ず最大値になるというのをプログラムしてみましょう。
この設計だと絶対に値がオーバーしないのでOverイベントを発生しないようにしてしまいますが、それはそれで柔軟性がなくなってしまうので今まで通りに動作するか、最大値を 超える値をセットできないように動作するか動作モードをAllowOverプロパティとして実装することにします。
AllowOver = Trueのときは従来どおりの動作です。AllowOver = Falseの時はオーバーする値をセットしようとすると自動的にMax値がセットされOverイベントは発生しません。
メモ 今回は作りませんが最大値を超える値をセットしようとしたときでもOverイベントを発生させるべきだと言う考え方もあるかもしれません。腕試しにそのように改造してみるのも一興です。ごく簡単な改造で済みます。 |
完成版を見る前に練習代わりにまずは自分で作ってみてください。
完成版は次の通りです。
Public Class
AlarmInteger Private m_Value As Integer Private m_Max As Integer Private m_AllowOver As Boolean = True Public Event Over(ByVal sender As Object, ByVal e As AlarmOverEventArgs) |
Public Sub New(ByVal
max As Integer) Me.New(max, True) End Sub |
Public
Sub New(ByVal
max As Integer,
ByVal allowOver As
Boolean) Me.Max = max Me.AllowOver = allowOver End Sub |
'■AllowOver ''' <summary>Maxプロパティの値を超える代入を許可するかを示します。</summary> Public Property AllowOver() As Boolean Get Return m_AllowOver End Get Set(ByVal value As Boolean) m_AllowOver = value End Set End Property |
'■Value ''' <summary>整数値を取得または設定します。</summary> Public Property Value() As Integer Get Return m_Value End Get Set(ByVal value As Integer) If value > Me.Max Then If AllowOver Then 'セットしようとしている値が最大値を超える場合にOverイベントを発生させる。 Dim e As New AlarmOverEventArgs e.CurrentValue = Me.Value '現在の値をセット e.NewValue = value '新しい値をセット RaiseEvent Over(Me, e) If e.Cancel = True Then Return End If Else value = Me.Max End If End If m_Value = value End Set End Property |
'■Max ''' <summary>Valueプロパティの最大値を取得または設定します。</summary> Public Property Max() As Integer Get Return m_Max End Get Set(ByVal value As Integer) m_Max = value End Set End Property End Class |
■リスト15:クラス側のプログラム。最大値を超える値をセットしようとしたら必ず最大値になる。
これを試すフォーム側のプログラムは次のようになります。
Dim WithEvents i As New AlarmInteger(100, False) '最大値に100を指定 |
Private Sub
Button1_Click(ByVal sender
As System.Object,
ByVal e As System.EventArgs)
Handles Button1.Click i.Value += 34 Label1.Text = i.Value End Sub |
Private
Sub i_Over(ByVal
sender As Object,
ByVal e As
AlarmOverEventArgs) Handles i.Over MsgBox("Over!") End Sub |
■リスト16:フォーム側のプログラム。AlarmIntegerクラスのテスト。
一応Overイベントプロシージャも書いておきましたが、これはむしろOverイベントが発生しないことを確認するためのものです。
AllowOverプロパティの値をいちいちセットするのは面倒なので、コンストラクタでAllowOverプロパティの値をセット してみました。もちろん他のプロパティと同様にフォームのLoadイベントなどでAllowOverプロパティの値をセットすることもできます。
次にOverイベント発生時に値をどうするのか自由に指定できるように改造してみます。先ほどの改造ではキャンセルできるようにしたり、自動的に最大値がセットされたりするようにはできましたが自由な値を指定することはできませんでした。
たとえば、最大値が100の時に120をセットしようとしたら値を20にするなど、プログラム中で自由に指定できるようにします。
これもまずは自分で挑戦してみてください。
方針としてはやはりOverイベント回りを改造することになります。
Overイベントで第2引数を使ってキャンセルかどうか判断したように、新しい値も第2引数を経由して受け渡しすればよいのです。キャンセルを実装したときは第2引数にCancelプロパティを追加しましたが、今回は既にNewValueというプロパティがあるのでこれを使用しましょう。
つまり、イベントの呼び出し側ではキャンセルされなかった場合は常にNewValueに設定されている値が文字通り新しい値となるようにすればよいのです。
次のようになります。
Public Class
AlarmInteger Private m_Value As Integer Private m_Max As Integer Private m_AllowOver As Boolean = True Public Event Over(ByVal sender As Object, ByVal e As AlarmOverEventArgs) |
Public Sub New(ByVal
max As Integer) Me.New(max, True) End Sub |
Public Sub
New(ByVal max
As Integer,
ByVal allowOver As
Boolean) Me.Max = max Me.AllowOver = allowOver End Sub |
'■AllowOver ''' <summary>Maxプロパティの値を超える代入を許可するかを示します。</summary> Public Property AllowOver() As Boolean Get Return m_AllowOver End Get Set(ByVal value As Boolean) m_AllowOver = value End Set End Property |
'■Value ''' <summary>整数値を取得または設定します。</summary> Public Property Value() As Integer Get Return m_Value End Get Set(ByVal value As Integer) If value > Me.Max Then If AllowOver Then 'セットしようとしている値が最大値を超える場合にOverイベントを発生させる。 Dim e As New AlarmOverEventArgs e.CurrentValue = Me.Value '現在の値をセット e.NewValue = value '新しい値をセット RaiseEvent Over(Me, e) If e.Cancel = True Then Return End If value = e.NewValue Else value = Me.Max End If End If m_Value = value End Set End Property |
'■Max ''' <summary>Valueプロパティの最大値を取得または設定します。</summary> Public Property Max() As Integer Get Return m_Max End Get Set(ByVal value As Integer) m_Max = value End Set End Property End Class |
■リスト17:クラス側のプログラム。Overイベント発生時に値をどうするのか自由に指定できる。
このようにたった1行を追加するだけですみます。
これを試すフォーム側のコード例も紹介します。
Dim WithEvents i As New AlarmInteger(100, True) '最大値に100を指定 |
Private Sub
Button1_Click(ByVal sender
As System.Object,
ByVal e As System.EventArgs)
Handles Button1.Click i.Value += 34 Label1.Text = i.Value End Sub |
Private
Sub i_Over(ByVal
sender As Object,
ByVal e As
AlarmOverEventArgs) Handles i.Over e.NewValue = e.NewValue - i.Max End Sub |
■リスト18:フォーム側のプログラム。AlarmIntegerクラスのテスト。
これを実行するとラベルの値は34 → 68 → 2 → 36 → 70 → 4 というように変化していきます。
最後にイベントの利用方法について再確認しておきます。
普段ButtonのClickイベントやFormのLoadイベントなどを使っていると「イベントと言うのは何かあったときに自動的にプロシージャを呼び出すことができる機能」というように思ってしまいますが、実はそれだけではありません。
イベントと言うのはクラスとクラスが連携するための1つの手段なのです。一方的な呼び出しの場合もありますし、双方向的な情報のやりとりということもあります。
もし、あなたがプログラムしているときにクラスとクラスをうまく連携させる方法が思いつかなかったらイベントのことを思い出してください。
以下で具体例を紹介しつつクラス間の連携で考慮すべきことを説明します。
ClassAがClassBから情報を受け取るにはどのような手段があるでしょうか?もっとも単純でしかも実際に役に立つ手段はClassB側にPublicなどで外部から参照できるプロパティまだはメソッドを用意しておいてClassAからそれを呼び出すと言うものです。
次はコードはそのイメージです。
Public
Class ClassA Public B As New ClassB |
Public
Sub
New() |
■リスト19:ClassAのプログラム。ClassBの情報を取得できる。
Public
Class ClassB Public ReadOnly Property Info() As Object Get 'ここでClassBは外部のクラスに情報を渡す。 Return "ここで任意の情報を返すことができます。" End Get End Property End Class |
■リスト20:ClassBのプログラム。ClassAに情報を渡せるようにInfoプロパティを実装。
この段階でのClassAとClassBでの情報の交換は次の通りです。
ClassAのプログラム | ClassBのプログラム | |
ClassAがClassBの情報を 取得する |
ClassBのプロパティを呼び出す。 Info = B.Info |
Publicなプロパティを実装。 Public ReadOnly Property Info ... |
ClassBがClassAの情報を 取得する |
× | × |
■表2
問題が複雑になってくるのはこの先です。今後は逆にClassBがClassAの情報を受け取る方法を考えてみてください。
これも多くのケースではシンプルに解決できます。ClassB側でClassA型のプロパティを用意しておけばよいのです。ClassA側ではClassBを使用するときにはまずそのプロパティに自分自身をセットするようにします。このプロパティにはコンストラクタで値をセットできるようにしておくと便利でしょう。
後はClassB側ではいつでもそのプロパティを経由してClassAの情報が受け取れるわけです。必要に応じてClassAでもPublicなど外部から参照できる形でプロパティやメソッドを用意しておきます。
この方法では次のようになります。
Public
Class ClassA Public B As New ClassB(Me) |
Public
Sub
New() |
Public ReadOnly Property Info() As ObjectGet 'ここでClassAは外部のクラスに情報を渡す。 Return "ここで任意の情報を返すことができます。" End Get End Property End Class |
■リスト21:ClassAのプログラム。ClassBに情報を渡せるようにInfoプロパティを実装。
Public
Class ClassB Private m_Parent As ClassA |
Public
Sub
New(ByVal
parent As
ClassA) m_Parent = parent End Sub |
Public
ReadOnly Property
Info() As
Object Get 'ここでClassBは外部のクラスに情報を渡す Return "ここで任意の情報を返すことができます。" End Get End Property |
Public
Sub
DoAnything() Dim Info As Object Info = m_Parent.Info 'ここでClassBは外部のクラス(ClassA)の情報を取得できる。 End Sub End Class |
■リスト22:ClassBのプログラム。ClassAの情報を受け取れるようにコンストラクタでClassAのインスタンスを取得する。
この方法ではClassAとClassBの連携については完璧に動作します。ただし、ClassA側にInfoプロパティが用意されていることが前提です。
この段階でのClassAとClassBでの情報の交換は次の通りです。
ClassAのプログラム | ClassBのプログラム | |
ClassAがClassBの情報を 取得する |
ClassBのプロパティを呼び出す。 Info = B.Info |
Publicなプロパティを実装。 Public ReadOnly Property Info ... |
ClassBがClassAの情報を 取得する |
Publicなプロパティを実装。 Public ReadOnly Property Info ... |
ClassAのプロパティを呼び出す。 ClassAのインスタンスはコンストラクタで受け取る。 Info = m_Parent.Info |
■表3
同じことをイベントを使って実現することもできます。そのメリットは後で考えることにしてとりあえず例を見ていただきましょう。
Public
Class ClassA Public WithEvents B As New ClassB |
Public
Sub
New() Dim Info As Object Info = B.Info 'ここでClassAはClassBの情報を取得できる。 End Sub |
Private
Sub
B_RequestInfo(ByVal
sender As Object,
ByVal e
As
RequestInfoEventArgs) Handles
B.RequestInfo 'ここでClassAは外部のクラスに情報を渡す e.Info = "ここで任意の情報を返すことができます。" End Sub End Class |
■リスト23:ClassAのプログラム。ClassBに情報を渡せるようにRequestInfoイベントプロシージャを実装。
Public
Class ClassB Public Event RequestInfo(ByVal sender As Object, ByVal e As RequestInfoEventArgs) |
Public
ReadOnly Property
Info() As
Object Get 'ここでClassBは外部のクラスに情報を渡す Return "ここで任意の情報を返すことができます。" End Get End Property |
Public
Sub
DoAnything() Dim e As New RequestInfoEventArgs Dim Info As Object RaiseEvent RequestInfo(Me, e) 'ここでClassBは外部のクラス(ClassA)の情報を取得できる。 Info = e.Info End Sub End Class |
■リスト24:ClassBのプログラム。ClassAから情報を受け取るためにイベントを発生させる。
Public
Class RequestInfoEventArgs Inherits EventArgs Private m_Info As Object Public Property Info() As Object Get Return m_Info End Get Set(ByVal value As Object) m_Info = value End Set End Property End Class |
■リスト25:ClassBのRequestInfoイベントの第2引数であるRequestInfoEventArgsクラス
この段階でのClassAとClassBでの情報の交換は次の通りです。
ClassAのプログラム | ClassBのプログラム | |
ClassAがClassBの情報を 取得する |
ClassBのプロパティを呼び出す。 Info = B.Info |
Publicなイベントプロシージャを実装。 Public ReadOnly Property Info ... |
ClassBがClassAの情報を 取得する |
RequestInfoイベントプロシージャを実装して第2引数に情報を渡す。 e.Info = ... |
RequestInfoイベントを発生させて第2引数から情報を受け取る。
RaiseEvent
RequestInfo(Me,
e) |
■表4
さて、イベントを使って情報を交換するメリットを考えて見ましょう。プロパティを使う場合とイベントを使う場合とでClassA側のプログラムがどのようになるか表にしてみました。
プロパティを使う場合 ClassAのプログラム |
イベントを使う場合 ClassAのプログラム |
|
ClassBがClassAの情報を取得する | Publicなプロパティを実装 | イベントプロシージャを実装 |
■表5
表を見るとわかるように結局のところClassA側にプロパティを作る必要があるのかそれともイベントプロシージャを作る必要があるのかという問題に帰着します。
そしてプロパティは必ず実装しなければエラーになってしまいますが、イベントプロシージャは必要なときだけ実装すればよいことを思い出してください。TextBoxを貼り付けたら必ずKeyPressイベントプロシージャに何か書かなくてはいけないと言うことはなかったでしょう。
また、イベントプロシージャは必要であれば簡単に実装できるのに対し、プロパティを実装しようとするとどのような型でどのような引数のプロパティが必要であるか人間が覚えておかなければならない点も重要です。たとえば「ClassBをインスタンス化するクラスには必ずObject型で引数なしのInfoプロパティを作成しなければならない」のような約束事が増えてしまって面倒なことになる可能性があるのです。
もっともこの点に関しては中級講座で扱う予定の「インターフェース」という技術を用いれば全く改善されます。
もちろんプロパティを使った方が便利な場合もあります。たとえば、さきほど「プロパティは必ず実装しなければエラーになっていしまいます」と書きましたが逆に言うと実装し忘れてしまうということがなくなります。
また、イベントを使った場合は適切な第2引数を別にクラスとして用意しなければならないのでいささか面倒です。
以上のことを考えると、イベントを使ったクラス間の連携は柔軟性に優れ、プロパティを使ったクラス間の連携は確実性に優れていると言えます。柔軟性よりも確実性を重視する場合もありますからどのような方法で連携すべきかはケースバイケースです。ここではイベントを使った連携が可能であることを記憶しておいて選択肢の幅を広げてください。
メモ 本文中で「プロパティを使ったクラス間の連携」のような表現を随所でしていますが、メソッドを使った連携も可能です。この場合はプロパティを使った連携とまったく同じ事情になるので説明を割愛しました。 また、プロパティおよびメソッドを使って連携する場合は継承やインターフェースのことも知っておいてください。 |