#1 フォームのオープン・クローズ

ここに出てくるイベント
  • Open/開く時
  • Load/読み込み時
  • Resize/サイズ変更時
  • Activate/アクティブ時
  • Current/レコード移動時
  • Unload/読み込み解除時
  • Deactivate /非アクティブ時
  • Close/閉じる時


イベントトレースの準備

それではまず、Accessの数あるイベントの中から、フォームのオープン・クローズに関わるイベントについて調べていきましょう。

まず、どのようにしてイベントの発生を調べるか、その方法を決めておかなければなりません。このシリーズで特に重点をおいているのは、各イベントがどのような順序で発生し、そのイベントをどのように利用したらよいか検討することです。例えばフォームならフォームが、どのようなイベントを持っているか、それぞれのイベントがどのようなときに発生するかなどは、ヘルプを参照すれば調べることができます。しかし、実際にどのようなタイミングで発生するかなどは、フォームを開いたり閉じたりしてみなければ分かりません。もちろん、一般的に発生する順序は、Accessのヘルプにも書かれていますが、状況やプログラムの作りによっては、発生しないイベントがあったり、他のイベントを発生させたりすることもあるためです。それらをしっかりチェックするには、やはり1つ1つのイベントの発生をトラッキングしていく必要があります。そのための"イベントトレースツール"のようなものがあれば便利なのですが、Access内のイベントをトレースするようなツールはありません。

1つの方法として、すべてのイベントについてイベントプロシージャを作成し、そのイベントが発生したらメッセージを表示させるという方法があります。例えば、フォームのイベントをトレースするために、次のようなイベントプロシージャを記述する方法です。

Private Sub Form_Load()

  MsgBox "読み込み時イベントが発生しました"

End Sub

Private Sub Form_Open(Cancel As Integer)

  MsgBox "開く時イベントが発生しました"

End Sub


しかし、この方法では、山ほどあるすべてのイベントについて、イベントビルダを使ったりしてイベントプロシージャを作成したり、コードウィンドウへの書き込みを行ったりと、大変手間がかかります。そこで今回は、少しでもその手間を省くため、次のようにして各イベントの発生を追いかけるようにしました。

まず、標準モジュールに次のようなFunctionプロシージャ"TrackEvents()"を作成します。

Function TrackEvents(strObjName As String, strEventName As String)

  Static intNum As Integer
  
  intNum = intNum + 1

  Debug.Print Format$(intNum, "###  ");
  Debug.Print strObjName, strEventName

End Function

このプロシージャは、2つの引数を持ちます。1つ目がイベントを発生させたオブジェクトを表する文字列変数です。例えば、"frmフォーム1"や"txtテキストボックス1"などの文字列を受け取ります。2つ目の引数は、発生したイベント名を表す文字列変数です。"Load"や"開く時"といった、任意の表現の文字列を受け取ります。いずれかのイベントが発生するたびにインクリメントされる番号(intNum)と、2つの引数をそのまま Debug.Print を使ってイミディエイトウィンドウに書き出すだけの単純なプログラムです。ただ、イベント発生順を表す変数 intNum はStaticで宣言されていますので、何回もテストを続けていくと、ひたすらインクリメントされていってしまいます。そのため、あるテストを開始する前には、VBEのツールバーより[リセット]を実行して、初期状態、つまりゼロの状態に戻すように注意します。


プロパティシートの設定次に、このプロシージャの呼び出し方法です。それには、各オブジェクトのプロパティシートに、このプロシージャの呼び出しを直接記述します。例えば、今回はフォームのイベントについて調べますので、"フォーム"オブジェクトの各プロパティを右図のように設定します。

こうすることによって、「イベント」に関する呼び出しをすべてプロパティシート上だけで管理できますし、個々にイベントプロシージャを生成する手間も省けます(それでも、たくさんあるイベントごとに引数を書き換えるのは手間がかかりますが)。


これでイベントの発生をトレースするための準備が整いました。いずれかのイベントが発生すれば、プロシージャ"TrackEvents()"が、そのイベント式に指定された引数で呼び出されますので、どのオブジェクトのどのイベントが、どのような順番で発生したか、イミディエイトウィンドウで知ることができます。



フォームイベントのトレース

ここではサンプルとして"frm顧客マスタ"というフォームを用意しました。このフォームはテーブル"tbl顧客マスタ"をレコードソースとして、テーブルに連結されています。
サンプルフォームの画面

それでは、このフォームを開いて、すぐにそのまま閉じるという操作をしてみましょう。

実行後、VBE画面に移動して、イミディエイトウィンドウを確認してみます。その結果は次のようになりました。

発生順序 オブジェクト名 イベント名
1 frm顧客マスタ 開く時
2 frm顧客マスタ 読み込み時
3 frm顧客マスタ サイズ変更時
4 frm顧客マスタ アクティブ時
5 frm顧客マスタ レコード移動時
6 frm顧客マスタ 読み込み解除時
7 frm顧客マスタ 非アクティブ時
8 frm顧客マスタ 閉じる時

ここでは、1〜5までがフォームが開く際のイベント、6以降が閉じる際のイベントです。この様子を文章で表すと、次のような感じになるのではないでしょうか。なにしろ、内部的な動作ですので、あくまでも想像の域を出ない表現もありますが、ご了承ください。

まず、フォームが開く際のイベントです。
  1. データベースファイル内に格納されているフォームオブジェクトの保存場所が開かれる
  2. フォームオブジェクトの内容(デザインや各プロパティの設定など)がメモリに読み込まれる
  3. Accessウィンドウに対するフォームの位置やサイズ合わせ、センタリングなど、フォームの位置とサイズの設定動作が行われる
  4. それまでフォーカスのあったオブジェクト(例えば他のフォームやデータベースウィンドウなど)から、このフォームにフォーカスが移動して、このフォームがアクティブになる
  5. フォームがテーブルに連結されているので、レコードソースの1レコード目に移動する

続いて、フォームが閉じる際のイベントです。
  1. 読み込まれていたフォームオブジェクトが解放される
  2. フォームがなくなったので、他のオブジェクトにフォーカスが移る
  3. データベースファイル内に格納されているフォームオブジェクトの保存場所を閉じる



通常のイベントの発生順序を調べただけでは、とても"徹底研究"とはいえませんので、ここでいろいろと条件を変えて、いくつかの調査を行ってみたいと思います。


フォームはいつ画面に表示されるのか?

この確認は、実際に自分の目で行うしかありません。そこで、AccessのウィンドウとVBEのウィンドウを左右に並べた状態にして、プログラムをステップ実行してみます。Functionプロシージャ"TrackEvents()"のコードの途中にブレークポイントを設定し、このプロシージャが呼び出されるたびに、プログラムが中断するようにします。そして、それぞれの時点でのフォームの表示有無を、目視で確認することとします。

その結果は次のようになりました。

イベント名 画面表示の有無
開く時 ×
読み込み時 ×
サイズ変更時 ×
アクティブ時 ×
レコード移動時 ×→○
読み込み解除時
非アクティブ時
閉じる時 ○→×


ここでは、"レコード移動時"イベントが「×→○」という記述になっていますが、これは、厳密には"レコード移動時"イベントが完了したあと、プログラム上では"Current/レコード移動時"イベントプロシージャ(「End Sub」の行)を抜けたあと、フォームが画面表示されたことを表しています。また、同様に、"閉じる時"イベントが完了してから、フォームが画面から消えたことを表しています。

この結果から考えてみると、フォームのあるプロパティをコードから設定するような場合、例えば、フォームのウィンドウの"標題/Caption"プロパティを、プロパティシートではなくプログラムから設定するような場合、どのイベントプロシージャ上で行っても同じ結果が得られるということになります。どの時点で設定しても、「画面が目視で確認される状態になったときにはプロパティ設定が完了している」ということになります。そのことを検証してみましょう。

表示される直前の、最後のイベント、"Current/レコード移動時"イベントプロシージャを次のようにして、フォームを開いてみます。

Private Sub Form_Current()

  Me.Caption = "標題のテスト"

End Sub


標題が変更されたフォーム予想通り、確かに、フォームが見える状態になったときには、すでにフォームの標題は「標題のテスト」に変更されていることが確認できました。

ただし、実際には、"レコード移動時"イベントはフォームを開く時以外にも発生しますので、そのたびにこのようなコードを実行させるのは無駄なことです。フォームが開く時だけに発生する、"開く時"または"読み込み時"イベントで行うのが正解でしょう。イベントプロシージャを使う上では、それぞれのイベントがどのようなときに発生するかを理解して、適切な時点で独自の処理を行わせることが大切です。



もっと早い段階でフォームを表示できないか?

フォームが開く際の5つのイベントすべてが完了した時点でフォームが表示されるということが分かりましたが、それなら、初めの方に実行されるイベントプロシージャを利用することによって、もっと早い段階でフォームを強制的に表示させることができるのではないでしょうか?。試しに"Open/開く時"イベントプロシージャを使って、フォームの描画を行ってみましょう。

次のようなイベントプロシージャを記述して、フォームを開いてみます。

Private Sub Form_Open(Cancel As Integer)

  DoCmd.SelectObject acForm, Me.Name
  DoCmd.RepaintObject acForm, Me.Name

End Sub

先ほど行ったのと同様に、Functionプロシージャ"TrackEvents()"の途中にブレークポイントを設定して動作確認してみたところ、"Open/開く時"イベントプロシージャを抜ける時点で、すでにフォームがきちんと表示されていました

しかし、このコードを結論としてストレートに書いてしまいましたが、実はその前にいくつか試みたことがあります。そのコードを紹介しましょう。
Private Sub Form_Open(Cancel As Integer)

  Me.Repaint

End Sub

Private Sub Form_Open(Cancel As Integer)

  Me.Repaint
  DoEvents

End Sub

Private Sub Form_Open(Cancel As Integer)

  DoCmd.RepaintObject acForm, Me.Name

End Sub

これら3つはいずれもダメな例です。同じようなことをしているつもりでも、最初に書いた「DoCmd.SelectObject」と「DoCmd.RepaintObject」を併用した方法でないと、"開く時"イベントの時点でフォームを表示させるのはできないようです。



"開く時"と"読み込み時"はどう使い分ける?

"開く時"でさえ、フォームを描画できるということが分かりましたが、実は、「この時点ではまだフォームオブジェクトは読み込まれていないわけだから、フォームを描画させることはできない」と考えていました。しかし、実際はそうではありませんでした。「DoCmd.SelectObject」と「DoCmd.RepaintObject」を実行することによって、"開く時"イベント内から"読み込み時"イベントが強制的に発生させられているということも考えられますが、いずれにしても、このことは、"開く時"と"読み込み時"イベントに使用上の違いはないことを示しているといえます。では、どうしてこれら2つのイベントが別のものとして用意されているのでしょう。そして、それらはどのように使い分けたらよいのでしょう。その答えは、イベントプロシージャの宣言にありそうです。

それら2つのプロシージャの宣言は、次のようになっています。

【開く時】
    Private Sub Form_Open(Cancel As Integer)

【読み込み時】
   Private Sub Form_Load()


ご覧のように、2つの大きな違いは「Cancel」という引数を持っているかいないかにあります。この引数が、イベントプロシージャ内で"True"に設定されると、そのイベント自体がキャンセルされます。つまり、フォームが"開く"という動作がキャンセルされて、フォームが開かれないのです。したがって、もし、次のようなプロシージャを作ると、そのフォームは「開くことのできないフォーム」になります。

Private Sub Form_Open(Cancel As Integer)

  Cancel = True

End Sub

※何らかの理由で、"読み込み時"イベントプロシージャ内でフォームを閉じるようにしたいという場合には、"読み込み時"イベントプロシージャで「Docmd.Close acForm, Me.Name」というコードを実行させることによって実現させることができます。


このことから、"開く時"と"読み込み時"イベントの使い分けは、「フォームを開かせない状況があるかどうか」がその判断基準になります。もし、「ある条件にあてはまっているときだけフォームを開くようにする」という処理が必要なら、その条件をチェックするコードは"Open/開く時"イベントプロシージャに記述します。そのよい例が、「パスワードを入力させ、正しいパスワードが入力されたときだけフォームを開く」という処理でしょう。

コード例:
Private Sub Form_Open(Cancel As Integer)

  If InputBox$("パスワードを入力") <> "PASSWORD" Then
    Cancel = True
  End If

End Sub

実行例:
実行したパスワード入力画面



レコード連結されていなければ"レコード移動時"イベントは発生しないのか?

今回使っているフォームは、テーブルに連結されているものです。"レコード移動時"イベントというからには、移動すべきレコードがなければそのイベントは発生しないはずです。フォームの"レコードソース"プロパティをいったん空にして、同じテストを行ってみましょう。

レコードソースが設定されている レコードソースをクリア


実行結果のイミディエイトウィンドウです。
実行結果のイミディエイトウィンドウ

テーブルとの連結の有無に関わらず、"レコード移動時"イベントが発生していることが分かります。"レコードソース"プロパティを空欄にするだけでなく、テキストボックスの"コントロールソース"プロパティを空欄にしても、さらにコントロールをすべて削除しても、同様の結果になりました。

レコードというオブジェクトを持っていなくてもレコード移動を試みる、これはAccessの特性なのかもしれません。



自動中央寄せなどをしなければ"サイズ変更時"イベントは発生しないのか?

"サイズ変更時"イベントは、一般には、ウィンドウのサイズを変更したときに発生するものです。しかし、今回確認できたように、フォームを開く際にもこのイベントが発生しています。その要因として、"サイズ自動修正"プロパティや"自動中央寄せ"プロパティの設定によって、自動的にフォームのサイズや位置の調整が行われ、それによってイベントが発生したと考えることができます。

そこで、それらのプロパティをいずれも"いいえ"に変更し、同様の確認を行ってみました。
プロパティシート

その結果、これまでと同様に"サイズ変更時"イベントが発生しているということが分かりました。おそらく、フォームのウィンドウが生成される際に、デザイン上の各プロパティ値(左位置・上位置・幅・高さ)がウィンドウの実体に適用されるため、あるいはAccessのウィンドウに対する位置調整などの処理が内部的に行われているのでしょう。



読み込み時に強制的にフォームのサイズ変更を行うとどうなる?

フォームの位置とサイズを前回閉じたときの状態に復元するなど、フォームを開く際に、強制的にそれらを変更することがあります。上記のように、何もしなくても"サイズ変更時"イベントが発生しましたが、ではもし、サイズや位置を変更するようなプログラムを"Load/読み込み時"イベントプロシージャで実行したようなときには、どのような動作をするのでしょうか?。検証してみたいと思います。

まず、次のような"Load/読み込み時"イベントプロシージャを用意します。

Private Sub Form_Load()

  Debug.Print "フォーム変更前"
  DoCmd.MoveSize 100, 200, 8000, 4000
  Debug.Print "フォーム変更後"

End Sub


その結果のイミディエイトウィンドウです。
実行結果のイミディエイトウィンドウ

ご欄のように、"サイズ変更時"イベントが2回発生していることが分かります。上述のコードとイミディエイトウィンドウとを見比べてみてください。明らかに「DoCmd.MoveSize」によって最初の"サイズ変更時"イベントが発生していることが分かります。


このように、イベントを使ったプログラミングでは、その内容によって、イベントプロシージャの中から別のイベントプロシージャが知らない間に呼び出されていたり、同じイベントプロシージャが重複して実行されたり、といったケースも多々あります。あるイベントプロシージャが呼び出されるたびに、変数値を+1したつもりが、実際は二重に呼び出されていて、一回の操作で+2されてしまうというバグも起こりかねません。これは、イベントプロシージャのコーディングにおいて、特に注意すべきことの1つです。


| Index | Prev | Next |

 

Copyright © T'sWare All rights reserved