えくせるちゅんちゅん

ことりがエクセルをちゅんちゅんするブログ

VBAのデバッグにおける真のイミディエイトウィンドウの使い方

今回はVBAデバッグにおける真のイミディエイトウィンドウの使い方を紹介します。

VBAの開発画面(VBE)には様々なウィンドウがありますが、その中でも特に奥が深いのがイミディエイトウィンドウです。

イミディエイトウィンドウをどれだけ使いこなしているかで、その人のVBAプログラミングスキルがどれ程のものかひと目で分かる(かもしれません)

今回はそんなイミディエイトウィンドウだけに焦点を絞って、徹底的に紹介していきます。

好評につきワンライナー使用の様子を前半だけ動画にしてみました。

f:id:Kotori-ChunChun:20190211203421g:plain


元ネタ

元ネタは2018/11/30にTwitterにて投稿したツイートです。

こんなものでも(当時の私にしては)大盛況でしたが、140文字では書ききれなかった事が沢山あって、ずっと心の中でもやもやしていました。そんな思いを込めて執筆に取り組みました。

残念ながら当時は誰からも追加情報をいただけませんでしたが・・・(泣いてません。)

記事を書くに当たって、色々と勘違いも見つかったのでしっかり直しました。もう大丈夫です。

また、今回の記事を書くにあたって、ネタ提供にご協力頂きました方々には心からの感謝を。


VBEのイミディエイトウィンドウの使い方

基本操作テクニック

これだけ抑えておけばOKです。(多いとか言わないの!)

他のどの資料を探っても解説していないような、超マイナー操作もあると自負しております。

  1. Ctrl+Gでイミディエイトウィンドウが開ける。
  2. F7でコードウィンドウに戻れる。
  3. Debug.PrintDebug.?で短縮可)の結果がイミディエイトウィンドウに出力される。
  4. イミディエイトウィンドウにおいてはDebug.PrintPrint?で代用できる。
  5. Ctrl+Spaceでインテリセンス(入力補完)が使える。
  6. (VBE共通)Ctrl+Endで末尾に、Ctrl+Homeで先頭に飛べる。
  7. Enterを押すと選択中の行が実行されて、次の行に結果が出力される。
  8. カレントのプロジェクト/モジュールに対して実行される。(VBEタイトルバー参照)
  9. デバッグ中は現在のプロシージャに対して実行されるので、ローカル変数の書き換えができる。
  10. デバッグ中でもモジュールレベル以上の広域変数は書き換えられる。
  11. Ctrl+Enter現在の行を実行せずに改行を挿入できる。
  12. (VBE共通)Ctrl+N現在の行の先頭に改行を挿入できる。
  13. (VBE共通)Ctrl+Y現在の行を削除できる。
  14. セミコロンは実行後に改行が入らない。また、連続して書ける。 ?"a";"b";abと出力される。
  15. 数値は前後に半角スペースが1つ入る ?1;2;3_1__2__3_と出力される。
  16. コンマ14文字毎になるようにタブが入る?また、連続して書ける。?"a","b"a_____________bと出力される。
  17. つまり面倒な "a" & "b"(スペース+アンド+スペース)の入力は不要。
  18. コロンを使えばFor~Nextのようなマルチステートメントの実行が出来る。
  19. ログに残るのは200行までセミコロンで終わらせた場合を除いて通常は199行しか残らない。
  20. Debug.Printは結構重い。ループの中に記載すると凄まじい重さになるので、本番ではコメントアウトするか条件付きコンパイルで無効化すること。
  21. イミディエイトウィンドウがポップアップ表示のときだけ Alt+Enterでコードウィンドウに戻れる。(厳密には直前のウィンドウに戻る)

上記のうち、分かりづらい数値と文字列の違いとコンマの14文字毎のタブですが、具体的にはこんな感じで数値は11桁まで、文字列は13桁までが1タブに収まる限界です。

※環境によって14桁にならない時がある気がするので良くわかっていません。

?12345678901,2
 12345678901   2 
?123456789012,2
 123456789012                2 

?"1234567890123","1234567890123"
1234567890123 1234567890123
?"12345678901234","1234567890123"
12345678901234              1234567890123


バッドノウハウ

  1. Ctrl+Zを押してもイミディエイトウィンドウは戻せない。

    • コードの方が戻って大変なことになります。
    • でも、直前に書いていたコードに飛べるので便利なことも結構あったり・・・。
  2. 未定義の変数でもエラーにならない。

    • イミディエイトウィンドウはOption Explicit されません。
    • 気をつけないとタイプミスでハマります。
    • 一方でForやForEachのカウンタが無宣言で使えるので便利なことも・・・。
  3. VBAからイミディエイトウィンドウを消すコードが提供されていない。

    • 手動操作(Ctrl+ADeleteCtrl+XCtrl+BackSpace)くらいしか方法が無い。
    • ただし強引に消す方法は存在する。サンプルはTIPSに記載した。
  4. 切り取りコピー貼り付けが使えなくなる時がある。

    • コマンドがグレーアウトしており、Ctrl+XCtrl+CCtrl+Vも使えない。
    • 原因はカレントのプロジェクトがパスワードでロックされているから。
    • しかし直接入力と実行は出来るので、パスワードでロックされたプロジェクトに対してもコードの実行ができてしまう。(悪いことには使わないように!)
  5. 数値を右寄せで整列させたくても、適切な関数が存在しない。

    • サンプルの汎用関数をTIPSに記載した。


参考資料

ちなみに、あまり役に立たないMicrosoftのイミディエイトウィンドウの説明はこちらです。


プログラムを中断する方法一覧

イミディエイトウィンドウ活用のためにも、プログラムの中断方法を記載しておきます。

  1. ブレークポイントの設定
  2. Stop
  3. Debug.Assert
  4. 実行時エラー
  5. ウォッチウィンドウに設定した条件一致
  6. ESC、Ctrl+Pause/Breakキー

マクロの中断については、こちらも是非ご覧ください。

www.excel-chunchun.com


VBAのイミディエイトウィンドウの活用事例集

以上の仕組みを丸暗記したとして、実際に使いこなせるかどうかは別の話。

ここで実際に使っている事例や、ワンライナーを紹介します。

プロシージャ(Sub)を実行する

Proc "a"    'Procプロシージャが実行される

プロシージャ(Function)を実行する

こうすると自作関数のデバッグが容易に出来ますね。

?FuncSum(10,3)
 13 

簡易的なデバッグは、ここで値を変えながら繰り返し実行してテストをします。

ある程度、大規模なテストが必要な場合は、下記記事の方法などを推奨します。

www.excel-chunchun.com

文字コードが知りたい

ちょっと文字コードが思い出せない時。ありますよね?

?asc("A")
 65 
?asc("""")
 34 

IF文を使いたい

シングルステップで書ける記法なら問題ありません。

この時、End Ifは不要になるのでご注意を。

If a=1 Then ?1 Else ?2
 2 

For文を使いたい

通常、For文は最低でも3行必要なのでイミディエイトウィンドウで使えないように思えますが、コロンを使ってマルチステートメントな記法にすればできます。

For i = 1 To 5 : ?i : Next
 1 
 2 
 3 
 4 
 5 

上記のままだと、すごい勢いでログが流れてしまうので、セミコロンを付けると良いです。

For i = 1 To 5 : ?i; : Next
 1  2  3  4  5 

でもカーソルが右の遥か彼方に飛ぶので、Ctrl+Enterを使って帰ってきてから終わりましょう。(そうしないと、次の出力が同じ行の続きからになります)

For文の中でIfを使いたい

このような作業はあまりありませんが、知らないと苦労するので書いておきます。

実際には実行出来ないダメダメな例

For i = 1 To 5 : If i Mod 2 Then : ?1 Else ?2 : End If : Next
'行頭のみ許されるステートメントです。
For i = 1 To 5 : If i Mod 2 Then ?1 Else ?2 : Next
'Next に対応する For がありません。

IIF関数を使えば対応できます。

For i = 1 To 5 : ?iif(i Mod 2, 1, 2); : Next
 1  2  1  2  1

デバッグ中にループカウンタを少し戻したい

For文のデバッグ中にもう一度同じ行をやり直したい時とか、一つ前に戻りたい時とかに。

i=i-1

Trueが整数の何に当たるか知りたい

私、たまに混乱するんですよね。(いい加減に覚えてもいいんじゃないか?と思いますが。)

?true
True
?0+true
-1

覚えられない原因はExcelの数式とVBAで仕様が違うからだったりします。

表に整理するとこんな感じ。

- FALSE TRUE ことりちゅん式の思い出し方
EXCEL 0 1(0以外) TRUEをSUMしてカウントするテクニックで使うから
VBA 0 -1(0以外) 0のビットを反転させると-1だから

VBAの式がどのように評価されるのか知りたい

稀にしか使わない演算子だと、どれだったか思い出せないんですよね。

複数言語をやっている人だと演算子の違いで苦労しそうですし、重宝すると思います。

?10 / 3
 3.33333333333333
?10 \ 3
 3 
?10 mod 3
 1 
?10 and 3
 2 
?10^3
 1000 
?"1"=1
True

いますぐApplication配下のスイッチを変えたい

高速化等のために切ることがありますが、逆に遅くなっている原因やエラーを発見しづらくなってしまった時とか・・・。

ちょっとした作業中に機能をON/OFFしたい時とか・・・。

Application.EnableEvents = True
Application.ScreenUpdating = True
Application.DisplayAlerts = True
Application.Calculation = xlCalculationAutomatic

マニュアルなんかを作成している時は、作業スペースを確保するために不要なツールバーを消したいときがあります。

そんな時はこの辺のコマンドを叩いてウィンドウをスッキリさせます。

Application.DisplayStatusBar = False
Application.DisplayFormulaBar = False
Application.DisplayFullScreen = False   'Alt+V+Uでも出来る

全てのシートを表示状態にしたい

Excelってシートの非表示は一括でできるのに、再表示はできないんですよね。

シート数が多いとかなり面倒なことになります。

For Each ws in worksheets : ws.visible=-1 : next

※ちなみに非表示の不要なシートの一括削除ならドキュメントの検査から出来ます。

全てのシートを保護したい/保護解除したい

Twitterにて情報をいただきました。

言われてみれば確かに一括操作出来ないので、配布前とかに便利です。

For Each ws In Worksheets: ws.Protect: Next
For Each ws In Worksheets: ws.Unprotect: Next

全てのシートの選択セルをA1に戻したい

これも配布前に是非使いたいやつ。

For Each ws In Worksheets: ws.Select: ws.Cells(1, 1).Select: Next

全てのシートのズーム倍率を統一したい

これまた配布前に是非使いたいやつ。

For Each ws In Worksheets: ws.Select: ActiveWindow.Zoom=80: Next

配布用前に実行しておきたいコマンドは結構多いので、まとめて個人用マクロやアドインに組み込んだほうが良いかもしれません。

全てのシートにウィンドウ枠の固定を適用したい

Accessからエクスポートしたテーブルをエクセルで使う時に真っ先に実行するやつ。

For Each ws In Worksheets: ws.Select: ws.Cells(2, 2).Select: ActiveWindow.FreezePanes=True: Next

ウィンドウが非表示のブックを閉じたい

※閉じたいプロジェクトを選択してから実行

アドインとかPERSONAL.XLSBが邪魔な時に。

ThisWorkbook.Close

開いてるエクセルブックのパスが知りたい

?ThisWorkbook.Path
C:\~~~~.xlsm

アドインのシートを表示したい

アドイン固有の設定値をシートに保存させている場合に。(悪用厳禁?)

ThisWorkbook.IsAddin = False

直前のエラーのメッセージをコピペしたい

誰かに質問する時は必ずコードと一緒にエラーメッセージも送りましょう。

?Err.Description

選択セルのデータ型が知りたい

謎の動きをした時はとりあえず型をチェック!

?typename(Selection.Value)
String
?typename(Selection)
Range

配列の中身を全部出力したい

一次元配列ならJoinを使えば一発です。

?Join(arr, vbTab)
a   b   c

二次元配列だとForしかなさそう。

?for i=1 To ubound(arr):?arr(i,1);:next

表の値をArrayのリテラルに使いたい

For Each r In Selection : ?"""" & r & ""","; :Next
"あああ","いいい","ううう","えええ",

これをコピペして、Arr = Array("あああ","いいい","ううう","えええ")で完成っと!

広範囲のセルを選択したい

例えば、現在の選択しているセルを基準に100x100の範囲を選択したい時。

Selection.Resize(100,100).Select

セルを一行おきに選択したい

このコマンドを実行した後、削除、挿入、着色なんでも出来ます。

For i = 1 To 100 Step 2: Union(Selection, Rows(i)).Select: Next

もちろん列もOK

For i = 1 To 100 Step 2: Union(Selection, Columns(i)).Select: Next

選択範囲のセルを絞り込みたい

選択範囲を絞り込む時Intersect()を使うと便利です。

Intersect(Selection, Columns("A:H")).Select
Intersect(Selection, ActiveSheet.UsedRange).Select

選択範囲にアドレスを埋め込みたい

for each r in Selection:r.value=r.address(0,0):next

選択範囲のセルを結合する数式(CONCATENATE関数)を作りたい

Office365やExcel 2019以降にはCONCATやTEXTJOINがありますが、無いものは仕方ありません。

=CONCAT(A1:I1)と同等のものと作りたいとしたら、A1:I1を選択して下記を実行

for each r in Selection:?r.address(0,0);",";:next
A1,B1,C1,D1,E1,F1,G1,H1,I1,

結果をコピーして=CONCATENATE(A1,B1,C1,D1,E1,F1,G1,H1,I1)として完成!

=TEXTJOIN("-",False,A1:I1)と同等のものと作りたいとしたら、A1:I1を選択して下記を実行

for each r in Selection:?r.address(0,0);",""" & "-""";",";:next
A1,"-",B1,"-",C1,"-",D1,"-",E1,"-",F1,"-",G1,"-",H1,"-",I1,"-",

=OCONCATENATE(A1,"-",B1,"-",C1,"-",D1,"-",E1,"-",F1,"-",G1,"-",H1,"-",I1)として完成!

入力規則の入ったセルのアドレスを列挙したい

for each r in cells.SpecialCells(xlCellTypeAllValidation):?r.address:next

全てのシートから入力規則を削除したい

リンクの解除でブックがクラッシュする場合は、結構な確率でデータの入力規則のリストが外部ファイルを参照している。

for each ws in Worksheets:ws.cells.validation.delete:next

EXCEL方眼紙の文字列を結合したい

Office365 / Excel2019はCONCAT関数で結合できますが、以前のバージョンでは一苦労です。

これならシートを汚さず素早く結合できますね。

for each r in Selection:?r;:next

結合セルを分解して同じ値で埋めたい

間違ったエクセルの使い方をする人から送られてきたデータの加工に!

for each r in Selection:r.unmerge:Selection=r.value:next

ダブルクォーテーションを含む文字列の生成式が難しいので練習したい

?"""C:\""" & "hoge\" & "file.jpg"""
"C:\"hoge\file.jpg"

例えばShell関数で「出力したファイルを選択した状態でエクスプローラを開きたい」ときとかですね。

?"explorer.exe /select,""" & "path" & """
explorer.exe /select,"path"

テーブルを管理するクラスのヘッダの宣言文を作りたい

これは教えていただいて、初めて気がついたテクニックです。

ID 名前 住所 時間 ←ヘッダ部を選択して
0001 hoge ほげほげ 102030
0002 fuga ふがふが 112233
0003 piyo ぴよぴよ 443322
for each r in selection:?"Public ";r;" As String":next
Public ID As String
Public 名前 As String
Public 住所 As String
Public 時間 As String

今までEXCEL関数で作ってましたが、これのほうが超かっこいいですね!

同様にTypeや列番号を定義する定数Enum、Constにも応用が効きそうです。



その他のTIPS

出力を右寄せにしたい

様々な利用方法を説明しました。

しかし一つだけどうしても解決したい問題があります。

それが数値が左寄せで出力されてしまう。ということです。

数値を確認するときは右寄せにしたいですよね。

いっその事ワークシート上に出力してしまえば済む話なのですが、イミディエイトウィンドウに出したいという時のために関数を紹介します。

適当に書いたものなので、使う時は都合の良いように直して下さい。

'14桁毎になるように右寄せにする
'※14桁以上のデータは上位の桁が消える
Function dpr(ParamArray vals() As Variant) As String
    Dim v As Variant
    For Each v In vals
        dpr = dpr & Right(String(13, " ") & CStr(v), 14)
    Next
End Function

id:Z1000S 様よりコメント欄にてFormat関数を使う方法も提案して頂きました。

こちらのほうが可読性は良くなりそうです。

sFunction dpr(ParamArray vals() As Variant) As String
    Dim v As Variant
    For Each v In vals
        dpr = dpr & Format(v, String(14, "@"))
    Next
End Function

id:umeyoshioka 様よりコメント欄にて固定長文字列を使ったサンプルを頂きました。

素晴らしいアイディアをありがとうございます。

'14桁毎になるように右寄せにする
'※14桁以上のデータは上位の桁が消える
Function dpr(ParamArray vals() As Variant) As String
    Dim v As Variant, str14 As String * 14
    For Each v In vals
        RSet str14 = CStr(v)
        dpr = dpr & str14
    Next
End Function

テスト結果

?"1234567890123","12345"
1234567890123 12345
?"1234567890123","123"
1234567890123 123
?dpr("1234567890123","12345")
 1234567890123         12345
?dpr("1234567890123","123")
 1234567890123           123

イミディエイトウィンドウが行方不明になった

イミディエイトウィンドウが行方不明になるというトラブルが稀にあります。

多くはデュアルディスプレイ環境で、ウィンドウが増減したことに伴うトラブルだと思いますが、対処法は2つみつかりました。

まずは、Windows共通の基本奥義。アプリケーションメニューからの移動十字キーです。

Ctrl+G → Alt+Space → 移動(M) → 十字キー → マウスに極小のウィンドウがついてくる

もう一つはVBE特有のオプションの変更です。

ツール → オプション → ドッキングタブ → チェックをOFF → コードウィンドウに出現する → 同様に操作してON

VBAからイミディエイトウィンドウの中身をリセットしたい

たまにデバッグのたびにイミディエイトウィンドウをリセットしたいときがあります。

ところが標準ではイミディエイトウィンドウを操作する方法が存在しません。

大抵は毎回Ctrl+A → Deleteで消すんですが、テスト回数があまりにも多い時は自動でリセットしたい!

そんなことを考えるのは私だけではないようで、ネットで調べたところ3種類の方法が見つかりました。

  1. Debug.Printで流し切る
  2. SendKeyで消す
  3. APIで消す

1. ログが流れきるまで出力する

イミディエイトウィンドウは200行しか保持されないという性質を利用して、199回Debug.Printを実行して流してしまえ。というアイディアです。

基本命令のみなのでシンプルなのが最大のメリットですね。

また後に紹介する方法のような、エラーの可能性もありません。

しかしエラーにはならないものの、Debug.Printは結構負荷がかかるため、使う場所には注意したほうが良いでしょう。

できれば条件付きコンパイルで制御し、本番では出力されないようにしておくと良いと思います。

またDebug.Printはカーソルの位置から出力するという性質であることから、カーソルの位置が末尾にないと完全には消えないので注意が必要です。

ちなみに末尾に飛ぶにはCtrl+ENDキーがおすすめです。

'空欄でログを流す
'カーソルが末尾にないと意味がない
'イミディエイトウィンドウは200行しか表示できないので199出力した時点で全滅する
Sub DebugPrint_Flush()
    Dim i As Long
    For i = 1 To 199
        Debug.Print
    Next
End Sub

2. SendKeyで消す

ちょっと有名なのがApplication.SendKeyで操作を再現して消す方法

VBEを開いてない時は働かないように判定しているため、「Excelのオプション」→「マクロの設定」→「VBAプロジェクトオブジェクトモデルへのアクセスを信頼する。」を有効にしないと使えないのが難点です。

上記設定を変えること無く、判定する方法がないか捜索中です。

'イミディエイトウィンドウを全て削除する
Sub DebugPrint_Clear()
    On Error GoTo ENDPOINT
    If Application.VBE.MainWindow.Visible And _
        Application.VBE.Windows("イミディエイト").Visible Then
            SendKeys "^{g}", False
            DoEvents
            SendKeys "^{Home}", False
            SendKeys "^+{End}", False
            SendKeys "{Del}", False
            SendKeys "{F7}", False
    End If
ENDPOINT:
End Sub

しかし、イミディエイトウィンドウからプロシージャを呼び出して、結果を確認するというデバッグをする場合は全て消されては困る。

そこで新たに考えたのがこちら。

'イミディエイトウィンドウの先頭行を除いてすべて削除する
'フォーカスをイミディエイトウィンドウに残す
Sub DebugPrint_ClearAfter2ndLine()
    If Application.VBE.MainWindow.Visible And _
        Application.VBE.Windows("イミディエイト").Visible Then
            SendKeys "^{g}", False
            DoEvents
            SendKeys "^{Home}", False
            SendKeys "{Down}", False
            SendKeys "^+{End}", False
            SendKeys "{Del}", False
    End If
End Sub

3. APIで消す

SendKeyなんて方法は使いたくない。もっと良い方法はないのか。と調べていたら、StackOverflow に良さげなのがありました。

https://stackoverflow.com/questions/10203349/use-vba-to-clear-immediate-window

ただし、結局はPostMessageしており、SendKeyの問題も解決出来ておらず、色々と問題があったので放置です。

重要なヒントが隠れていそうなので、記録として残しておきます。

VBAからイミディエイトウィンドウのカーソルを末端に戻したい

カーソルの位置を末尾に移動するにはCtrl+Endです。

プロシージャでDebug.Printを始める前に次のプロシージャを呼ぶようにしておくと、常に最終行に出力されるので幸せになれます。

'イミディエイトウィンドウの末尾にフォーカスを移動する
Sub DebugPrint_CursolMoveToLast()
    If Application.VBE.MainWindow.Visible And _
        Application.VBE.Windows("イミディエイト").Visible Then
            SendKeys "^{g}", False
            DoEvents
            SendKeys "^+{End}", False
            SendKeys "{F7}", False
            DoEvents
    End If
End Sub

イミディエイトウィンドウの内容をファイル出力したい

イミディエイトウィンドウの文字列を取得する方法が見つけられませんでした。

現実的な方法は2つあります。

  1. Debug.Print の代わりのプロシージャを使うようにする。
  2. イミディエイトウィンドウの内容をコピーで読み取ってファイルに出力する。※199行制限、開発画面の表示が必須

いずれにせよ本番運用向けであれば、エラー出力用の専用クラスを作って出力しましょう。


まとめ

「イミディエイトウィンドウ」というネタから、随分と壮大な記事になってしまいました。

イミディエイトも、なかなか、どうして、侮れません。

たくさんの事例を上げましたが、私も全部は覚えきれないので、時々見に来て少しづつ覚えるとしましょう。

頻繁に使うようなコードは、いっその事IMEに登録しちゃうと便利ですよ!下記の記事をご覧ください。

www.excel-chunchun.com

ここに載せていない便利なコマンドがあれば、是非コメント欄へ書き込みをよろしくおねがいします。

以上


何か御座いましたらコメント欄、またはTwitterからどうぞ♪

週1回の更新を目指して、頑張ってますので応援よろしくおねがいします!

幸せなVBAライフを♪ ちゅんちゅん(・8・)

プライバシーポリシー