デザインパターン

クラスモジュールの活用を進めると汎用的なライブラリを作ることができるようになる反面、どのようなクラスを作って、クラスとクラスの関係をどうするかが難しくなってきます。当方もいろいろと試行錯誤しました。

最近、目にするデザインパターンはクラス設計を容易にするものではと思い、AccessのVBAに適用してみました。デザインパターンの解説書でよく見かけるのはJava言語を使ったもので、これをVBAに活用できるのかと不安がありましたが、今の感触としては十分役に立つものと認識しています。

私が学習に使った書籍はJava言語で学ぶデザインパターン入門(結城浩著)です。余談ですが、この書籍にあるデザインパターンをDelphiで書き直すと、Class参照型を活用することにより、書籍のJava言語による例以上にすっきりとデザインパターンを表現することができました。

デザインパターンについてはAccess 2000 VBAによるクラスモジュール活用の応用編としてVisual Basic Magazine2002年5月号に投稿させていただきました。コマンドパターンを中心にVBAで利用しやすいパターンを解説しています。

Linux

当方事務所にはLinux環境を揃えて、今後増えるであろうLinuxサーバを利用したアプリケーション構築に備えています。現在のところ2件のWebアプリケーション開発でLinux環境が役に立ちました。

先日、Linuxサーバ上でPostgreSQLを使用したWebアプリケーションをSI様に提案しました。しかしながらバックアップ・セキュリティチェックなどサーバの運用管理をどうするかといった問題を解決するためレンタルサーバを探してみたのですが、なかなかぴったりのものがなかったり、割高であったりしました。

そのなかでAKIRAというプロバイダの運営しているレンタルサーバは、共用サーバであるものの、2001年7月時点で最新バージョンであるPostgreSQL7.1.2をサポートしている上、リブートの依頼ができ、Perlモジュールもインストールしていただけるので開発時にも便利そうです。共用サーバであるがゆえに日々のバックアップ・セキュリティチェックがついています。費用も大変安く月額7,900円で500MByteの容量を確保できます。現在は募集していません。(2015/05/13追記)

今後、高速通信が普及すると事務所内で使用するクライアント/サーバシステムでさえ、このような安価なレンタルサーバを利用するソリューションを提案できる道が開けそうな気がします。

現在は事情が大きく変わり、大手レンタルサーバが安価なVPSを用意しています。(2015/05/13)

COM(ActiveX)について

COM(ActiveX)は基本的にはバイナリ標準で動的リンクの仕方を定めたものと解釈しておりますが、シンプルな機構でいろいろなものに応用されている点が面白いと思います。

表示形態による分類
表示形態 説  明
ActiveX DLL 画面表示しない、オブジェクト指向のDLLとして使える DAO,ADOなど
ActiveX コントロール フォームに貼り付けて使用する MSCommなど
ActiveX ドキュメント アプリケーションユーザがドキュメントを貼り付けて使用する Word文書中にExcelの表を貼り付ける場合など
実行形態による分類
実行形態 説  明
インプロセスサーバ 同一プロセスで実行する DAOなど
ローカルサーバ 同じマシンの別のプロセスで実行する VBからExcelをOLEサーバとして呼び出した場合など
リモートサーバ(DCOM) 他のマシンでプログラムを実行できる 分散アプリケーション

Accessバグ情報

Access97のバグやオンラインヘルプの間違い情報です。右端はマイクロソフトの回答です。
現象 対応等
同名のレポートをデザインビューで開くとフォームビューのフォームが閉じる 対応策なし
Option Compare Binaryで漢字がユニコード順で大小比較される、ヘルプではシフトJISとなっている ヘルプの間違い
Option Compare DatabaseでLike演算子の文字範囲指定に漢字を指定した場合、比較順序が比較演算子と異なりユニコード順になる 対応策なし
KeyPressイベントでKeyAscii=0としてもFEPの入力をキャンセルできない 次期バージョンで対応
Err.Raise vbObjectError+1050とすると、1050番のエラーが発生するとヘルプには書いてあるが実際にはvbObjectError+1050のエラーが発生する ヘルプの間違い
VB5も同様
Err.Raise vbObjectError+513で、96番のエラーが発生する。この番号はVB5のオンラインヘルプで指示している番号であり、このバグのためにActiveXのエラー処理が正常に働かない可能性もある。 対応策なし
Commitで引数にdbFlushOSCacheWritesを指定するとエラーになる dbForceOSFlushの間違い

レポートでグループ毎にページ番号とグループ内のページ数を表示する方法

レポートの機能を使って、グループ毎にページ番号とページ数を印刷することができます。レポートでは[Pages]を使って全体のページ数を各ページに印刷することができますが、この時Accessは全ページを2回スキャンしています。これを利用して1回目のスキャンで各グループのページ数を配列に保存しておき、2回目のスキャンで配列からページ数を取り出してコントロールに値をセットするのです。

具体的には、レポートに以下のプロシージャを記述してください。そうすれば[ページ数表示]というコントロールに表示されます。注意点として[Pages]をダミーで配置させておくことです。[ページ数表示]は、ページヘッダ・ページフッタどちらでもOKです。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
Option Compare Database
Option Explicit
 
Dim gps(1000) As Integer 'グループ毎のページ数を保存する配列
Dim i As Integer
 
Private Sub レポートヘッダー_Format(Cancel As Integer, FormatCount As Integer)
i = 0
End Sub
 
Private Sub グループフッター0_Format(Cancel As Integer, FormatCount As Integer)
If FormatCount = 1 Then
If Pages = 0 Then gps(i) = Page
i = i + 1
Page = 0
End If
End Sub
 
Private Sub ページヘッダー_Format(Cancel As Integer, FormatCount As Integer)
If Pages > 0 Then ページ表示 = Page & "/" & gps(i)
End Sub

レポートで複数レコードを横一行に表示させる方法

通常、詳細セクションは次のレコードに移るときに行を換えてしまいますが、MoveLayout プロパティを False に設定することで同じ行に重ね打ちします。さらに、コントロールの Left プロパティを設定すると印字する左右の位置を設定できますので、この2つを使うと複数レコードを横一行に印字できます。

下の例は、dataというフィールドがあるテーブルの複数レコードを一行に印字する例です。実際には、右いっぱいになったら改行するとか、別のフィールドでグループ化して改行するとかを考慮する必要がありますがこれは簡単にできるでしょう。

1
2
3
4
5
6
7
8
9
10
11
12
Option Compare Database
Option Explicit
Dim i As Integer
 
Private Sub 詳細_format(Cancel As Integer, PrintCount As Integer)
data.Left = (i + 2) * 567
MoveLayout = False
End Sub
 
Private Sub 詳細_Print(Cancel As Integer, PrintCount As Integer)
i = i + 1
End Sub

切り捨て・切り上げ・四捨五入

下の例は0に関して対称に丸めます。切り上げに関しては0.9を足して切り捨てをする方法がNifty Forumに紹介されているのを見かけますが間違いですので気をつけてください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
'切り捨て
Function Floor(x As Double) As Long
Floor = Fix(x)
End Function

'切り上げ
Function Ceil(x As Double) As Long
Ceil = -Sgn(x) * Int(-Sgn(x) * x)
End Function

'四捨五入
Function Round(x As Double) As Long
Round = Fix(x + 0.5 * Sgn(x))
End Function

リンクテーブルで Seek を使う方法

レコードセットで検索をする場合、FindFirstよりもIndexを使ったSeekを使ったほうが速いとされています。Seekを使うためには、dbOpenTableを指定してレコードセットを開く必要があります。次の手順を参考にしてください。

1
2
3
4
Set db = DBEngine(0).OpenDatabase("テーブルを保存しているファイル")
Set rec = db.OpenRecordset("テーブル",dbOpenTable)
rec.Index = "PrimaryKey"
---- 以後はオンラインヘルプを参照 ----

トランザクションと定義域集合関数

トランザクション中に定義域集合関数を使う場合には注意が必要です。トランザクションを開始してから加えた変更は DLookup などの定義域集合関数には反映されません。それに対し、Recordsetオブジェクトを使った場合はコミットする前でも変更が反映されます。

以下のサンプルはトランザクション中で、レコードのないテーブルに一件のレコードを追加し、これをDLookupとRecordsetの2つの方法で表示させようというものです。フィールド:f1を持つテーブル:T1を予め作成してこのプロシージャを実行してください。

1
2
3
4
5
6
7
8
9
10
11
Sub Temp()
BeginTrans
CurrentDb.Execute "INSERT INTO T1 (F1) VALUES(100)", dbFailOnError
MsgBox "F1(DLookup):" & DLookup("F1", "T1")
With CurrentDb.OpenRecordset("T1", dbOpenDynaset)
.MoveFirst
MsgBox "F1(Recordset):" & !f1
.Close
End With
CommitTrans
End Sub

トランザクションとRunSQLメソッド

RunSQLメソッドではトランザクションが効きません。トランザクション中ではExecuteメソッドを使う必要があります。

以下のサンプルはトランザクション中に、レコードのないテーブルに一件のレコードを追加し、ロールバックでもとに戻すものですが、RunSQLメソッドを使った場合は元に戻らず、トランザクションが効いていないことがわかります。フィールド:f1を持つテーブル:T1を予め作成してこのプロシージャを実行してください。RunSQLの行をコメントにして、Executeメソッドの行のコメントを外して実行するとロールバックで元に戻ります。

1
2
3
4
5
Sub Temp() 
BeginTrans DoCmd.RunSQL "INSERT INTO T1 (F1) VALUES(100)"
' CurrentDb.Execute "INSERT INTO T1 (F1) VALUES(100)", dbFailOnError
Rollback MsgBox "F1(DLookup):" & DLookup("F1", "T1")
End Sub