Visual Basic 業務アプリ構築法 第12回

ADODataコントロールの扱い(2)~レコードの移動や追加/削除
長谷川裕行
1999/04/08

長谷川 裕行 (はせがわ ひろゆき)
有限会社 手國堂 代表取締役
http://www.hirop.com/
テクニカルライターとして活躍。プログラミングに関する著書多数、DB Magazineなどにも多くの記事を提供している。

前回に引き続き、ADODataコントロールを使った住所録を作る過程を紹介しましょう。前回は、ADODataコントロールを貼り付けてデータベースを接続し、テキストボックスにフィールドを設定するところまでを作成しました。
今回は、ADODataコントロールを制御してレコードの移動や追加、削除などの処理を作ってみます。前々回(第10回)で紹介したデータフォームウィザードを使えば、同じ処理が簡単にできてしまいます。とにかく手っ取り早く作ってしまいたい場合にはデータフォームウィザードが便利ですが、それではデータベースの扱いを理解することはできません。
ここでは、ADODataのプロパティやメソッドを操作して、テーブルに対する基本的な制御を行う方法を紹介します。簡単なようですが、様々な例外処理に対応させようと思えば、結構面倒な処理も必要になってきます。



ADODataのメソッド操作が中心

前回、フォームをデザインして各コントロールのプロパティを設定するところまで説明しました。また、自分自身と同じフォルダに存在するデータベースをオープンし、テーブルをアクセスできるようにする処理も作成しました。
ADODataコントロールは、datAddressという名前です。今回作成する処理では、ADODataのメソッドを操作することが中心となります。
なお、コード中のマジックナンバー(突然現れる定数)は記号定数として、以下のように定義してあります(前回――第11回のリスト1参照)。


Const DB_NAME = "Addr01.mdb"
↑データベース・ファイル名
Const CON_STRING = _  ←接続文字列
"PROVIDER=Microsoft.Jet.OLEDB.3.51;Data Source="
Const SQL_STRING = _ ←レコードセット用SQL文
"SELECT ID,読み,氏名,性別,電話番号," _
& "郵便番号,住所1," _
& "住所2 FROM T_住所録 ORDER BY 読み ASC;"


エラーへの対応

プログラムにエラー処理は必須です。どんなプログラムも、エラーを起こさない保証はありません。エラーへの対処を考えておきましょう。


- すべてのエラーを予測できない -

ユーザーが間違った操作をすることもあれば、他のプログラムやシステムとの問題、あるいはハードウェアの問題などで、処理が思い通りに進められなくなることがあります。
プログラムを構成するすべての要素がVBのソースコードレベルで制御できるなら、発生しそうなエラーは予測できます。従って、各プロシージャ内で例外的な状況を判断してメッセージを出すなどの対応が可能です。
しかしデータベース処理では、外部のデータベースエンジンをVBのプログラムから制御し、さらにデータベースエンジンがデータベースを制御するという形となります。つまり、VBのプログラムはADODataを通じて、データベースを間接的にアクセスすることしかできないのです。
そのため、データベース周りで発生しそうなエラーを予測することが困難になります。データベースのエラーはVBのプログラム(=今あなたが作ろうとしているアプリケーション)ではなく、データベースエンジンが検知するためです。



- コントロールのErrorイベント -

ADODataを含む様々なオブジェクトがエラーを検知した場合、オブジェクトはエラーが発生したことをVBに知らせます。VBにはこういったエラーを、例外として検知し処理する仕組みがあります。
On Error GoToステートメントです。
プロシージャの先頭に

On Error GoTo ラベル

と記述しておけば、何らかのエラーが発生した場合に<ラベル>で示す行に処理を移行できます。
データベースのエラーでは、存在しないフィールドをアクセスしようとした場合のように、よほど明確にソースコードが間違っていない限り、その解決をVBのソースコードレベルでは行えません。多くの場合、エラーの原因がデータベースまたはデータベースエンジンの側にあるためです。
データベースエンジンから先でエラーが発生した場合、ADODataのErrorイベントが発生します。これを処理するのがリスト1のdatAddress_Errorプロシージャです。引数が多くて非常に長い宣言となっていますが、実際の処理はDbErrorMsgプロシージャを呼び出しているだけです。
このような、エラー発生時に行われる処理を「エラートラップ」と呼びます。トラップ(trap)とは「罠」「仕掛け」のことです。このプロシージャ内で、独自の処理を行わせることも可能です。



- エラーメッセージの表示 -

リスト2がDbErrorMsgプロシージャのソースコードです。単に、エラーメッセージを表示するだけです。しかし、単に「エラーです」だけでは、ユーザーにはなんのことだか分かりません。エラーの種類も示すべきです。
MsgBox関数の引数に

Err.Description

と指定しています。Errはエラーの状況を保持するオブジェクトで、アプリケーション全体に適用されるグローバル・オブジェクトであり、加えて最初から存在している組み込みオブジェクトなので、プログラミングの段階でオブジェクトを生成する必要はありません。つまり、ソース中でいきなり利用できるのです。
エラーが発生すると、ErrオブジェクトのDiscriptionプロパティに、発生したエラーの内容を示す文字列が設定されます。リスト2では、それをメッセージボックスに表示しているだけです。



- エラー表示を下請けとする -

このアプリケーションでは、エラーが発生したら単にエラーメッセージを表示するだけとしておきます。アプリケーション内でエラーを回復することはできませんが、処理が正常に終わらなかったことを、ユーザーに伝えることはできます。
データベース関連のエラーは、レコードを操作する様々な状況で発生することが考えられるので、エラーメッセージの表示は下請けのプロシージャとし、各プロシージャから呼び出せるようにしておきます。

リスト1:ADODataのエラートラップ


Private Sub_
 datAddress_Error(
ByVal ErrorNumber As Long, _
     Description As String, _
     ByVal Scode As Long, _
     ByVal Source As String, _
     ByVal HelpFile As String, _
     ByVal HelpContext As Long, _
     fCancelDisplay As Boolean)
   DbErrorMsg
End Sub

リスト2:エラーメッセージの表示

Private Sub DbErrorMsg()
 MsgBox Err.Description, , "データベースエラー"
End Sub


- レコードの追加 -

[追加]ボタン(cmdAddNew)をクリックすると実行されます。
レコードの追加では、ADODataコントロール(datAddress)のRecordsetオブジェクトでAddNewメソッドを実行します。これだけでRecordsetの最後尾に新しいレコードが追加され、そこがカレントレコード(操作対象のレコード)となります。
このプロシージャの先頭行にある

On Error GoTo AddNewErr

が、エラー発生時のための例外処理を行う命令です。
エラーが発生すると、AddNewErrというラベルに処理が移行します。そこには

AddNewErr:  ←ラベル
DbErrorMsg ←処理

と記述されているので、先述したDbErrMsgプロシージャが呼び出されて、状況に応じたエラーメッセージが表示されます。
この仕組みは、続く削除、更新、レコードの移動処理でもまったく同じです。

リスト3:レコードの追加


Private Sub cmdAddNew_Click()
On Error GoTo AddNewErr
 datAddress.Recordset.AddNew
 Exit Sub
AddNewErr:
 DbErrorMsg
End Sub


レコードの削除

[削除]ボタン(cmdDelete)をクリックすると実行されます。


- 削除の前に確認する -

レコードの削除は、AdoDataコントロールのRecordsetオブジェクトにあるDeleteメソッドを実行するだけです。しかし、この処理をボタンクリックと同時に実行してはいけません。一旦削除すると元に戻せないので、必ず間に確認処理を挿入します。

If MsgBox("削除してもよろしいですか?", _
       vbYesNo + vbQuestion, _
       "レコード削除") = vbNo Then Exit Sub

と、メッセージボックスでユーザーに削除の確認を行い、Ifステートメントで[はい]がクリックされたときだけ、Deleteメソッドを実行します。
- 削除したらレコードを移動する -

Deleteメソッドで削除されるのはカレントレコードです。すると、レコードを削除した途端に画面に表示されている内容も消えてしまいます。通常は#Deleted#という表示となるのですが、これは不格好です。それに、存在しないレコードが表示されていると、ユーザーが知らずにそこを書き直そうとして、他のエラーを誘発してしまう危険性もあります。

With datAddress.Recordset
   .Delete  ←レコードを削除
   .MoveNext ←次のレコードに移動

として、削除したレコードの次のレコードに移動するようにしておきます。この場合、削除したレコードが最終レコードだったら、その次のレコードは存在しません。そこで

If .EOF Then .MoveLast

としておきます。EOFはレコードセットの最後を示すプロパティです。これがTRUEの場合は「もう後ろのレコードがない」ということを示しているので、MoveLastメソッドを実行して最終レコードに移動させます。

リスト4:レコードの削除



レコードの更新

[更新]ボタン(cmdUpdate)をクリックすると実行されます。


- 更新処理の必要性 -

レコードセット(Recordsetオブジェクトの保持しているデータ)は、ユーザーが操作する都度データベースファイルに書き込まれているわけではありません。データベースエンジンの側で一旦作業用の領域に保存しておき、変更内容が一定量溜まったときや、データベースを閉じるときなどに実際に書き込まれます。
そのため、画面上ではデータが書き換えられていても、処理中にエラーが発生してアプリケーションが異常終了したときなどには、その書き換えたデータがデータベースに反映されないことがあります。
そこで、レコードの内容を書き換えたりレコードを追加したりした場合には、必ずレコードセットを更新するようにしておきます。



- Updateメソッド -

レコードセットの更新処理には、RecordsetオブジェクトのUpdateメソッドと、UpdateBatchメソッドの2種類があります。
カレントレコードの更新内容だけを記録するなら、Updateメソッドで構いません。UpdateBatchメソッドは、レコードセット内の未保存データをすべて書き込みます。レコード件数が多いと、処理に若干時間がかかります。

リスト5:レコードの更新


Private Sub cmdUpdate_Click()
  On Error GoTo UpdateErr
  datAddress.Recordset.Update
Exit Sub
  UpdateErr:
  DbErrorMsg
End Sub


レコードの移動

レコード移動ボタン[|<][<][>][>|]をクリックすると実行されます。


- 簡単そうで厄介な処理 -

これらの処理は、どれもRecordsetオブジェクトのメソッドで対処できます。

MoveFirst:先頭レコード
MovePrevious:1つ前のレコード
MoveNext:次のレコード
MoveLast:最終レコード

非常に単純そうですが、1つ問題があります。先頭と最終レコードへの移動は、どんなときでも固定された位置へ移動するだけなので、直接メソッドを実行して構いませんが、「1つ前」と「次」の場合、現在のレコード位置を意識しなければなりません。
現在先頭レコードが表示されている状態で「1つ前」に移動しようとしたり、最終レコードが表示されているときに「次」に移動しようとすると、存在しないレコードに移動することになってしまいます。



- 現在位置によって移動先を切り替える -

そこで「1つ前」への移動なら

If datAddress.Recordset.BOF = True Then
  datAddress.Recordset.MoveFirst
 Else
  datAddress.Recordset.MovePrevious
 End If

という具合に、
現在先頭レコードなら→先頭レコードへ
そうでなければ→1つ前のレコードへ
と、カレントレコードによって移動先を切り替えます。
同様に「次レコード」への移動も、

If datAddress.Recordset.EOF = True Then
  datAddress.Recordset.MoveLast
 Else
  datAddress.Recordset.MoveNext
 End If

として、
現在最終レコードなら→最終レコードへ
そうでなければ→次のレコードへ
移動するようにします。



- 二重のIfで対処する -


BOFかどうかを調べて「先頭レコード」だったら「先頭レコード」へ移動する――というのは、ちょっと不自然な気がします。が、BOFと先頭レコード、EOFと最終レコードは同じではありません。
実際にレコードを移動してみて、
先頭レコードより前に移動したらBOFが
最終レコードより後ろに移動したらEOFが
それぞれTrueになります。そのため、実際に「今先頭レコードであるかどうか/最終レコードであるかどうか」は、取りあ
えずレコードを移動して先頭または最終を通り過ぎ、BOFかEOFがTrueになったかどうかを調べるまで分かりません。
従って、レコード移動処理としては若干不自然な形となります。そこで、例えば1つ前に移動するなら、以下のように二重のIf文で対処します。
先月のサンプルでは、この部分を先に示した単一のIf文としてあります。今月のサンプルと比較してみてください。


If datAddress.Recordset.BOF = True Then
  datAddress.Recordset.MoveFirst
   ↑BOFなら先頭レコードへ移動
 Else
  datAddress.Recordset.MovePrevious
   ↑そうでなければ1つ前へ移動
  If datAddress.Recordset.BOF = True Then
   ↑ここでもう一度BOFかどうか調べ
   datAddress.Recordset.MoveFirst
    ↑BOFなら先頭レコードへ移動
  End If
End If

リスト6:先頭レコードへ移動(|<)
リスト7:1つ前のレコードへ移動(<)
リスト8:次のレコードへ移動(>)
リスト9:最終レコードへ移動(>|)



終了処理

[閉じる]ボタンをクリックしたら、リスト10のcmdQuit_Clickが実行されます。

Unload Me

として、単に自分自身を閉じて破棄するだけです。
しかし、これでは不十分です。オープンしたレコードセットをクローズし、最終的な更新内容を実際のデータベースファイルに書き戻さねばなりません。この処理は、[X]ボタンでウィンドウがクローズされた場合にも必要です。従ってリスト11のように、FormオブジェクトのUnloadイベントで実行させます。

datAddress.Recordset.Close

RecordsetオブジェクトのCloseメソッドを実行すれば、溜まっていたレコードセットの内容がすべてデータベースファイルに書き込まれ、データベースはクローズされます。

リスト10:終了処理

Private Sub cmdQuit_Click()
 Unload Me
End Sub

リスト11:フォームを閉じるときの処理

Private Sub Form_Unload(Cancel As Integer)
 datAddress.Recordset.Close
End Sub


コードレベルでデータベースを操作すると言っても、ほとんどRecordsetオブジェクトのメソッドで解決できます。ただ、単に目的のメソッドを実行するだけでは、思いもかけない例外が発生したときに対処できません。
先頭/最終レコードへの移動やデータベースのエラーなどに対処できるよう、様々なケースを想定して処理を組み上げる必要があります。これらの仕組みについては、次回に説明しましょう。また次回は、文字ばかりで寂しいフォームのコマンドボタンに、画像を貼り付けて見た目を整え、より扱いやすくすることも考えてみます。

DownloadVBプロジェクトファイルのダウンロード
(LZH形式 33.5KB)
Copyright © MESCIUS inc. All rights reserved.