関数 | Excel作業をVBAで効率化 https://vbabeginner.net いつものExcel作業はVBAを使えば数秒で終わるかもしれませんよ Sat, 09 Nov 2024 14:19:13 +0000 ja hourly 1 https://wordpress.org/?v=6.6.2 https://vbabeginner.net/wp-content/uploads/2019/02/favicon-150x150.png 関数 | Excel作業をVBAで効率化 https://vbabeginner.net 32 32 VBAでフォルダやファイルの更新日時を取得する(FileDateTime) https://vbabeginner.net/filedatetime/ Wed, 30 Aug 2023 15:33:54 +0000 https://vbabeginner.net/?p=6876 FileDateTime関数

FileDateTime関数は指定したファイルやフォルダやドライブの更新日時を返します。

この更新日時はDate型のため年月日時分秒のデータになります。ミリ秒やマイクロ秒は持っていません。

FileDateTime関数は引数のファイルやフォルダが存在しない場合にエラーになり、処理が止まるため、引数のファイル存在チェックが必須になります。また、日時を文字列に変換するFormat関数も一緒に扱った方が都合がよいです。後述のサンプルコードで説明します。

構文

Function FileDateTime(PathName As String)

PathName 更新日時を確認したいファイルやフォルダのパスを文字列で指定します。

存在しないパスの場合は実行時エラー53が発生して処理が止まります。

また、パスが不完全な場合は実行時エラー76が発生し処理が止まります。

戻り値 厳密には戻り値型は規定されていないためVariant型になりますが、実質ではDate型でPathNameで指定されたパスの更新日時が返されます。

発生するエラー

FileDateTime関数では主に2つのエラーが発生します。

1つは、FileDateTime関数の引数のパスが存在しない場合に「実行時エラー’53’ ファイルが見つかりません。」が発生します。例えば、ファイル名だけの「abc.txt」だけを引数に指定すると発生します。

もう1つは、FileDateTime関数の引数のパスにファイル名しか指定していないなどでフォルダパスが不完全な場合に「実行時エラー’76’ パスが見つかりません。」が発生します。例えば、本来は「C:\abc\test.txt」のフルパスを「abc\test.txt」のようにドライブや親フォルダが書いてないと発生します。

いずれの場合も、「C:\aaa\test.txt」のようにドライブからファイル名までの正しいフルパスが書いてないことが原因です。

ファイルの更新日時を取得するサンプル

Sub FileDateTimeTest0()
    Debug.Print FileDateTime("C:\aa.txt")
End Sub

実行結果例
2020/01/16 2:32:47

引数のファイルパスが存在していれば、そのファイルの更新日時が返されます。

フォルダやドライブの更新日時を取得するサンプル

Sub FileDateTimeTest1()
    Dim d   As Date
    d = FileDateTime("C:\abc\testあああ")
    Debug.Print d
End Sub

実行結果例
2019/12/25 0:58:10

引数のフォルダパスが存在していれば、そのフォルダの更新日時が返されます。

注意点

フォルダパス(ドライブパスも同様)を指定する場合に注意点があります。

それは、フォルダパスの右端にパス区切り文字の「\」や「/」を指定してはいけない、という点です。パス区切り文字が右端にあると「実行時エラー’53’ ファイルが見つかりません。」のエラーになります。

そのため上のサンプルコードの引数の右端に「\」があるとエラーになります。

Sub FileDateTimeTest2()
    Dim d   As Date
    d = FileDateTime("C:\abc\testあああ\")
    Debug.Print d
End Sub

引数右端に「\」があるためこれはエラーになります。

FileDateTime関数は引数存在チェックとFormat関数が必須

先に書いたとおり、FileDateTime関数は引数が存在しない場合にエラーになります。また、戻り値がDate型のため、指定フォーマットの文字列で日時を取得したい場合はFormat関数を利用することが必要になります。

以下のサンプルはそれらの問題に対応した関数です。

ファイルパスが存在するかをDir関数で判定しています。

Dir関数でのファイル存在チェックの詳細については「VBAでファイルの存在をチェックする」をご参照ください。

また、ファイルの更新日時を”yyyy-mm-dd hh:mm:ss”の文字列で取得するためにFormat関数を利用しています。

Sub GetFileTimestamp()
    Dim filePath    As String       '// ファイルパス
    Dim updDateTime As Date         '// 更新日時
    Dim s           As String       '// 更新日時文字列
    
    '// ファイルのパスを指定します
    filePath = "C:\aaa\料理\とり肉の山賊焼き.pdf" ' ファイルの実際のパスに置き換えてください
    
    '// ファイルの存在しない場合
    If Dir(filePath) = "" Then
        MsgBox "ファイルが見つかりません。"
        Exit Sub
    End If
    
    '// ファイルの更新日時を取得
    updDateTime = FileDateTime(filePath)
    
    '// 指定フォーマットで取得
    s = Format(updDateTime, "yyyy-mm-dd hh:mm:ss")
    
    Debug.Print s
End Sub

実行結果例
2021-07-31 01:38:40

On Error Resume Nextでエラーを回避する

上のコードのようにファイル存在チェックを入れてエラー回避することをお勧めしますが、「On Error Resume Next」でエラーが発生しても処理を続行するようにすることも可能です。

Sub FileDateTimeTest4()
    On Error Resume Next
    Dim d   As Variant
    d = FileDateTime("C:\aaa\料理\とり肉の山賊焼き.pdf")
    Debug.Print d
    
    If Err.Number > 0 Then
        Debug.Print Err.Description
    End If
End Sub

]]>
VBAでワークシート関数のVLOOKUPを使う https://vbabeginner.net/vlookup/ Sat, 13 May 2023 16:39:39 +0000 https://vbabeginner.net/?p=6698 VLOOKUP関数とは

このページを見てる時点でVLOOKUP関数の説明は不要とは思いますが、一応軽く説明します。

VLOOKUP関数はExcelのセルに埋め込むワークシート関数の1つで、縦に並んだ表形式のセルに書いてあるデータの中から条件に合う1つを取り出す関数です。

引数が4つと多いため、使い方に少し慣れが必要です。

構文
=VOOKUP(検索値, 範囲, 列番号, [検索方法])

検索値 表形式のセル範囲から探したい値と対になるキーを指定します。
範囲 表形式のセル範囲を指定します。キーと値が範囲内に入っていることが前提になるため一般的には2行以上の範囲を指定します。
列番号 表形式のセル範囲のキーの列から見て値が何列目になるかを指定します。範囲でD2:E5としてした場合、D列を1、E列は2と数えます。
検索方法 近似一致の場合はTRUE、完全一致の場合はFALSEを指定します。省略時はTRUE扱いになります。
戻り値 VLOOKUP関数の結果(見つかったデータ)が返ります。

以下はB3セルにVLOOKUP関数を設定した場合の例です。右側の赤いD2:E5の範囲から、コード「B」の対になる名称である「名称2」を表示するためにB3セルに「=VLOOKUP(B2,D2:E5,2,FALSE)」を設定しています。

通常のVLOOKUP関数の使い方は以上です。

VBAでVLOOKUP関数を使うには

VBAでVLOOKUP関数を使うには、セルにVLOOKUP関数の数式を入力する方法と、セルには入力せずにVBA処理内でVLOOKUP関数を使う方法の2つに大別されます。

どちらでもいいのですが、場合によってはセルに入力できない場合もあるため、そのような場合はVBAの処理内で実行します。

セルにVLOOKUP関数の数式を入力する場合は、対象セルのRangeオブジェクトのFomulaプロパティに「=VLOOKUP(・・・)」と数式を書きます。

この場合は、セルの書式を「標準」などのVLOOKUP関数を実行できるセル書式にしておく必要があります。

セルに「=VLOOKUP()」を入力せずにVBA処理内でVLOOKUP関数を使う場合には、3つの書き方があります。

WorksheetFunctionプロパティを使う方法、Evaluateメソッドを使う方法、角括弧[]を使う方法の3つです。

これら4つの書き方はどれを使ってもVLOOKUP関数の結果が得られますので、状況や好みに応じて使い分けをしてください。

1つずつ説明していきます。

1. セルにVLOOKUP関数の数式を入力する方法(Range.Fomulaプロパティ)

セルにVLOOKUP関数を設定する場合はRangeオブジェクトのFormulaプロパティに、セルに入力するときと同じようにVLOOKUP関数の数式を設定します。

以下のコードは同じシートにある表形式のセル範囲を参照する場合です。

Sub VlookupTest1()
    Range("B4").Formula = "=VLOOKUP(B2,D2:E5,2,FALSE)"
End Sub

VLOOKUP関数を使う場合に別シートに表形式のセル範囲を用意することもよくあります。その場合もセルに手で入力するときと同じで、VLOOKUP関数の第二引数には「シート名!セル範囲」の形式で書きます。他の引数は上と同じです。

Sub VlookupTest2()
    Range("B4").Formula = "=VLOOKUP(B2,Sheet2!D2:E5,2,FALSE)"
End Sub

VBAにFormuraプロパティを使って書く場合は、一度セルにVLOOKUP関数を書いて、それをコピーしてVBAに反映させるとラクです。

2. WorksheetFunctionプロパティを使う方法

WorksheetFunctionプロパティを使う場合はVLookup関数の第一引数と第二引数はRangeオブジェクトでセルを指定する必要があります。

Sub WorksheetFunctionVlookupTest()
    Dim s   As String   '// VLOOKUP関数の結果
    
    s = Application.WorksheetFunction.VLookup(Range("B2"), Range("D2:E5"), 2, False)
    
    Debug.Print s
End Sub

以下のように第一引数や第二引数に座標文字列だけを渡した場合は構文エラーになります。

Application.WorksheetFunction.VLookup(“B2”, “D2:E5”, 2, False)

3. Evaluateメソッドを使う方法

Evaluateメソッドを使う場合は、RangeオブジェクトのFormulaプロパティのときとほとんど同じ書き方になります。

Evaluate関数の引数文字列の左端にはExcel関数の数式を表す「=」は一般的には付けません。でも左端の「=」付けてても動作するため、Excelのセルに書いてある数式をそのままコピペしても構いません。

Sub EvaluateVlookupTest()
    Dim s   As String   '// VLOOKUP関数の結果
    
    s = Application.Evaluate("VLOOKUP(B2,D2:E5,2,FALSE)")
    
    Debug.Print s
End Sub

VLOOKUP関数を使う場合にはほとんど関係ありませんが、Evaluateメソッドで文字列を扱うワークシート関数を書く場合、文字列のダブルクォーテーションのエスケープが必要になってきます。

例えば「=DATEDIF(“2023/1/1”, TODAY(), “D”)」とセルに書かれている数式をEvaluateメソッドの引数に転記する場合は、以下のように日付部分「2023/1/1」と「D」のダブルクォーテーションのエスケープが必要になります。

Application.Evaluate(“DATEDIF(“”2023/1/1“”, TODAY(), “”D“”)”)

4. []を使う方法

Evaluateメソッドと同じように全てのワークシート関数を角括弧の[]で書くことも出来ます。

セルの数式をコピペで大体いけます。ただ、この書き方はVBAでは一般的とは言えないため、未来の自分や他の人が見たときに「なにこれ?」って言われることがあるかもしれません。

Sub SquareVlookupTest()
    Dim s   As String   '// VLOOKUP関数の結果
    
    s = [VLOOKUP(B2,D2:E5,2,FALSE)]
    
    Debug.Print s
End Sub

]]>
VBAで疑似的にキーボード入力を行う(SendKeys) https://vbabeginner.net/sendkeys/ Thu, 12 Jan 2023 16:37:35 +0000 https://vbabeginner.net/?p=6572 VBAからキーボードの入力操作を行うには

文字の入力はキーボードを使って行うのが普通です。これを疑似的に行うことが出来るのがSendKeysステートメントです。実際にはキーボード入力を行っていませんが、入力したときと同じ動作をします。

通常のキーボードの入力はアクティブウィンドウに対して行います。SendKeysステートメントも同様で、Excelに限定されず、アクティブウィンドウになっているアプリケーションがキー入力の対象になります。

一つの用途として、VBAで外部アプリケーションを起動もしくはアクティブにして、そのあとにSendKeysステートメントでキーボードのショートカットを入力する、というようなことも可能になります。

Application.SendKeysステートメントではなくWshShellオブジェクトのSendKeysを使う

VBAにはSendKeysが2つあります。VBA標準のApplicationオブジェクトのSendKeysステートメントと、WshShellオブジェクトのSendKeysメソッドです。

SendKeysの使い方は同じですが、後者のWshShellオブジェクトを使う場合はCreateObject関数か参照設定をしておく手間があります。しかし、その手間があってもWshShellオブジェクトのSendKeysメソッドを使うことをお勧めします

というのも、VBA標準のApplication.SendKeysステートメントには、実行するとNumLockキーがオフになり数値入力ではなく矢印キーなどの扱いになるバグのような挙動になることがあるためです。これは極めて高い頻度で発生します。

もう一度NumLockキーを押すSendKeysステートメントを実行すれば元に戻りますが、元のNumLockキーの状態がオンなのかオフなのか分からないと、もう一度NumLockキーを押すかどうかの判定ができません。元がオンであればオフになってしまいます。

このNumLockバグは、小さいサイズのノートPCのキーボードのようにテンキーが無いのであれば影響はないかもしれませんが、USBで外部キーボードを接続したりすると、そうもいきません。

Win32APIのGetKeyboardState関数を使ってNumLockキーがオン、オフのどちらなのかを判定して、Win32APIのkeybd_event関数でNumLockキーの状態をオフやオフに変更することは出来ますが、はっきり言って面倒です。

実際にSendKeysを使う場面では、単純な文字列を入力するよりも、Ctrlキーなどと一緒にショートカットキーとして入力することの方が圧倒的に多いはずです。その場合は上記のNumLockオンのバグが高確率で発生します。

このように、NumLockのバグに対応しようとするとなかなか大変ですが、解決方法があります。それはWshShellオブジェクトのSendKeysメソッドを利用する方法です。この場合は上記バグは発生しません。使い方も全く同じため、WshShell.SendKeysの利用をお勧めします。

以降で紹介する処理はWshShell.SendKeysの利用する方法で記載しています。

WshShell.SendKeysを関数化する

WshShellは先に書いた通り、CreateObject関数か参照設定をしておく必要があります。

CreateObject関数:
「Set w = CreateObject(“WScript.Shell”)」

WshShellの参照設定:
VBA画面→ツールメニュー→参照設定、を選択し、参照設定ダイアログで「Windows Script Host Object Model」にチェックを付けます。

参照設定はVBAに限定されますが、SendKeysはVBScriptでも実行できますので、CreateObject関数を使う方法で考えると、以下のようにWshShell.SendKeysを実行する関数を用意しておくと、何も考えずに済みます。

Public Sub SendKeys(Keys As String, Optional Wait As Boolean = False)
    Static w    As Object                               '// WshShellオブジェクト
    
    '// WshShellオブジェクトが生成されていない場合
    If w Is Nothing Then
        '// WshShellオブジェクトを生成
        Set w = CreateObject("WScript.Shell")
    End If
    
    Call w.SendKeys(Keys, Wait)
End Sub

CreateObject関数は処理に時間が掛かるため、上記SendKeys関数が呼ばれたときにWshShellオブジェクトが生成済みかどうかを判定して、未作成の場合のみ作成するようにします。SendKeys関数を抜けたあともStaticとして変数は保持されますので、2回目以降にSendKeys関数を実行したときはCreateObject関数は実行されません。

上記の関数の名前が「SendKeys」となっていますが、SendKeysステートメントと同じ名前であっても、関数の方が動作します。VBA標準のSendKeysを使いたい場合は「Application.SendKeys」と書けば利用できます。

構文

Sub SendKeys(Keys, [Wait])

Keys 入力するキーボードのキーを文字列で指定します。
Wait省略可 キーボード操作が終わるまでVBAの次の処理を行うかどうかをBoolean(TrueかFalse)で指定します。Trueの場合はキーボード入力操作が終わるまで待ちます。False(規定値)の場合はキーボード入力操作が終わるのを待たずに次の処理を行います。

キーボード入力一覧

キー 設定値
a~z “a” ~ “z”
A~Z “A” ~ “Z”(Shiftキーと小文字英字を組み合わせずに大文字をそのまま利用可能)
0~9 “0” ~ “9”
記号キー “!”、”#” のように””で囲う。ダブルクォーテーション(”1文字は)””””と書く。
F1~F16 {F1} ~ {F16}
Esc {ESC}
Tab {TAB}
CapsLock {CAPSLOCK}
BackSpace {BACKSPACE}、{BS}、 {BKSP} のいずれか
Enter {ENTER} または ~
PrintScreen {PRTSC}
ScrollLock {SCROLLLOCK}
Break {BREAK}
Insert/Ins {INSERT} または {INS}
Home {HOME}
PageUp {PGUP}
Delete/Del {DELETE} または {DEL}
End {END}
PageDown {PGDN}
{UP}
{DOWN}
{LEFT}
{RIGHT}
NumLock {NUMLOCK}
キー 設定値
Shift +
Ctrl ^
Alt %

テンキーの数字はSendKeysで入力できません。どうしてもテンキーの操作を行いたい場合は、Win32APIのkeybd_event関数を利用してください。

利用例

キーボードの各キーの入力をSendKeysで行う場合、キー入力は文字列として扱うためダブルクォーテーションで囲います。

ショートカットキーとして、Shift(+)、Ctrl(^)、Alt(%)と英数字を組み合わせる場合は、英数字を丸括弧で囲みます。(例:Ctrl + a = “^(a)”)

上記のWshShell.SendKeysを関数化したSendKeys関数があればそちらが呼び出され、無ければVBA標準のSendKeysステートメントが呼び出されます。

Sub SendKeysTest()
    '// メモ帳起動(キーボード入力を行うアプリケーションをアクティブにする)
    Shell "Notepad.exe", 1
    
    '// 12345 を入力
    Call SendKeys("12345 abc ABC ")
    
    '// Ctrl + a(全選択)
    Call SendKeys("^(a)")
    
    '// Ctrl + c(コピー)
    Call SendKeys("^(c)")
    
    '// Ctrl + v(貼り付け)×2回
    Call SendKeys("^(v)")
    Call SendKeys("^(v)")
    
    '// Home(カーソルを先頭に移動)
    Call SendKeys("{HOME}")
End Sub

実行すると、新規のメモ帳に「12345 abc ABC 12345 abc ABC 」とキーボード入力が行われ、カーソルを先頭(左端)に移動します。

]]>
VBAでメッセージボックスを表示する(MsgBox) https://vbabeginner.net/msgbox/ Sun, 08 Jan 2023 02:52:08 +0000 https://vbabeginner.net/?p=6553 MsgBox関数はメッセージダイアログを表示する

MsgBox関数は、出したいメッセージをメッセージダイアログで表示します。

表示されるメッセージダイアログの見た目はWindowsの設定によって変わります。

MsgBox関数の戻り値で「はい」や「いいえ」や「×ボタン」などのどのボタンが押されたが分かる仕組みになっています。

単純にメッセージダイアログを出す方法

Sub MsgBoxTest1()
    Call MsgBox("MsgBoxのテスト。")
End Sub


メッセージダイアログのどのボタンが押されたかを判定する方法

Sub MsgBoxTest2()
    Dim result
    result = MsgBox("MsgBoxのテスト。")
    
    Debug.Print result
End Sub

構文

Function MsgBox(Prompt, [Buttons As VbMsgBoxStyle = vbOKOnly], [Title], [HelpFile], [Context]) As VbMsgBoxResult

Prompt(必須) ダイアログダイアログに表示する文字列を設定します。
Buttons(省略可) 主にボタンの種類を設定します。

他にも、アイコンの種類、標準ボタン(Enterキーで押せるボタン)、メッセージダイアログのモーダル状態、ヘルプボタン、最前面表示、テキスト位置、などを定数で指定します。指定方法は癖があるため後述します。省略すると「OK」ボタンが表示されます。

Title(省略可) ダイアログボックスのタイトルバーに表示する文字列を設定します。省略すると「Microsoft Excel」と表示されます。
HelpFile(省略可) 第二引数ButtonsでvbMsgBoxHelpButtonを指定してヘルプボタンを押したときに表示するヘルプファイルのパスを設定します。
Context(省略可) 第二引数ButtonsでvbMsgBoxHelpButtonを指定してヘルプボタンを押したときに表示するヘルプの内容に対応する番号を設定します。
戻り値 押したボタンの種類がVbMsgBoxResult列挙型の定数で返されます。

定数 ボタン
vbOK 1 「OK」ボタン
vbCancel 2 「キャンセル」ボタン
vbAbort 3 「中止」ボタン
vbRetry 4 「再試行」ボタン
vbIgnore 5 「無視」ボタン
vbYes 6 「はい」ボタン
vbNo 7 「いいえ」ボタン

引数がたくさんありますが、一般的に利用するのは第一引数(メッセージ本文)と第二引数(ボタンの種類)です。他は必要なときに使うぐらいです。

Buttonsの定数

第二引数のButtonsはVbMsgBoxStyle列挙型で定義されています。

5種類(ボタン、アイコン、標準ボタン、状態、その他)に分けられており、+記号で組み合わせ、「vbYesNo + vbInformation」または「4 + 64」または「68」のように指定します。

ボタンの種類
定数 説明
vbOKOnly 0(初期値) 「OK」ボタンを表示します。×ボタンも「OK」ボタン扱いになります。
vbOKCancel 1 「OK」「キャンセル」ボタンを表示します。×ボタンは「キャンセル」ボタン扱いになります。
vbAbortRetryIgnore 2 「中止」「再試行」「無視」ボタンを表示します。×ボタンは非活性で押せません。
vbYesNoCancel 3 「はい」「いいえ」「キャンセル」ボタンを表示します。×ボタンは「キャンセル」ボタン扱いになります。
vbYesNo 4 「はい」「いいえ」ボタンを表示します。×ボタンは非活性で押せません。
vbRetryCancel 5 「再試行」「キャンセル」ボタンを表示します。×ボタンは「キャンセル」ボタン扱いになります。
メッセージボックスの表示状態
定数 説明
vbApplicationModal 0(初期値) メッセージボックスのボタン操作が終わるまでExcelの操作ができない状態(モーダル状態)になります。
vbSystemModal 4096 メッセージボックスのボタン操作が終わるまでExcelを含む全てのアプリケーションでメッセージボックスが表示された状態(常にメッセージボックスが見える状態)になります。
アイコンの種類
定数 説明
vbCritical 16 警告アイコン(×マーク)を表示します。
vbQuestion 32 問い合わせアイコン(?マーク)を表示します。
vbExclamation 48 感嘆符アイコン(!マーク)を表示します。
vbInformation 64 情報アイコン(小文字のi)を表示します。
標準ボタン(エンターキーで実行されるボタン)の指定
定数 説明
vbDefaultButton1 0(初期値) 一番左のボタンを標準ボタンにします。
vbDefaultButton2 256 左から2番目のボタンを標準ボタンにします。2番目のボタンが無い場合は無視されます。
vbDefaultButton3 512 左から3番目のボタンを標準ボタンにします。3番目のボタンが無い場合は無視されます。
vbDefaultButton4 768 左から4番目のボタンを標準ボタンにします。4番目のボタンが無い場合は無視されます。
その他(これらは通常使うことはありません)
定数 説明
vbMsgBoxHelpButton 16384 ヘルプボタンを追加します。
vbMsgBoxSetForeground 65535 メッセージボックスウィンドウを前面ウィンドウとします。ただ、指定しなくても通常は最前面に表示されます。
vbMsgBoxRight 524288 テキストを右寄せにします。
vbMsgBoxRtlReading 1048576 アラビア語のようにタイトル、メッセージ本文、ボタン配置がいずれも左右逆に表示します。

引数を括弧で囲う書き方と囲わない書き方

どのボタンが押されたかの判定が不要で、単にメッセージだけを表示させたい場合は、第一引数のPromptのみにメッセージ本文を指定します。ボタンの種類を指定していませんが、ボタンは「OK」ボタンのみが表示されます。

この場合、MsgBox関数の呼び出し方にはCallを使うか使わないかの2通りの書き方があります。

Callを使う場合

'// Callを使って関数呼び出しを明示する書き方(引数を括弧で囲う)
Sub MsgBoxTest3()
    Call MsgBox("MsgBoxのテスト")
End Sub

 

Callを使わない場合

'// Callを使わない場合の書き方(括弧不要)
Sub MsgBoxTest3()
    MsgBox "MsgBoxのテスト"
End Sub

一般的には括弧を書かない書き方が多い気がしますが、個人的には関数呼び出しを明示するためにCallを使う方がよいと思われます。

メッセージを変数を使って可変表示する場合

メッセージ本文に可変部分が指定した場合は、変数を利用することも可能です。

Sub MsgBoxTest4()
    Dim n
    
    n = Now
    
    Call MsgBox("MsgBoxのテスト" & n)
End Sub

上のコードではNow関数の結果の日時(変数n)をそのまま&で文字列”MsgBoxのテスト”に連結していますが、メッセージは文字列のため変数が文字列でない数値などの場合は暗黙の変換を期待せず、以下のようにCStr関数を使って文字列に変換する方が無難です。

CStr関数などの型変換関数についての詳細は「VBAの型変換関数(キャスト)」をご参照ください。

Sub MsgBoxTest5()
    Dim i   As Integer
    
    i = 12345
    Call MsgBox("MsgBoxのテスト" & ":" & CStr(i))
End Sub

メッセージを改行して複数行表示するには

メッセージを改行して複数行表示したい場合は、メッセージ文字列の改行したい位置で、改行コードを連結します。

改行コードにはLF、CR、CRLFの3種類がありますが、いずれを使ってもメッセージは改行されます。

  • LF(ラインフィード):「vbLf」または「Chr(10)」
  • CR(キャリッジリターン):「vbCr」または「Chr(13)」
  • CRLF:「vbCrLf」または「Chr(13) & Chr(10)」

Sub MsgBoxTestLF()
    Call MsgBox("1行目" & vbLf & "2行目" & Chr(10) & "3行目")
End Sub

Sub MsgBoxTestCR()
    Call MsgBox("1行目" & vbCr & "2行目" & Chr(13) & "3行目")
End Sub

Sub MsgBoxTestCRLF()
    Call MsgBox("1行目" & vbCrLf & "2行目" & Chr(13) & Chr(10) & "3行目")
End Sub

押したボタンの種類で処理を分ける場合

メッセージボックスのどのボタンのどちらを押したかで処理を変えたい場合は、MsgBox関数の戻り値で判定できます。

例えば、第二引数に「vbYesNoCancel」を指定して「はい」「いいえ」「キャンセル」ボタンを表示するようにしている場合、「はい」ボタンであれば「vbYes(値は6)」、「いいえ」ボタンであれば「vbNo(値は7)」、「キャンセル」ボタンであれば「vbCancel(値は2)」がMsgBox関数の戻り値として返されます。

この場合はCallではなく戻り値を受け取る書き方になります。

Sub MsgBoxYesNoCancelTest()
    Dim result As Integer
    
    result = MsgBox("MsgBoxのテスト", vbYesNoCancel)
    
    '// はい が押された場合
    If result = vbYes Then
        Debug.Print "はい が押された:" & CStr(result)      '// "はい が押された:6"
    '// いいえ が押された場合
    ElseIf result = vbNo Then
        Debug.Print "いいえ が押された:" & CStr(result)    '// "いいえ が押された:7"
    '// キャンセル または ×ボタンが押された場合
    Else
        Debug.Print "キャンセル または ×ボタンが押された:" & CStr(result)    '// "キャンセル または ×ボタンが押された:2"
    End If
End Sub

メッセージボックスはボタンが複数ある場合に×ボタンを押せることがありますが、いずれも「キャンセル」ボタン扱いになります。OKボタンだけのメッセージボックスでも×ボタンを押せますがその場合は「OK」ボタン扱いになります。

MsgBox関数ではフォントの変更は出来ない

メッセージボックスに表示するメッセージの見た目は変更できません。そのため、メッセージのサイズ、色、太さ、フォントの変更は出来ません。

どうしても見た目を変えたいのであれば、ユーザーフォームでメッセージボックスを自作してそれを利用する方法があります。

メッセージボックスは処理が止まっていいところで出すようにする

メッセージボックスを多用するのは避けましょう。理由は大きく2つあります。

1つ目の理由は、メッセージボックスが表示されている間はVBAの処理が止まるためです。メッセージボックスのボタンを押されるまでずっと次の行の処理が待たされます。そのため、メッセージボックスは処理の最後に「処理が終わったよ」ということを通知することに使うなど、後続処理に影響がないタイミングで出すようにした方がよいです。

2つ目の理由は、うるさい、です。マクロを実行する度に毎回毎回メッセージボックスが表示されると、「あー、うるさいな」と感じます。メッセージボックスは必ずボタン操作を要求するため、それが煩わしく感じてくることがあります。そういう場合は出さない方が精神衛生上よいです。

でも、処理の状況を知りたい、ということはあります。そういう場合はメッセージボックスではなく「Debug.Print」でイミディエイトウィンドウに出力したり、テキストファイルに処理状況を出力するなどして処理を止めないようにしましょう。

Debu.Printの例

Sub MsgBoxTest7()
    
    '// MsgBoxをやめる
    '// Call MsgBox("処理の途中報告")
    
    '// かわりにDebug.Printを使う
    Debug.Print "処理の途中報告"
End Sub

テキストファイルの例
テキストファイルに出力する場合は以下のようにOpen、Print、Closeステートメントを使うことで対応できます。

Sub MsgBoxTest8()
    Dim n       As Integer  '// ファイル番号
    Dim sPath   As String   '// テキストファイルパス
    
    sPath = "C*\test\a.txt"
    
    '// ファイル番号取得
    n = FreeFile
    
    '// 追加モードでファイルを開く
    Open sPath For Append As #n
    
    Print #n, "処理の途中報告"
    
    '// ファイルを閉じる
    Close #n
End Sub

テキストファイルへの出力の詳細については「VBAでテキストファイルの読み書きを行う」をご参照ください。

他にもFileSystemObjectを使ってテキストファイルを出力する方法もあります。詳細は「VBAでのFileSystemObjectとTextStreamの使い方」をご参照ください。

]]>
VBAのオブジェクト変数かどうかを判定する(IsObject) https://vbabeginner.net/isobject/ Sun, 28 Nov 2021 16:28:54 +0000 https://vbabeginner.net/?p=6360 IsObject関数

VBAで扱う変数がオブジェクト変数かどうかを調べたい場合、IsObject関数で判定できます。

判定はデータ型のみで行われるため、変数の値は判定には影響しません。

オブジェクト変数とはクラス型の変数のことを指します。具体的には、変数の後ろにドット(.)を入力してプロパティやメソッドが表示されるものはオブジェクト変数です。例えばセルを表すRange型の変数もオブジェクト変数の一種で、ドット(.)を入力するとValueなどのプロパティが表示されることからオブジェクト変数であることが分かります。

Integer型やString型はドット(.)を押しても候補表示はされません。これらはオブジェクト変数ではないと判別できます。なおこれらクラス型ではないデータ型のことを総称してプリミティブ型と言います。

コードを書いている最中はドット(.)を入力することでオブジェクト変数かどうか判定できますが、処理中に判定したい場合にIsObject関数を利用します。

構文

Function IsObject(Expression) As Boolean

Expression 引数を指定します。
戻り値 引数で渡された変数がオブジェクト変数の場合はTrue、そうでない場合はFalseを返します。

配列のデータ型を判定したい場合は、要素ごとに判定する必要があります。

オブジェクト型の配列の各要素(obj[0]など)を判定した場合はTrueを返しますが、配列自体を判定した場合は保持するデータ型に関わらずFalseを返します。

用途

IsObject関数の主な用途としては、Variant型の変数が実際にはどういうデータ型なのかを知りたいときに利用します。

例えば、自作した関数の引数がVariant型で、関数の内部で代入処理が必要な場合に、引数がオブジェクト型かどうかで代入の書き方が変わります。オブジェクト型であればSetステートメントを使って「Set obj = 引数」のように書き、オブジェクト型でなければ=を使って「s = 引数」のように書かなければなりません。そのような判定にIsObject関数を利用します。

サンプルコード

以下のコードはVariant型の引数を持つ関数で、その引数のデータ型をIsObject関数を使ってオブジェクト型かどうかを判定します。

2つ関数がありますが、1つ目がIsObject関数を使っている関数で、2つ目のIsObjectSampleTest関数はIsObjectSample関数を呼び出しています。

Sub IsObjectSample(a As Variant)
    '// オブジェクト型の場合は処理せず抜ける
    If IsObject(a) = True Then
        Exit Sub
    End If
    
    Range("A1").Value = a
End Sub

Sub IsObjectSampleTest()
    Dim str As String
    Dim obj As Object
    
    str = "abc"
    Set obj = Range("B2")
    
    '// 実行後にA1セルに"abc"を出力
    Call IsObjectSample(str)
    
    '// 何もしない
    Call IsObjectSample(obj)
End Sub

]]>
VBAでファイルのサイズを取得する(FileLen) https://vbabeginner.net/filelen/ Wed, 15 Jan 2020 17:28:16 +0000 https://vbabeginner.net/?p=4559 FileLen関数

FileLen関数は指定したファイルのサイズをバイト単位で返します。

指定したファイルが存在しない場合はエラーになります。(実行時エラー ’53’: ファイルが見つかりません。)

Long型の精度のため約2GBを超えるファイルの場合は取得できません。その場合の対応方法は後述しています。

構文

Function FileLen(PathName As String) As Long

PathName ファイルサイズを取得するファイルパスを指定します。ファイル名だけを指定することも可能です。
戻り値 Long型でファイルサイズを返します。詳細は後述していますが、約2GBを超えるファイルの場合は正しくファイルサイズを取得できません。

単純なコードであればこのような感じになります。

Sub FileLenTest()
    Dim iSize
    
    '// アクティブブックのファイルサイズを取得
    iSize = FileLen(ActiveWorkbook.FullName)
End Sub

エラー発生時の対応方法

指定したファイルが存在しない場合は「実行時エラー ’53’: ファイルが見つかりません。」が発生し、そこで処理が止まります。

FileLen関数を使う場合は、ファイルパスは変数で渡すことが多く、可変であるため、そのファイルが存在しているかどうかが不明なままFileLen関数を実行することが多くなります。

そうなると当然、ファイルが存在しないことも発生しやすくなり、結果としてエラーになり処理が止まってしまう、ということに陥ります。

そこで、事前にそのファイルが存在するのかをチェックを行い、存在するファイルパスのみをFileLen関数を行うようにしてエラーを回避します。

以下のサンプルでは、ファイル存在チェックを含めたコードを紹介します。

サンプルコード

指定ファイルパスのファイルサイズを出力するサンプルコードです。

上記の通り、ファイルが存在しない場合はエラーで処理が止まるため、ファイルの存在チェックを行い、存在する場合のみFileLen関数でファイルサイズを取得するようにしています。

ファイル存在チェックの「IsExistFileDir」関数は「VBAでファイルの存在をチェックする」のサンプルコードを利用しています。

Sub FileLenTest()
    On Error GoTo ERR_LABEL
    
    Dim iSize       As Long         '// ファイルサイズ
    Dim sFilePath   As String       '// ファイルパス
    Dim bRet        As Boolean      '// ファイル存在チェック結果
    
    '// ファイルパスを取得
    sFilePath = ActiveWorkbook.FullName
    
    '// ファイル存在チェック
    bRet = IsExistFileDir(sFilePath)
    
    '// ファイルが存在しない場合
    If (bRet = False) Then
        Exit Sub
    End If
    
    '// アクティブブックのファイルサイズを取得
    iSize = FileLen(sFilePath)
    
    Debug.Print "ファイルサイズ=" & CStr(iSize) & "バイト"
    Exit Sub
    
ERR_LABEL:
    '// エラー内容を出力
    Debug.Print Err.Number & " " & Err.Description
    
End Sub

'// ファイル存在チェック
Function IsExistFileDir(a_sFilePath) As Boolean
    Dim a
    
    a = Dir(a_sFilePath)
    If (a <> "") Then
        IsExistFileDir = True
    Else
        IsExistFileDir = False
    End If
End Function

約2GBを超えるファイルは正しく動作しない

FileLen関数はファイルサイズをLong型で返すため、Long型の範囲の-2,147,483,648から2,147,483,648(約2GB)を超えるファイルサイズの場合は正しく動作しません

試しに「2.17 GB (2,340,790,272 バイト)」のファイルを使ってFileLen関数を実行すると「1,954,177,024バイト」という不正な値が返されました。

このようなLong型の精度を超えるようなファイルサイズを取得する場合は、FileLen関数ではなく、FileSystemObjectクラスのGetFileメソッドを使ってFileオブジェクトを取得し、そのSizeプロパティを使うことで取得できます。SizeプロパティはVariant型のためLong型を超える精度でも取得可能です。

Sub FileSizeTest()
    Dim fso         As Object
    Dim f           As File
    Dim sFilePath   As String
    
    '// FileSystemObjectのインスタンス作成
    Set fso = CreateObject("Scripting.FileSystemObject")
    
    sFilePath = "C:\aaa\abc.mts"
    
    '// Fileオブジェクトを取得
    Set f = fso.GetFile(sFilePath)
    
    Debug.Print "ファイルサイズ=" & CStr(f.Size) & "バイト"
    
End Sub

]]>
VBAでカレントドライブとフォルダの変更を行う https://vbabeginner.net/change-current-drive-folder/ Sun, 01 Sep 2019 10:22:40 +0000 https://vbabeginner.net/?p=4461 ChDriveステートメント

ChDriveステートメントはカレントドライブの変更を行います。存在しないドライブを指定するとエラーになります。

残念ながら、ネットワークドライブへの変更はできません。

ネットワークドライブへ変更したい場合は「VBAでネットワークドライブにカレントを変更する」をご参照ください。

ChDirステートメント

ChDirステートメントはカレントフォルダの変更を行います。

カレントフォルダとは現在作業中のフォルダを指し、ダイアログボックスの初期表示フォルダになります。

存在しないフォルダをカレントフォルダに変更しようとした場合はエラーになります。エラーを回避したい場合は事前にフォルダが存在するかチェックを行ってください。VBAでフォルダ存在チェック」にてフォルダ存在チェックを紹介しています。

カレントドライブとは異なるドライブのフォルダへの変更を行う場合は、先にChDriveステートメントでカレントドライブを変更しておく必要があります。

構文

ChDriveの構文

ChDrive Drive

Drive
変更後のドライブ名を指定します。2文字以上が指定された場合は先頭の1文字をドライブ名として判定して処理されます。

ChDirの構文

ChDir Path

Path
変更後のカレントフォルダのパスを指定します。ドライブ名を省略した場合はカレントドライブの中にある指定したパスが対象になります。フォルダ名のみを指定した場合はカレントフォルダ内にあるフォルダが対象になります。

ChDriveとChDirは一緒に使った方がよい

ChDriveステートメントとChDirステートメントは一緒にセットとして使うようにした方がよいです。

理由は2つあります。

1つは、ChDirステートメントがドライブの変更が出来ないこと、もう1つがChDriveステートメントの引数にフォルダパスを渡してもエラーにならずにドライブ部分のみを解釈して処理してくれることです。

この特性から、ChDriveステートメントとChDirステートメントはセットにしておいた方が使い勝手がよいでしょう。

カレントドライブとフォルダの変更を行う関数

カレントドライブとフォルダの変更を何度も行う場合は、以下のように関数化しておいてもよいです。

単にChDriveステートメントとChDirステートメントを並べているだけですが、ドライブが変わったことを意識しなくてよい利点があります。

Sub ChDriveDir(sPath As String)
    '// カレントドライブを変更
    Call ChDrive(Drive:=sPath)
    
    '// カレントフォルダを変更
    Call ChDir(Path:=sPath)
End Sub

サンプルコード

開いているブックがあるフォルダをカレントフォルダとして設定後に、フォルダ選択ダイアログを表示するサンプルです。

ブックがあるフォルダはActiveWorkbook.Pathで取得します。ここをChDriveステートメントとChDirステートメントでカレントフォルダとしています。

Sub ChDirTest()
    Dim fd      As FileDialog
    Dim ret
    Dim buf
    Dim sPath
    
    '// アクティブブックのパスを取得
    sPath = ActiveWorkbook.Path
    
    '// カレントドライブをアクティブブックがあるドライブに変更
    Call ChDrive(Drive:=sPath)
    
    '// カレントフォルダをアクティブブックがあるフォルダに変更
    Call ChDir(Path:=sPath)
    
    '// フォルダを選択ダイアログを設定
    Set fd = Application.FileDialog(msoFileDialogFolderPicker)
    
    '// ダイアログを開く
    ret = fd.Show
    
    '// キャンセルボタンまたは×ボタンの場合
    If ret = 0 Then
        '// キャンセル時は処理を抜ける
        Exit Sub
    End If
End Sub

]]>
VBA専用のレジストリの操作を行う https://vbabeginner.net/operate-registry-dedicated-vba/ Fri, 23 Aug 2019 18:21:00 +0000 https://vbabeginner.net/?p=4451 VBAの機能で操作できるレジストリキー

VBAにはレジストリ操作用の関数が用意されていますがこれらの関数には制約があり、操作できるレジストリは「HKEY_CURRENT_USER\Software\VB and VBA Program Settings」配下のみになります。

それ以外の個所のレジストリの操作(登録、更新、取得、削除)を行う場合は「VBAで全てのレジストリの操作を行う(WshShell)」をご参照ください。

用途

これらの関数は「HKEY_CURRENT_USER\Software\VB and VBA Program Settings」配下のみでしか扱えないという強い制限のためどうしても用途が限られます。

具体的な用途としては、個人用マクロブックであるPERSONAL.XLSBに登録されているVBAの関数を使った場合に、前回値として保存しておくような場合になります。

PERSONAL.XLSBはあくまでもソースコードのため、処理中の変数の値を保持することは可能でも、Excel終了後も保持しておくことはできません。そのような場合に、次のExcel起動時の初期値としてレジストリから値を取得するような用途として利用します。

レジストリ操作関数

VBAではレジストリの操作を行う関数が4つ用意されています。

Win32APIのレジストリ関連の関数は30個以上あるためそれと比べると少ないですが、利用できるレジストリキーが「HKEY_CURRENT_USER\Software\VB and VBA Program Settings」配下のみに限定されていることから、以下のように必要最低限のことに限定されています。

関数 処理概要
SaveSetting関数 データ登録、更新
GetSetting関数 指定キーのデータ取得
GetAllSettings関数 指定セクションの全キー取得
DeleteSetting関数 データ削除

SaveSetting関数

SaveSetting関数はレジストリにキーを追加する関数です。「HKEY_CURRENT_USER\Software\VB and VBA Program Settings」キーの直下に追加されます。同じキーが既に存在する場合は値を上書きします。

Sub SaveSetting(AppName As String, Section As String, Key As String, Setting As String)

AppName(省略不可) アプリケーション名を文字列で指定します。
Section(省略不可) セクション名を文字列で指定します。
Key(省略不可) キー名を文字列で指定します。
Setting(省略不可) キーに設定する値を文字列で指定します。
戻り値 なし

GetSetting関数

GetSetting関数はレジストリのキーの値を取得する関数です。

Function GetSetting(AppName As String, Section As String, Key As String, [Default]) As String

AppName(省略不可) アプリケーション名を文字列で指定します。
Section(省略不可) セクション名を文字列で指定します。
Key(省略不可) キー名を文字列で指定します。
Default(省略可) キーが存在しない場合に返す値を指定します。省略した場合は””(空文字列)が設定されます。
戻り値 対象キーの値が返されます。対象キーが存在しない場合は引数Defaultの値を返します。

GetAllSettings関数

GetAllSettings関数は指定セクションに含まれる全てのキーとその値を二次元配列で返します。二次元目のインデックスが0の配列にはキー、1の配列には値が設定されます。

Function GetAllSettings(AppName As String, Section As String)

AppName(省略不可) アプリケーション名を文字列で指定します。
Section(省略不可) セクション名を文字列で指定します。
戻り値 セクションに含まれる全てのキーを配列で返します。

DeleteSetting関数

DeleteSetting関数は指定したキーを削除します。キーを省略した場合は指定したセクション全体を削除します。セクションも省略した場合は指定したアプリケーション名全体を削除します。

Sub DeleteSetting(AppName As String, [Section], [Key])

AppName(省略不可) アプリケーション名を文字列で指定します。
Section(省略可) セクション名を文字列で指定します。
Key(省略可) キー名を文字列で指定します。
戻り値 なし

サンプルコード

関数を呼び出すだけでレジストリ操作が可能です。

レジストリへの登録、更新、取得、削除、の一連の流れを書いたコードです。

Sub RegTest()
    '// レジストリを追加
    Call SaveSetting(AppName:="Test_AppName1", Section:="TEST_SECTION1", Key:="test_key1", Setting:="Test_Value")
    Call SaveSetting(AppName:="Test_AppName1", Section:="TEST_SECTION1", Key:="test_key2", Setting:="Test_Value")
    Call SaveSetting(AppName:="Test_AppName1", Section:="TEST_SECTION2", Key:="test_key1", Setting:="Test_Value")
    Call SaveSetting(AppName:="Test_AppName1", Section:="TEST_SECTION2", Key:="test_key2", Setting:="Test_Value")
    Call SaveSetting(AppName:="Test_AppName2", Section:="TEST_SECTION1", Key:="test_key1", Setting:="Test_Value")
    Call SaveSetting(AppName:="Test_AppName2", Section:="TEST_SECTION1", Key:="test_key2", Setting:="Test_Value")
    Call SaveSetting(AppName:="Test_AppName2", Section:="TEST_SECTION2", Key:="test_key1", Setting:="Test_Value")
    Call SaveSetting(AppName:="Test_AppName2", Section:="TEST_SECTION2", Key:="test_key2", Setting:="Test_Value")
    
    '// 上書き
    Call SaveSetting(AppName:="Test_AppName", Section:="TEST_SECTION1", Key:="test_key1", Setting:="Update")
    
    '// 取得
    Dim s
    s = GetSetting(AppName:="Test_AppName", Section:="TEST_SECTION1", Key:="test_key1")
    
    '// セクション取得
    Dim ar
    ar = GetAllSettings(AppName:="Test_AppName", Section:="TEST_SECTION1")
    Debug.Print ar(0, 0)    '// test_key1
    Debug.Print ar(0, 1)    '// Update
    
    '// 削除
    Call DeleteSetting(AppName:="Test_AppName")
    Call DeleteSetting(AppName:="Test_AppName1", Section:="TEST_SECTION1")
    Call DeleteSetting(AppName:="Test_AppName2", Section:="TEST_SECTION1", Key:="test_key1")
End Sub

]]>
VBAの処理を一時中断してWindowsの操作を行う(DoEvents) https://vbabeginner.net/doevents/ Fri, 16 Aug 2019 19:54:11 +0000 https://vbabeginner.net/?p=4434 DoEvents関数

DoEvents関数はVBAの処理を一時中断させてExcelの操作を行うことができるようになります。

通常、VBAの処理中はExcelの操作は出来ません。しかし、DoEvents関数を使うとVBAの処理を離れ、Excelの操作が可能になります。

用途としては、ループ処理中に途中で処理を止めたい場合やプログレスバーの表示などが挙げられます。Ctrl + Breakキーを押してVBAの処理を止めようと思っても止まらない事象に遭遇したことがあると思いますが、それも止まってくれます。

DoEvents関数でWindows側に制御が渡されたあとは、OSで管理しているイベントキューのキーイベントの全てのキーが送信されるとVBAに制御が戻ります。WindowsAPIのプログラミングやOSのタスク管理などが分からないとこの意味は分かりにくいかもしれませんが、分からなくても気にしなくていいです。意味が分からない方は0.1秒未満ぐらいDoEventsで待ってからVBAの処理が続行する、と思ってください。

VBA処理中もExcelの操作を行いたい場合にはDoEvents関数は有効ですが、上記の通り、Windowsの処理待ちが発生するためVBAの処理が長くなるなどのいくつかの欠点があります。

構文

DoEvents() As Integer

引数はありません。

戻り値はその時点で開いているフォームの数を返します。フォームを開いていない場合は0が返ります。通常は戻り値を利用することがないため、戻り値を受け取らずに関数名だけをコードに書くことが多いです。

DoEvents関数の欠点

DoEvents関数にはいくつか欠点があります。

  • VBAの処理が遅くなる。(対応方法:GetInputState関数(Win32API)を使う)
  • 想定外のエラーが頻発する。(対応方法:On Error Resume Nextでエラー回避する)
  • イベント処理は対象のブックに書く必要がある。(対応方法:イベント(ボタン押下やセル値変更など)が発生するような実装をしない)

これらの欠点から、重要性が高い処理の場合はDoEvents関数を使わないという判断をした方がいい場合がありますので、無理にDoEvents関数を使うよりも本来行うべき処理を優先させるかの判断が必要になることもあります。

ただ、それぞれの欠点には一応対応方法はあるため、ある程度は回避できます。

具体的には、DoEvents関数を使うときは、On Error Resume Nextは必須で、さらに高速化を求めるならGetInputState関数の利用することをお勧めします。

以下のサンプルコードは上記の欠点に対応しています。

サンプルコード

ループ内でDoEvents関数を呼び出してWindowsに処理を渡すサンプルです。

Windowsに処理が渡るようになっているため、ワークシートでの操作が可能です。B1セルに”a”を入力すると処理が止まります。

詳細は後述しますが、エラーでVBA処理が終了することやDoEvents関数の呼び出し回数が多いことによる処理が遅くなる点への対応も入れています。

'// 64bit版
#If VBA7 And Win64 Then
    Private Declare PtrSafe Function GetInputState Lib "user32" Alias "GetInputState" () As Long
'// 32bit版
#Else
    Private Declare Function GetInputState Lib "user32" () As Long
#End If

Sub DoEventsTest()
    On Error Resume Next
    
    Dim i
    Dim dTimer      '// 0時からの経過秒数
    Dim dElapsed    '// 前回Timer関数実行時からの経過秒数
    
    '// セルの入力値を削除
    Cells.Clear
    
    i = 0
    m_bButtonClick = False
    dTimer = Timer
    
    '// 横(列)ループ
    Do
        '// イベント発生時、または、1秒経過した場合
        If (GetInputState() Or dElapsed > 1) Then
            '// Windowsに制御を渡す
            DoEvents
                        
            '// B1セルに"a"を入力
            If (Range("B1").Value = "a") Then
                '// ループを抜ける
                Exit Do
            End If
                
            '// DoEvents用ループカウンタを初期化
            i = 0
            
            dTimer = Timer
        End If
        
        '// A1セルにループカウンタ値をセット
        Range("A1").Value = CStr(i)
        
        '// DoEvents用ループカウンタを加算
        i = i + 1
        
        '// 経過秒数
        dElapsed = Abs(Timer - dTimer)
        
        '// エラー発生時
        If Err.Number <> 0 Then
            '// イミディエイトウィンドウにエラーを出力
            Debug.Print Err.Number & " " & Err.Description
            '// エラーをクリア
            Err.Clear
        End If
    Loop
End Sub

コード説明

処理の内容はコメントの通りなので省略します。

コードの先頭でWin32APIのGetInputState()を2つ書いていますが、Win32APIは32bit版と64bit版で定義が異なるためです。

Win32APIの32bit版を64bit版にしたい場合の詳細については「VBAでWin32API(WindowsAPI)を64bit対応する方法」をご参照ください。

Windowsに制御が移ったときのエラーの対策と、DoEvents関数が遅い問題の対策について補足説明します。

エラー対策

上で「DoEvnetsはエラーが頻発する」と書いていますが、具体的には、DoEvents関数でExcel上での操作は可能になりますが、実際にセルに”a”とかの文字列を入力すると、入力途中の状態で止まりエラー1004が発生して実行していたVBAの処理自体が終了します。

このように、DoEvents関数でWindowsに制御が移ったとしても、そこでどういう操作が可能なのかはかなり制限があります。少なくとも、セルへの入力はエラーが頻発します

そこで、「On Error Resume Next」を使ってエラーが起きても処理を続行するようにしています。なお、エラーが発生した場合はイミディエイトウィンドウにエラー内容を出力しています。

なお、マクロ実行時に「コンパイルエラー 定数、固定長文字列、配列、ユーザー定義型および Declareステートメントは、オブジェクトモジュールのパブリックメンバーとしては使用できません。」とのエラーが出た場合は「Private Declare Function GetInputState Lib “user32” () As Long」の記述で先頭の「Private」が書いてあるかを確認してください。

処理遅延対策

待機中のイベントがあるかどうかの判定にWin32APIのGetInputState関数を利用して、イベントがあるときだけDoEvents関数を呼び出すようにしています。

ただ、マクロを実行後に数秒(体感では5秒程度)経過すると、それ以降では待機中のイベントの検出を行えないのかDoEvents関数が呼び出されず、Excelブックが応答しなくなってしまいます。こうなるとタスクマネージャ等でExcelを強制終了するかVBAの処理が終わるまで待つかになります。

そのため、別でTimer関数を使って経過秒数を取得し、1秒ごとにDoEvents関数を呼び出して応答なしの状態になるのを回避しています。

まとめ

上に書いたことの繰り返しになりますが、DoEvents関数を使うときはWin32APIのGetInputState関数と「On Error Resume Next」でのエラー回避はセットで実装することをお勧めします。

]]>
VBAで配列から指定文字列を含む要素を取得する(Filter) https://vbabeginner.net/filter/ Mon, 12 Nov 2018 15:45:46 +0000 https://vbabeginner.net/?p=3668 Filter関数

Filter関数は配列から指定した文字列を含む要素を取得します。指定した文字列があった場合は、それらを配列として返却します。指定した文字列がなかった場合は、空の配列を返却します。Filter関数は要素は返しますが、配列の位置は返しません。

指定文字列がある配列の要素の位置を調べたい場合は「VBAで配列に指定文字列が存在する位置を調べる」をご参照ください。

構文

Function Filter(SourceArray, Match As String, [Include As Boolean = True], [Compare As VbCompareMethod = vbBinaryCompare]) As Variant

SourceArray 検索対象の配列を指定します。

1次元配列に限ります。

1次元配列でない場合はエラーになります。

Match 検索する文字列を指定します。
Include
省略可
検索文字列を含む配列の要素を返す場合はTrue、含まない配列の要素を返す場合はFalseを返します。

省略時はTrueになります。

Compare
省略可
VbCompareMethod列挙型で比較モードを指定します。

省略時はOption Compareステートメントでの設定内容が採用されます。

Option Compareステートメントが未設定の場合はバイナリモード(vbBinaryCompare)が指定されます。

VbCompareMethod列挙型

定数 内容
vbBinaryCompare 0 バイナリモード(大文字と小文字、全角と半角、ひらがなとカタカナを区別する)
vbTextCompare 1 テキストモード(大文字と小文字、全角と半角、ひらがなとカタカナを区別しない)

省略時はバイナリモードで検索されます。

バイナリモードとテキストモードの違いは、大文字と小文字、全角と半角、ひらがなとカタカナを区別するかしないかです。

バイナリモードの場合は区別しますが、テキストモードの場合は区別しません。

戻り値 検索結果を配列で返します。

検索で一致するデータがない場合は空の配列を返します。

 

サンプルコード

要素数が6の1次元配列から指定文字があるデータをFilter関数で抽出するサンプルコードです。

Sub FilterTest()
    Dim ar(5)
    Dim v
    
    ar(0) = "abc"
    ar(1) = "A"
    ar(2) = "  a  "
    ar(3) = "  b  "
    ar(4) = "  B  "
    ar(5) = "  B  "
    
    v = Filter(ar, "B", True, vbTextCompare)
    
    Dim s
    For Each s In v
        Debug.Print "[" & s & "]"
    Next
End Sub

実行結果
[abc]
[  b  ]
[  B  ]
[  B  ]

Filter関数の用途

Filter関数の用途ですが、必要なデータが配列の中にいくつあるのか、ということを調べる場合には便利に利用できます。

Filter関数の戻り値は、検索一致データがあってもなくても必ず配列を返します。

データがない場合も空の配列を返します。

これを利用すると、戻り値の配列の要素数をUBound関数で取得した値に+1したものが検索に一致したデータ数になります。

]]>