IsNumeric 関数

IsNumeric 関数は、数値かどうかを判定する関数として、一般的には認知されています。

ヘルプには「式が数値として評価できるかどうかを調べ、結果をブール型 (Boolean) で返します」と有りますが、この「数値として評価できる」式と、一般的にユーザーが思い描く数値のイメージとの間には、どうも微妙に温度差が有るような気がします。

この辺を把握していないと、IsNumeric 関数で数値の妥当性チェックを掛けているから大丈夫、と思っているその隙を思わぬ値がすり抜けていく可能性が残ってしまいます。

そこで、IsNumeric 関数が数値と評価する例を具体的にピックアップしてみました。

【IsNumeric 関数が True を返すケース一覧】
ケース例(文字列)補足
数字のみ 123456
123456
00123456
全角でも半角でも許容します。
符号付き +123456
-123456
+123456
−123456
+ 123456
123456 -
符号は全角でも半角でも、数字の前でも後ろでも許容します。また符号と数字のあいだに 1 個以上の全角/半角スペースが入っても許容します。
小数点記号(ピリオド)付き 123.567
123.
.567
123.567
小数点記号は全角でも半角でも許容します。また小数点記号の前か後ろのどちらかに数字がなくても許容します。
桁区切り記号(カンマ)付き 123,567
123,
,567
123,567
1,2,,3,7,,,
桁区切り記号は全角でも半角でも許容します。また先頭でさえなければ、桁区切り記号はどこに何回出現しても許容します。
指数記号(E, D)付き 1.2E+16
1.2e-16
1.2E+16
1.2e-16
.2E+16+
指数記号は全角/半角/大文字/小文字のいずれでも許容します。また指数の前後に符号が付いても許容します。左の例の「E」は、いずれも「D」と置き換えても有効です。
通貨記号(¥)付き \1,234.567
1,234.567\
¥1,234.567
1,234.567¥
\ 1,234.567
通貨記号は全角でも半角でも許容します。また通貨記号と数字のあいだに 1 個以上の全角/半角スペースが入っても許容します。
スペース付き  1234 数字の前後に 1 個以上の全角/半角スペースが入っても許容します。
8 進数/16 進数 &0200
&HABCD
&habcd
&0200
&HABCD
8 進数/16 進数符号(&0/&H)は全角/半角/大文字/小文字のいずれでも許容します。
括弧( )付き (1234)
(1234)
( 1234 )
( 1234 )
括弧は全角でも半角でも許容します。また括弧と数字のあいだに 1 個以上の全角/半角スペースが入っても許容します。なお括弧付きは負の値を指します。

上記は日本語版 OS の標準設定を前提としています。

ロケールの設定が異なれば、通貨記号などのパターンが変わることが予想されます。

結局のところ、たとえば仮に 0-9 の 10 種類の文字しか許容したくないのであれば、Like 演算子や正規表現のようなパターンマッチングを使うのが適切な対応ということになるでしょう。

StopWatch クラス

なんかプログラミングの例題に出てきそうなクラスですが、必要なので作ってみました。

Option Compare Database
Option Explicit

Private Declare Function GetTickCount Lib "Kernel32" () As Long

Private m_Start As Long     ' 開始時間
Private m_Total As Long     ' 経過時間
' 時計測を開始または再開します。 Public Sub Start() m_Start = GetTickCount End Sub
' 時計測を一時停止し、前回の開始時からの経過時間(単位:ミリ秒)を返します。 Public Function Pause() As Long Dim l As Long If m_Start = 0 Then Exit Function l = GetTickCount - m_Start m_Total = m_Total + l m_Start = 0 Pause = l End Function
' 時計測を終了して、経過時間の総計(単位:ミリ秒)を返します。 Public Function Finish() As Long If m_Start Then Finish = m_Total + GetTickCount - m_Start m_Start = 0 m_Total = 0 ElseIf m_Total Then Finish = m_Total m_Total = 0 End If End Function

GetTickCount の分解能はあまり良くないので、10 ミリ秒ていどの誤差は織り込んでおく必要が有ります。

このクラスモジュールに clsStopWatch というクラス名を付けた場合の使用例が、以下です。

Dim sw As New clsStopWatch    ' インスタンスを生成

sw.Start    ' 測定開始
Set rs1 = CurrentDb.OpenRecordset(sSQL1)
sw.Pause    ' 測定を一時停止

Call SomeKindaFunc    ' 測定対象外の処理

sw.Start    ' 測定再開
Set rs2 = CurrentDb.OpenRecordset(sSQL2)
Debug.Print "所要時間(ミリ秒):"; sw.Finish   ' 測定終了

ちなみに、これを作った理由は、Collection と Dictionary のどちらがインスタンスの生成に時間が掛かるのか知りたかったからなのですが、結果はほとんど同じでした。

"プリンタに情報を送信できません。" エラー

最近遭遇したレポート絡みの問題を、今後のために記録しておきます。

MDB ファイルを開発環境から運用環境に持ち込んだ際、一部のレポートが下記のようなエラーで開かなくなりました。

プリンタに情報を送信できません。

開発環境と運用環境ではプリンタが異なるため、ページ設定をやり直す必要が有るであろうことは予想していたのですが、レポートがデザインビューでもプレビューでも開かず、[ファイル]-[ページ設定] も同様のエラーで、ダイアログすら出ない始末でした。

そこで、Access オブジェクトをテキストファイルに変換する方法を使い、テキストファイルに出力。

Application.SaveAsText acReport, "レポート名", "C:\Foo\Bar.txt"

出力したテキストファイルを開くと、最初の方に

LayoutForPrint = NotDefault

という行があるのを確認。

ページ設定がカスタマイズされていることを示しているため、この行を削除してからテキストファイルを保存し直し、以下のコードでインポートし直しました。

Application.LoadFromText acReport, "レポート名2", "C:\Foo\Bar.txt"

この方法でインポートすると、オブジェクトの [説明] プロパティが復元されないため、オリジナルのレポート オブジェクトから [説明] プロパティをコピーした上で、オリジナルと差し替えました。

ほとんどのレポートはこれで開くようになったため、ページ設定をやり直して正常動作するようになりました。

一部のレポートについては上記だけでは対応できないものもありましたが、PrtMip セクション、PrtDevMode セクション、PrtDevNames セクションを削除した上でページ設定からいったんプリンタを選び直すことで回避できました。

インプットボックスがキャンセルされたかどうかを知る方法

有効な入力値として、空文字列を許容しないのであれば、単純に以下のように判定するのが一般的でしょう。

sRet = InputBox("値を入力してください。")
If sRet = vbNullString Then Exit Sub  ' キャンセル時は関数を終了

有効な入力値として空文字列を許容する場合は、空文字列かどうかでは判定できません。

そのため、[OK] ボタンが押されたのか [キャンセル] ボタンが押されたのか区別する必要が有る場合は、次のようにします。

sRet = InputBox("値を入力してください。")
If StrPtr(sRet) = 0 Then Exit Sub  ' キャンセル時は関数を終了

キャンセル時はメモリ領域が確保されないため、ポインタが 0 を指します。

ちなみに StrPtr 関数は非公開関数なのでヘルプには載っていませんが、れっきとした VBA 関数の一つです。

最近使用したファイル一覧 最適化 VBS

MS-Access をアプリケーションのみ起動すると、スタートアップ ダイアログの [既存のファイルを開く] の中に、ファイルの一覧が出ますね。

あるいは [ファイル] メニューをドロップダウンすると、下の方にやはりファイルの一覧が出ます。

これが最近使用したファイル一覧なわけですが、この中に既に存在しないはずのファイル名が残っていて困る、という話を聞くことがあります。

この情報はレジストリに記録されているため、削除するにはレジストリを直接編集する必要が有ります。

[AC97] 最近使用したファイル リストを削除する方法

HOW TO: Clear the Most Recently Used Files List in an Office XP Program by Modifying the Windows Registry

私は個人的にはスタートアップ ダイアログを使うことが無いので、何が残っていようが一向に気にしないのですが、気になるけれどレジストリに手が出せない、という方もいらっしゃるようなので、Access 97 以降に対応した簡単なツールを VBS で 作ってみました。

CmpAcMRU.vbs (4.92kb)

ダウンロードしたスクリプト ファイルをダブルクリックするだけで実行されます(エラー発生時以外は、完了しても特に何もメッセージは出ません)。

リンクから直接開かず、必ずいったんデスクトップなどに保存(IE の場合は右クリックから [対象をファイルに保存] を選択)してください。

また中に注意事項が書いてあるので、ダウンロードしたスクリプト ファイルをメモ帳など任意のテキストエディタで開き、必ず事前に注意事項を確認してください。

Excel 出力時の -0 問題

最近、とある BBS でのやりとりで発覚した -0 問題についての覚書です。

集計クエリで、ある条件を満たした集計だけの列を含めたいとき、次のような IIf 関数を使用した演算フィールドが用いられる場合があります。

SELECT
  [図書館名],
  Sum([貸出中]) AS 貸出中の冊数,
  Sum(IIf(Date() - [貸出日] > 14, [貸出中], 0)) AS 返却期限切れの冊数
FROM 貸出テーブル
GROUP BY [図書館名];

上記は、SQL 文を見れば分かるとおり、貸出中の冊数と、返却期限切れの冊数とを、図書館別に表示する集計クエリです。

これは正常動作しますが、関数呼び出しにはコストがかかるため、処理速度を重視する場合は、以下のように積極的に演算子で代用して効率化を図ります。

SELECT
  [図書館名],
  Sum([貸出中]) AS 貸出中の冊数,
  -Sum([貸出中]*(Date() - [貸出日] > 14)) AS 返却期限切れの冊数
FROM 貸出テーブル
GROUP BY [図書館名];

これもやはり正常動作しますし、IIf 関数を使用した場合よりも高速です(もっとも、処理件数がよほど多くないと体感するほどの差は出ないでしょうが)。

ところが、MS-Access 上では何の問題も無いこの集計クエリを Excel に出力すると、該当レコードが存在しない場合の値「0」が、Excel 上では「-0」として表示されてしまいます。

IIf 関数を使用した場合は、Excel 上でも「0」のままです。

どうやら Excel の 0 には、符号が付かないプラスの「0」と、符号の付くマイナスの「-0」の二種類が有るようです。

とりあえず回避策としては、次のように CLng 関数か Val 関数で明示的に型変換をしておけば、問題は有りません。

CLng(-Sum([貸出中]*(Date() - [貸出日] > 14))) AS 返却期限切れの冊数

または

Val(-Sum([貸出中]*(Date() - [貸出日] > 14))) AS 返却期限切れの冊数

正規表現でよく使うパターン

目的パターン設定上の注意点
空行の削除 ^$\r\n

Global と MultiLine プロパティは True。置換文字列を空文字列とします。

また文末の改行は残ります。

行頭に全角スペースを挿入 ^

Global と MultiLine プロパティは True。置換文字列を全角スペースとします。

既に行頭に全角スペースが存在する場合もさらに挿入されます。

また空行であってもやはり挿入されます。

全てのひらがな [ぁ-ん]

句読点や長音(ー)は含まれません。これらを含みたい場合は、文字クラスに別途追加する必要が有ります。

全ての全角カタカナ [ァ-ヶ]

句読点や長音(ー)は含まれません。これらを含みたい場合は、文字クラスに別途追加する必要が有ります。

全ての半角カタカナ [ヲ-゚]

句読点や中点、カギカッコは含まれません(長音(ー)は含む)。これらを含みたい場合は、文字クラスに別途追加する必要が有ります。

全ての漢字 [一-龠]

「〃々〆〇(漢数字のゼロ)」は含まれません。これらを含みたい場合は、文字クラスに別途追加する必要が有ります。

なお Shift-JIS の場合は全漢字範囲の指定として [亜-熙] が使われてきましたが、Unicode 対応環境下ではこの指定は使えません。

たとえば Unicode 環境下で "一丁目の黒いビル" という文に対してパターン "[亜-熙]+" を適用すると、何もマッチしません。パターン "[一-龠]+" を適用すると、"一丁目" と "黒" がマッチします。

すべての漢字を取り出す正規表現 によると、龠は音読みで「ヤク」,訓読みで「ふえ」,Unicodeでは「9FA0」にあたるとのこと。

CreateRegExp 関数

正規表現(VBScript.RegExp)のオプションを一括設定できるように、ラッピングした関数です。

' ***************************************
' 関数名: CreateRegExp
' 目 的: RegExp オブジェクトを返します。オプションを指定可能です。
' 作成者: YU-TANG@http://www.f3.dion.ne.jp/~element/msaccess/
' 作成日: 2004/12/10
' 戻り値: Object 型(内部処理形式 VBScript.RegExp)
' 引数の説明:
' IgnoreCase -> 省略可です。アルファベットの大文字と小文字の違い
'    を無視するか指定します。既定値は True(無視する=大文字と
'    小文字を区別しない)です。
' IsGlobal -> 省略可です。全体を対象とするか指定します。既定値は
'    True(全体を対象とするか)です。False を指定すると先頭の
'    マッチング処理のみ行われます。
' MultiLine -> 省略可です。複数行として検索するか指定します。
'    既定値は True(複数行として検索するか)です。False を
'    指定すると単一行とみなされます。これは文頭・文末を区別
'    する正規表現(^ および $)の適用範囲に影響します。
'    また使用環境が VBScript 5.5 未満の場合は、この引数を
'    指定しても無視されます。エラーは特に発生しません。
' Pattern -> 省略可です。パターン文字列を指定します。
' 使用上の注意:
'    使用環境に VBScript.RegExp ライブラリが存在しない場合は
'    エラーが発生しますが、関数内ではトラップしていません。
'    呼び出し元でエラーを処理してください。
' 使用例 1:
' Set re = CreateRegExp()
' 使用例 2:
' Set re = CreateRegExp(False,,,"\d+th")
' 使用例 3:
' ret = CreateRegExp(False,,,"\d+th").Test("20th Century")
' ***************************************
Public Function CreateRegExp( _
    Optional ByVal IgnoreCase As Boolean = True, _
    Optional ByVal IsGlobal As Boolean = True, _
    Optional ByVal MultiLine As Boolean = True, _
    Optional ByRef Pattern As String) As Object

    Dim re As Object

    ' ----------( 変数を初期化 )----------
    Set re = CreateObject("VBScript.RegExp")

    ' ----------( オプションを設定 )----------
    With re
        .IgnoreCase = IgnoreCase  ' 大文字・小文字を区別するか。
        .Global = IsGlobal        ' 全体を対象とするか。
' MultiLine は VBScript 5.5 未満には実装されていないため、
' エラーを無視します。
On Error Resume Next
        .MultiLine = MultiLine    ' 複数行として検索するか。
On Error GoTo 0
        ' パターン文字列を設定します。
        If Pattern <> vbNullString Then .Pattern = Pattern
    End With

    ' ----------( 戻り値をセット )----------
    Set CreateRegExp = re

    ' ----------( オブジェクト変数の参照を解放 )----------
    Set re = Nothing

End Function

ちょっと実際的な例を一つ。

接続文字列から任意のエントリの値を取得する例です。

Dim re   As Object
Dim sPtn As String
Dim sKey As String
Dim sCon As String

' 変数を初期化します。
sCon = "Excel 5.0;HDR=YES;IMEX=2;DATABASE=C:\Book1.xls;TABLE=Sheet1$"
sKey = "DATABASE"
sPtn = "(?:^" & sKey & "|;\s*" & sKey & ")\s*=\s*([^=;]*)(?=\s*;?)"
Set re = CreateRegExp(Pattern:=sPtn)

' マッチする場合は値を出力します。
If re.Test(sCon) Then
    Debug.Print re.Execute(sCon)(0).SubMatches(0)
End If

上記コードを実行すると、イミディエイトウィンドウに次の文字列が出力されます。

C:\Book1.xls

ACWUSR.MDT のナゾ

以下はあくまで Access 2000 で確認した話です。

Access 2000 では、ウィザード等で使用するユーザー個別の情報について、ACWUSR.MDT というアドイン データ ファイルに情報を格納しています。

通常、このファイルは以下のディレクトリに配置されています。

<システムドライブ>:\Documents and Settings\<ユーザーアカウント>\Application Data\Microsoft\Access

※ NT 4.0 だと WINNT\Profiles 配下かもしれません。

ファイル名は若干違うかもしれませんが、MS-Access の他のバージョンでも同様のファイルが有るはずです。

この ACWUSR.MDT の中に tblLanguages というテーブルが有ります。

名前から推測するに、ユーザーが使用する言語情報を格納するテーブルっぽいのですが、私の環境で見ると、なぜか Japanese ではなく Chinese が有効になっているのです。

私は個人的にはウィザードをほとんど使わないので、この設定の影響がどこにどう出ているのか(出ていないのか)よく分からないのですが、それでも日本語版では Japanese が有効になっているべきなんじゃないか、という気がしてならない今日この頃。

更新履歴