2022/12/25

自動的に閉じるメッセージ




MsgBox関数は、情報の伝達とユーザーの意図を確認するのには欠かせないツールです。しかしダイアログを閉じる為にはマウスクリック(又はTabやEnterキーを押下)が必要で、何回も出現する時には結構な手間をユーザーに強いる事になります。
ユーザーにはメッセージを良く読んでもらい、その上で「OKボタン」をクリックしてもらいたいので、その手順は良いと思います。しかし例えば、処理が長く続いた後の「終了しました」みたいなメッセージの場合は、いちいちユーザーにマウス操作をしてもらうのはどうかと思います。
そこで今回は、表示後ある程度時間が経過したら「自動的に閉じてくれるメッセージ」を紹介します。

1.概要

紹介するのは、全7種の「自動的に閉じるメッセージ」です。表示される形としては図1のようになります。
自動的に閉じるメッセージ一覧
図1


サンプルファイル」では、図1のようにシート上に8つのボタンを配置しています。表示されるダイアログの位置は、説明用のために適当に配置し直しています。実際には、おおよそ「画面中央」辺りになります。

1番目「MsgBox」は「通常のMsgBox」です。OKボタンをクリックするまで閉じませんが、比較のために配置しています。
2番目「API」は、Windows APIの「MessageBoxTimeoutA」関数を使用したものです。他サイトでも良く「自動的に閉じるメッセージ」として紹介されているものです。
3番目「WSH」は、WshShellのPopupメソッドを利用したもので、これも他サイトで紹介されているものと同じです。

4~5番目の「UserForm」はフォームを起動させ、タイマーで自動的に閉じさせるものです。見かけは4番目も5番目も同じですが、4番目は「モードレス」でフォームを起動させているため、メッセージ中でもワークシート上の操作が可能です。
一方5番目は「モーダル」ですので、フォームが消えるまで他の操作は出来なくなります(フォーム右上×印をクリックすれば、時間が来なくてもフォームを終了させる事が可能です)。

6番目「図形」は、ワークシート上に「図形のテキスト」を表示するものです。今回は、背景色+外枠+影 という最低限の図形にしましたが、自由度は高いので「凝ったメッセージ」を作ることが可能と思います。

7~8番目「ステータスバー」は、Excel下部のステータスバー左端にメッセージを表示させるものです。但しマクロ等で「ステータスバーを非表示」にしている場合には、当然ながら表示できません(非表示のステータスバーにメッセージを出しても、特にエラーは発生しません)。ですので、今回は「一旦ステータスバーを表示」状態にしてからメッセージを出し、終了したら元の状態に戻すようにしています。
また7番目の仕様は、単純にステータスバーに一定時間だけメッセージを表示させるだけです。しかし、これだと「ユーザーが気が付かない」可能性が高いので、8番目の仕様は「メッセージを点滅」させてユーザーの注意を引くようにしてみました。

2.実行ボタンの準備(Sheet1)

サンプルファイル」では、図2のようにシート上にボタンを並べ、クリック時に実行されるマクロとして、標準モジュールのSUBプロシージャ「Button1」「Button2」・・・を上から順に登録します。
なお、各ボタンの表面Captionは、配置時に手動で書き換えています。
実行ボタンの配置
図2


以下でメッセージ表示のコードを紹介しますが、標準モジュール宣言部でDeclare文を記述する都合上、コードの番号が前後してしまいました。御了承下さい。
また「自動的に閉じるメッセージ」は、今回は「2秒」で閉じる仕様としています。用途により閉じる時間は調整可能ですが、小数点以下の秒数の設定が可能なものと、不可能なものがありますので御注意下さい。

3.MsgBox関数

1番目のボタンをクリックした時に呼び出されるのが、図3です。
  1. '========== ⇩(1) ボタン1による呼び出し(MsgBox) ============
  2. Sub Button1()
  3.  MsgBox "おはよう こんにちは"
  4. End Sub
図3


22行目「MsgBox "おはよう こんにちは"」で、メッセージを表示しますが、通常のMsgBoxですので「OKボタンをクリック」するまでは「他の処理が停止」します。MsgBox関数で表示されるメッセージは図4のようになります。
MsgBoxの実行結果
図4


4.Windows APIのMessageBoxTimeoutA関数

2番目のボタンをクリックした時に呼び出されるのは一つ下の図6ですが、その前に標準モジュールの宣言部(先頭部)で「MessageBoxTimeoutA関数を使用するための外部プロシージャへの参照」を宣言します(図5)。
  1. '========== ⇩(2) Declare文 ============
  2. #If Win64 Then
  3.  Private Declare PtrSafe Function MessageBoxTimeoutA Lib "User32" ( _
  4.   ByVal Hwnd As LongPtr, ByVal lpText As String, ByVal lpCaption As String, _
  5.   ByVal uType As VbMsgBoxStyle, ByVal wLanguageID As LongPtr, ByVal dwMilliseconds As LongPtr) As LongPtr
  6. #Else
  7.  Private Declare Function MessageBoxTimeoutA Lib "User32" ( _
  8.   ByVal Hwnd As Long, ByVal lpText As String, ByVal lpCaption As String, _
  9.   ByVal uType As VbMsgBoxStyle, ByVal wLanguageID As Long, ByVal dwMilliseconds As Long) As Long
  10. #End If
図5


図5は今回使用する「MessageBoxTimeoutA」関数を宣言するものですが、Excelが「32ビット」か「64ビット」かでDeclare文が異なります。32ビットの場合は図5の06~08行目の式となりますし、64ビットの場合は02~04行目の式となります。

違いは、64ビットはFunctionの前に「PtrSafe」を付けることと、Long型は「LongPtr」型とすることです。32ビットと64ビットを分岐させるために01行目「#If Win64 Then」でOfficeのビット数を確認し分岐しています。

なお64ビット版ExcelのVBE上では、06~08行目の式が「エラーのような表示」となりますが、実行されるのは02~04行目なので特に問題は無いようです。

2番目のボタンをクリックした時に呼び出されるのが図6のButton2プロシージャです。
  1. '========== ⇩(3) ボタン2による呼び出し(API) ============
  2. Sub Button2()
  3.  Call myMsgBox2("おはよう こんにちは", 2)
  4. End Sub
  5. '========== ⇩(4) MessageBoxTimeoutA ============
  6. Public Sub myMsgBox2(Word As String, T As Long)
  7.  MessageBoxTimeoutA Application.Hwnd, Word, "", vbOKOnly, 0, T * 1000
  8. End Sub
図6


32行目「Call myMsgBox2("おはよう こんにちは", 2)」では、36行目のmyMsgBox2プロシージャを呼び出します。引数として、下記の2つの値を渡します。
 第一引数:表示するメッセージ
 第二引数:自動的に閉じるまでの時間(秒)
なお閉じるまでの時間は「ミリ秒」での設定が可能です。

呼び出されたプロシージャでは、37行目「MessageBoxTimeoutA Application.Hwnd, Word, "", vbOKOnly, 0, T * 1000」を実行します。MessageBoxTimeoutA関数には、図7のように6つのパラメータを設定します。全て必須です。

MessageBoxTimeoutA関数のパラメータ
No.引数内容
1Hwndメッセージボックスのハンドル
2lpTextメッセージボックスに表示されるテキスト
3lpCaptionメッセージボックスに表示されるタイトル
4uTypeメッセージ ボックスの内容と動作を指定
5wLanguageIDメッセージボックスに表示される言語
6dwMillisecondsユーザーの応答を待機する時間 (ミリ秒) 
図7


第一引数「Hwnd」には、Excelのハンドルを指定するため「Application.Hwnd」または「Excel.Application.Hwnd」とします。この第一引数にNull相当の「ゼロ」の値を指定すると「オーナーウィンドウを持たない」状態になり、「メッセージボックスがExcelとは無関係」に動ける事になります。
実際に試してみると、ダイアログ表示中にExcel本体を操作すると「Excel本体が最前面」になり、逆にメッセージのダイアログが「Excelの裏」に隠れて見えなくなってしまいます。

第二引数「lpText」は、表示されるメッセージを指定します。
第三引数「lpCaption」は、ダイアログのタイトルの文字列を指定します。
第四引数「uType」は、表示するボタンの種類などを図8の値を組み合わせて指定します。
uTypeの値
グループ定数内容
ボタン
種類
vbOKOnly0[OK]のみ(既定)
vbOKCancel1[OK]+[キャンセル]
vbAbortRetryIgnore2[中止]+[再試行]+[無視]
vbYesNoCancel3[はい]+[いいえ]+[キャンセル]
vbYesNo4[はい]+[いいえ]
vbRetryCancel5[再試行]+[キャンセル]
アイコンvbCritical16 警告マーク
vbQuestion32 問い合わせマーク
vbExclamation48 注意マーク
vbInformation64 情報マーク
標準
ボタン
vbDefaultButton10第1ボタンを標準ボタンに(既定)
vbDefaultButton2256第2ボタンを標準ボタンに
vbDefaultButton3512第3ボタンを標準ボタンに
vbDefaultButton4768第4ボタンを標準ボタンに
モーダルvbApplicationModal0アプリケーションモーダルに設定(既定)
vbSystemModal4096システムモーダルに設定
vbMsgBoxHelpButton16384ヘルプボタンを追加
VbMsgBoxSetForeground65536最前面のウィンドウとして表示
vbMsgBoxRight524288テキストを右寄せで表示
vbMsgBoxRtlReading1048576テキストを右から左の方向で表示
図8


第五引数「wLanguageID」は「ゼロ」を指定すると、既定のシステム言語でボタン・テキストが表示されることになります。
第六引数「dwMilliseconds」には、メッセージボックスを開いている時間をミリ秒単位で指定します。

今回は「MessageBoxTimeoutA Application.Hwnd, Word, "", vbOKOnly, 0, T * 1000」と設定しましたので、メッセージには引数で得たメッセージ文字列を、タイトルは空欄に、ボタン類はOKボタンのみを表示しています。
また開いている時間は、引数で「2」の値を受け取っていますので、2 × 1000 = 2000ミリ秒 = 2秒 となります。
なお「ゼロ秒」を指定すると、自動的には閉じなくなります。

MessageBoxTimeoutAで表示されるメッセージは図9のようになります。
MessageBoxTimeoutAの実行結果
図9


5.WSHのPopupメソッド

Windows Script Host(WSH)は、Windowsに搭載されている「スクリプト実行」機能です。その中のWshShellオブジェクトのPopupメソッドを使うことでメッセージが表示できます。

3番目のボタンをクリックした時に呼び出されるのが、図10のButton3プロシージャです。
  1. '========== ⇩(5) ボタン3による呼び出し(WSH) ============
  2. Sub Button3()
  3.  Call myMsgBox3("おはよう こんにちは", 2)
  4. End Sub
  5. '========== ⇩(6) WSH ============
  6. Public Sub myMsgBox3(Word As String, T As Long)
  7.  Dim WSH As Object '←WshShellオブジェクト
  8.  Set WSH = CreateObject("Wscript.Shell")
  9.  WSH.Popup Word, T
  10.  Set WSH = Nothing
  11. End Sub
図10


52行目「Call myMsgBox3("おはよう こんにちは", 2)」では、56行目のmyMsgBox3プロシージャを呼び出します。引数として、メッセージ文字列と閉じるまでの時間(秒)を渡します。

なお閉じるまでの時間は、Popupメソッドでは「秒単位」設定ですので、56行目では「T As Long」とLong型で受け取るようにしています。
そのため小数点付きの値を受け取った時には、基本的には四捨五入になります。但しLong型変換時(CLng関数も同じ)には、小数部分がちょうど0.5のときには「最も近い偶数」に値を丸める特性があります。例えば「0.5」を渡した場合は「1」にはならず「ゼロ」になりますので注意が必要です。

59行目「Set WSH = CreateObject("Wscript.Shell")」では、WSHのWshShellオブジェクトを生成しています。
60行目「WSH.Popup Word, T」では、WshShellオブジェクトのPopupメソッドを使って、メッセージを表示します。Popupメソッドには図11のように4つのパラメータがあります。第一パラメータ以外は省略可です。

Popupメソッドのパラメータ
No.引数内容
1strText(必須)メッセージボックスに表示する文字列
2nSecondsToWaitメッセージボックスを閉じるまでの時間(秒単位)
3strTitleメッセージボックス上部のタイトル名
4nTypeボダンやアイコンの設定
図11


第一引数「strText」は、メッセージボックスに表示させるメッセージ文字列です。
第二引数「nSecondsToWait」は、メッセージを閉じるまでの時間を「秒単位」で設定します。この値に「ゼロ」値を設定すると、自動的には閉じなくなります。
なお、このWSHのPopupメソッドは不安定なようで、設定した秒数よりも長く表示されますし、自動的に閉じてくれない場合もあるようです。

第三引数「strTitle」は、ダイアログ上部のタイトル名です。省略した場合には「Windows Script Host」となります。
第四引数「nType」は、表示するボタンの種類などを図12の値を組み合わせて指定します。
uTypeの値
グループ定数内容
ボタン
種類
vbOKOnly0[OK]のみ(既定)
vbOKCancel1[OK]+[キャンセル]
vbAbortRetryIgnore2[中止]+[再試行]+[無視]
vbYesNoCancel3[はい]+[いいえ]+[キャンセル]
vbYesNo4[はい]+[いいえ]
vbRetryCancel5[再試行]+[キャンセル]
アイコンvbCritical16 警告マーク
vbQuestion32 問い合わせマーク
vbExclamation48 注意マーク
vbInformation64 情報マーク
標準
ボタン
vbDefaultButton10第1ボタンを標準ボタンに(既定)
vbDefaultButton2256第2ボタンを標準ボタンに
vbDefaultButton3512第3ボタンを標準ボタンに
vbDefaultButton4768第4ボタンを標準ボタンに
図12


図12は図8の上半分の形です。図12には無い値(例えば、システムモーダルに対応した「4096」値)を設定してしまうと、メッセージボックスが表示されなくなってしまいますので注意が必要です。

今回は「WSH.Popup Word, T」と設定しましたので、メッセージには引数で得たメッセージ文字列を、表示時間は「2秒(実際には5秒くらいしないと消えません)」となります。また第三・第四引数は省略した為、ダイアログのタイトル部は「Windows Script Host」となり、ボタンはOKボタンのみでアイコンは無い状態です。

表示終了後、61行目「Set WSH = Nothing」では59行目で生成したWSHオブジェクトを解放しています。
Popupメソッドで表示されるメッセージは図13のようになります。
Popupメソッドの実行結果
図13


なお、Popupでのメッセージが表示されている状態でExcelにフォーカスを移す(セル選択操作や書式設定は可能ですが、セル入力は不可能です)と、メッセージはExcelの裏側に廻り見えなくなってしまいますが、時間のカウントは続けているようです。
また「タスクマネージャー」を起動していると、なぜかPopupメッセージが閉じてくれません。いずれにしても、ちょっと不安定なメソッドです。

6.UserForm(モードレス)

6ー1.標準モジュール(Module1)

4番目のボタンをクリックした時に呼び出されるのが、図14です。
  1. '========== ⇩(7) ボタン4による呼び出し(UserForm モードレス) ============
  2. Sub Button4()
  3.  Call UserForm1.UFstart("おはよう こんにちは", 2)
  4. End Sub
図14


72行目「Call UserForm1.UFstart("おはよう こんにちは", 2)」では、UserForm1の中の「UFstart」プロシージャを呼び出しています。引数として、メッセージ文字列と閉じるまでの時間(秒)を渡します。
なお閉じるまでの時間は、フォーム内ではTimer関数で管理していますので、ミリ秒単位での設定が可能です。

6ー2.ユーザーフォーム(UserForm1)

6ー2ー1.フォームレイアウト

フォーム上には、図15のように「Labelコントロール」を1つ配置します。Labelの高さ(Height)は今回24ポイント(≒2行分)としましたが、メッセージを複数行表示する可能性があるのであれば、その行数が入るだけを確保する必要があります。
UserForm1のフォームレイアウト
図15


6ー2ー2.フォームモジュール

図14の72行目から呼び出されるのが、フォームモジュール上の図16です。
引数として、メッセージとして表示する文字列と、閉じるまでの時間(秒)を受け取ります。
  1. '========== ⇩(8) フォームの起動と終了 ============
  2. Public Sub UFstart(Word As String, T As Single)
  3.  Dim startT As Single    '←表示開始時の時刻
  4.  Me.Label1.Caption = Word
  5.  Me.Show vbModeless
  6.  startT = Timer()
  7.  Do While (startT + T) > Timer()
  8.   DoEvents: DoEvents
  9.  Loop
  10.  Unload Me
  11. End Sub
図16


84行目「Me.Label1.Caption = Word」では、Labelコントロールに引数で受け取ったメッセージを設定します。
85行目「Me.Show vbModeless」では、このフォームを「モードレス」で起動します。と言って85行目の実行で初めてフォームが生成される訳では無く、図16のUFstartプロシージャが呼び出された時には「UFstartプロシージャが実行されるより前にInitializeイベントが先に発生」しますのでフォーム生成済みです。なお今回はInitializeイベントプロシージャはありません。

85行目では「フォームはモードレスで起動」しますので、処理は次のコードに移り、87行目以降を実行します。
87行目「startT = Timer()」では、Timer関数を使って現在の時刻(ミリ秒単位)を変数startTに代入します。
88~90行目のDo~Loopでは、設定時間分だけ空回しをし、次のコードへ移る待ち時間を作ります。

88行目「Do While (startT + T) > Timer()」は、回る条件が「While (startT + T) > Timer()」となっていますので、87行目で設定した時刻(startT)に引数の「閉じるまでの時間(T)」を足した時刻と、現在の時刻を比較します。つまり設定した「閉じるまでの時間(T)」を経過するまではDo~Loopを繰り返すことになります。
Whileの代わりにUntilを使用し「Do Until (startT + T) < Timer()」としてもほぼ同じ結果が得られます。

89行目の「DoEvents」を2つ実行している部分は、万一Do~Loopが無限ループになってしまった時に「ESCキーで割り込んでVBAコードを中断」させるために、念のため入れています。

「閉じるまでの時間」が経過し Do~Loopを抜け出たら、92行目「Unload Me」で自分自身(UserForm1)を閉じます。閉じたら制御は図14に戻り、シート上のボタンクリックによる処理が終了します。
UserForm1を実行することで表示されるメッセージは図17のようになります。
UserForm1の表示状態
図17


モードレスでフォームを起動していますので、図17のようにフォーム起動中でもExcel操作が可能です。セル入力状態になっていても、フォーム内でカウントは進められる為に、設定時刻になれば「入力中でもフォームが閉じる」ことになります。

なお、表示中のメッセージダイアログの「右上×印」をクリックすることでフォームを閉じた場合でも、88~90行目のDo~Loopは回り続けていますので、制御が図14の73行目に戻ってくるのは「設定した秒数後」となります。閉じるまでの時間を長時間に設定することは少ないと思いますが、その場合には「ユーザーに無理やり閉じられた」時のことは考慮する必要があると思います。
と言って、Do~Loop内で毎回「フォームが表示されているかを確認」するのも行き過ぎの気がしますし、特にエラーは発生しないために、そのままとしてあります。

7.UserForm(モーダル)


7ー1.標準モジュール(Module1)

5番目のボタンをクリックした時に呼び出されるのが、図18です。
  1. '========== ⇩(9) ボタン5による呼び出し(UserForm モーダル) ============
  2. Sub Button5()
  3.  Call UserForm2.UFstart("おはよう こんにちは", 2)
  4. End Sub
図18


102行目「Call UserForm2.UFstart("おはよう こんにちは", 2)」では、UserForm2の中の「UFstart」プロシージャを呼び出しています。引数として、メッセージ文字列と閉じるまでの時間(秒)を渡します。
なお閉じるまでの時間は、フォーム内ではTimer関数で管理していますので、ミリ秒単位での設定が可能です。

7ー2.ユーザーフォーム(UserForm1)

7ー2ー1.フォームレイアウト

フォーム上には、図19のように「Labelコントロール」を1つ配置します。Labelの高さ(Height)は今回24ポイント(≒2行分)としましたが、メッセージを複数行表示する可能性があるのであれば、その行数が入るだけを確保する必要があります。
UserForm1のフォームレイアウト
図19


7ー2ー2.フォームモジュール

図18の102行目から呼び出されるのがフォームモジュール上の図20の114行目です。
引数として、メッセージとして表示する文字列と、閉じるまでの時間(秒)を受け取ります。
  1. '========== ⇩(10) フォームレベル変数の宣言 ============
  2. Dim T As Single '←フォーム内で使用する「閉じるまでの時間」
  3. '========== ⇩(11) メッセージのフォームを起動 ============
  4. Public Sub UFstart(Word As String, Tr As Single)
  5.  T = Tr
  6.  Me.Label1.Caption = Word
  7.  Me.Show vbModal
  8. End Sub
図20


標準モジュール側から呼び出された図20のUFstartプロシージャ内では、フォームをモーダル(フォーム上のコントロールしか操作不可)で起動している間、117行目の「Me.Show vbModal」でコードの進行がストップしています。そのため引数で受け取った「閉じるまでの時間」を、UFstartプロシージャ内ではカウントできません。

一方Showメソッドでフォームを起動した際には、Activateイベント(図21)が発生するため、その中で時間をカウントする事とします。そのため、UFstartプロシージャで受け取った「閉じるまでの時間」を一旦「フォームレベルの変数(111行目)」に代入し、その変数値を使ってActivateイベントプロシージャ内で「閉じるまでの時間」をカウントします。

そのフォームレベルの変数を宣言しているのが、111行目「Dim T As Single」で、その変数に「閉じるまでの時間」を代入しているのが115行目「T = Tr」になります。

116行目「Me.Label1.Caption = Word」ではフォーム上のLabelコントロールに、引数で受け取ったメッセージを書き込み、117行目「Me.Show vbModal」では、自分(UserForm2)を「モーダル」で起動します。

なお、標準モジュールの102行目からフォーム上のUFstartプロシージャを呼び出した際は、「 Initialize → UFstart 」の順序でプロシージャが実行されます。ですので117行目でShowメソッドを実行した時には、すでにInitializeイベントは通過しているため、次に実行されるのは「Activateイベント」となります。そのため「閉じるまでの時間」はActivate(図21)でカウントしています。

図20の117行目「Me.Show vbModal」でフォームを起動すると、図21のActivateイベントプロシージャが実行されます。
  1. '========== ⇩(12) 設定時間後にフォーム終了 ============
  2. Private Sub UserForm_Activate()
  3.  Dim startT As Single   '←表示開始時の時刻
  4.  startT = Timer()
  5.  Do While (startT + T) > Timer()
  6.   DoEvents: DoEvents
  7.  Loop
  8.  Unload Me
  9. End Sub
図21


134行目「startT = Timer()」では、Timer関数を使って現在の時刻(ミリ秒単位)を変数startTに代入します。
135~137行目のDo~Loopでは、設定時間分だけ空回しをし、次のコードへの待ち時間を作ります。

135行目「Do While (startT + T) > Timer()」では、設定した「閉じるまでの時間(T)」を経過するまではDo~Loopを繰り返した後、139行目「Unload Me」で自分自身(UserForm2)を閉じます。 なお136行目の「DoEvents」を2つ実行している部分は、万一Do~Loopが無限ループになってしまった時に「ESCキーで脱出」する為のものです

UserForm2を実行することで表示されるメッセージは図22のようになります。
UserForm2の表示状態
図22


図17とは異なり、モーダル状態でフォームを起動すると「フォームが閉じるまでは、シート操作が不可」となります。

なおモードレスの時と同様に、表示中のメッセージダイアログの「右上×印」をクリックすることでフォームを閉じた場合でも、135~137行目のDo~Loopは回り続けていますので、制御が図18の103行目に戻ってくるのは「設定した秒数後」となります。

8.テキスト図形

6番目のボタンをクリックした時に呼び出されるのが、図23のButton6プロシージャです。
  1. '========== ⇩(13) ボタン6による呼び出し(図形) ============
  2. Sub Button6()
  3.  Call myMsgBox6("おはよう こんにちは", 2)
  4. End Sub
  5. '========== ⇩(14) メッセージ図形の描画と削除 ============
  6. Public Sub myMsgBox6(Word As String, T As Single)
  7.  Dim L As Shape   '←表示する図形
  8.  Dim startT As Single   '←表示開始時の時刻
  9.  Dim X As Double   '←図形の左上角の位置(X方向)
  10.  Dim Y As Double   '←図形の左上角の位置(Y方向)
  11.  With ActiveWindow.VisibleRange
  12.   X = .Left + .Width / 2
  13.   Y = .Top + .Height / 2
  14.  End With
  15.  Set L = ActiveSheet.Shapes.AddLabel(msoTextOrientationHorizontal, X, Y, 0, 0)
  16.  L.TextFrame2.TextRange.Characters.Text = Word
  17.  L.TextFrame2.WordWrap = msoFalse
  18.  L.Fill.BackColor.RGB = RGB(255, 0, 0)
  19.  L.Line.Visible = msoTrue
  20.  L.Shadow.Type = msoShadow21
  21.  startT = Timer()
  22.  Do While (startT + T) > Timer()
  23.   DoEvents: DoEvents
  24.  Loop
  25.  On Error Resume Next
  26.   L.Delete
  27.  On Error GoTo 0
  28.  Set L = Nothing
  29. End Sub
図23


152行目「Call myMsgBox6("おはよう こんにちは", 2)」では、156行目のmyMsgBox6プロシージャを呼び出します。引数として、メッセージ文字列と閉じるまでの時間(秒)を渡します。

まず162~165行目で、メッセージ図形を描画する位置を計算します。
これまで説明してきた「メッセージボックスを表示するタイプ」は、極端に言えば「パソコンディスプレイのどこに表示されてもOk」です。例えば、Excelのリボンの上でも表示可能です。
しかし今回の「図形」では、それは出来ません。図形は「ワークシート上」に貼り付ける必要があります。しかも、広大なワークシートの中の「ユーザーが見える範囲」でないと意味がありません(Chartシート上に図形を貼り付けることも可能ですが、今回はワークシートにしか対応させていません)。

そこで、図24のような「ActiveWindow.VisibleRange」というセル範囲を今回は使用します。
UserForm2の表示状態
図24


「VisibleRange」は「見えているセル範囲」です。「見えているセル範囲」の左上セル(図24ではA1セル)は、セルの左上角がExcelのワークシートの原点に合った位置で表示されていますが、右下セル(同N17セル)は一部分しか見えていない状態です。しかし「VisibleRange」は、少しでも見えていれば範囲に含まれますので、図24での「VisibleRange」は「A1:N17」という事になります。

今回は、この「VisibleRange」の中央位置を、表示するメッセージ図形の左上角になるようにしました。
162行目「With ActiveWindow.VisibleRange」で「VisibleRange」範囲を基準とし、163行目「X = .Left + .Width / 2」で「VisibleRange」範囲の横(X)方向の中央位置を計算します。考え方としては、VisibleRange範囲左端までの距離とVisibleRange範囲の幅の中央を合計することで、ドキュメント座標原点(A1セルの左上角)からの距離を得ています。
また164行目「Y = .Top + .Height / 2」では、「VisibleRange」範囲の縦(Y)方向の中央位置を計算しています。

なお163行目の「.Left」の代わりに、「Columns(ActiveWindow.ScrollColumn).Left」という式を使っても同じ結果が得られます。「スクロール後の左端列の原点からの距離」という意味です。また164行目「.Top」の代わりは「Rows(ActiveWindow.ScrollRow).Top」となります。

「見えているセル範囲」の中央位置が計算できましたので、167行目「Set L = ActiveSheet.Shapes.AddLabel(msoTextOrientationHorizontal, X, Y, 0, 0)」で、新たにテキスト図形を作成し、変数Lとしています。

AddLabelメソッドには、図25のように5つのパラメータを指定します。全て必須です。
Shapes.AddLabel(Orientation, Left, Top, Width, Height)
パラメータ内容
OrientationMsoTextOrientationテキストの向き
LeftSingle左上角の横方向位置
TopSingle左上角の縦方向位置
WidthSingle
HeightSingle高さ
図25


また1番目のパラメータ「Orientation」は、図26のいずれかを指定します。
Orientationパラメータ(MsoTextOrientation型)
定数内容
msoTextOrientationHorizontal1横方向
msoTextOrientationUpward2上向き
msoTextOrientationDownward3下向き
msoTextOrientationVerticalFarEast4垂直(アジア言語用)
msoTextOrientationVertical5
図26


今回167行目では、テキスト方向として「横方向」を指定し、テキスト図形の左上角の位置として「横(X)方向」を163行目で計算した値、「縦(Y)方向」を164行目で計算した値を指定しています。
またテキスト図形の幅・高さは「ゼロ」とし、メッセージとして表示するテキストのサイズに大きさを合わせる(170行目)こととしました。

169~173行目では、167行目で作成したテキスト図形に文字や色の装飾を与えています。
169行目「L.TextFrame2.TextRange.Characters.Text = Word」は、メッセージの文字列を設定しています。
170行目「L.TextFrame2.WordWrap = msoFalse」は、「図形の境界に合わせてテキスト行を折り返す」をOFFにしています。これは、シート上の図形を選択した後「図形の書式設定」→「サイズとプロパティ」→「プロパティ」→「テキストボックス」を選び、「図形内でテキストを折り返す」のレ点を外すのと同じ動作になります。
この設定により「テキストに合わせて図形がサイズを変える」ことになります。
寄り道
手動で図形を作成した場合、図形のサイズをテキストに合わせる為には、上記の「図形内でテキストを折り返す」をOFFにするのと同時に、「テキストに合わせて図形のサイズを調整する」をONにする必要があります。
この「サイズ調整」をVBAで表すと「図形.TextFrame2.AutoSize = msoAutoSizeShapeToFitText」となります。なお、手動で図形を作成した時には、既定は「図形.TextFrame2.AutoSize = msoAutoSizeNone」とOFFとなっています。

一方VBAで図形を作成した直後は、「図形.TextFrame2.WordWrap」 は「msoTrue」 とONになっているため、170行目のように「= msoFalse」に変更してやる必要があるのですが、サイズ調整の方の既定は「図形.TextFrame2.AutoSize = msoAutoSizeShapeToFitText」なので、今回はAutoSizeプロパティを「変更する必要は無い」のです。

手動作成とVBA作成で「既定値が異なる」プロパティは、他にも存在するかもしれません。マクロ記録で取得したコードをそのまま使用すれば目的は達成できるかもしれませんが、無駄にコードを実行している場合もありそうです。

171行目「L.Fill.BackColor.RGB = RGB(255, 0, 0)」は、テキスト図形の背景色を「赤色」にしています。
172行目「L.Line.Visible = msoTrue」は、テキスト図形の枠線を「有り」にしています。
173行目「L.Shadow.Type = msoShadow21」は、テキスト図形に影を付けて「立体的」に見えるようにしています。Shadow.Typeプロパティには、msoShadow1 ~ msoShadow43 の43種類が設定できるようですが、今回は「右下に短く薄く伸びる影」を選んでいます。

以上でテキスト図形のメッセージが完成したため、175~178行目で設定秒数だけカウントをします。
175行目「startT = Timer()」で、現在の時刻(ミリ秒単位)を変数startTに代入します。
176行目「Do While (startT + T) > Timer()」では、設定した「閉じるまでの時間(T)」を経過するまではDo~Loopを繰り返します。

設定時間が経過したら181行目「L.Delete」で、メッセージのテキスト図形を削除します。
但しテキスト図形が表示されている間に、ユーザーが「テキスト図形を選択し、DELキー等で削除」してしまう事も考えられます。もし削除されてしまうと、Deleteする「オブジェクトが無い」事になりますので、181行目で「オブジェクトが必要です(エラー番号 = 424)」というエラーが発生してしまいます。
ですので、180行目「On Error Resume Next」でエラーが発生した際にはスルーをさせています。

184行目「Set L = Nothing」では、オブジェクトを解除しています。

なお、今回VisibleRangeで「見えているセル範囲」を取得しましたが、ウィンドウ枠の固定やウィンドウの分割をしていると、表示位置が変わるばかりか「メッセージのテキスト図形が見えない」可能性が出てきます。
対策として考えられるのは、「ウィンドウの固定や分割を一旦解除」する手法がありますが、元の状態に戻すのも結構面倒な気がしますので、固定や分割をする可能性のあるアプリでは別な手法を使った方が良いと思います。

9.ステータスバー(点滅無し)

7番目のボタンをクリックした時に呼び出されるのが、図27のButton7プロシージャです。
  1. '========== ⇩(15) ボタン7による呼び出し(ステータスバー点滅無し) ============
  2. Sub Button7()
  3.  Call myMsgBox7("おはよう こんにちは", 2)
  4. End Sub
  5. '========== ⇩(16) ステータスバーへの表示(点滅なし) ============
  6. Public Sub myMsgBox7(Word As String, T As Single)
  7.  Dim startT As Single   '←表示開始時の時刻
  8.  Dim stdStatusBar As Boolean   '←現状のステータスバー状態
  9.  stdStatusBar = Application.DisplayStatusBar
  10.  Application.DisplayStatusBar = True
  11.  Application.StatusBar = Word
  12.  startT = Timer()
  13.  Do While (startT + T) > Timer()
  14.   DoEvents: DoEvents
  15.  Loop
  16.  Application.StatusBar = False
  17.  Application.DisplayStatusBar = stdStatusBar
  18. End Sub
図27


192行目「Call myMsgBox7("おはよう こんにちは", 2)」では、196行目のmyMsgBox7プロシージャを呼び出します。引数として、メッセージ文字列と閉じるまでの時間(秒)を渡します。

今回は「ステータスバーにメッセージを出す」ので、そのステータスバーが表示されている必要があります。しかし、設定でステータスバーを非表示にすることが可能(以前のバージョンではON-OFFがメニューから出来ましたが、最近のバージョンではメニューがありません。しかしVBAのコード実行によりON-OFFが可能です)ですので、作るアプリによってはステータスバーが非表示にされている可能性があります。

念のため、ステータスバーの表示・非表示の状態を図28に示します。
ステータスバー有無
図28


そこで今回は、もしステータスバーが非表示に設定されている場合は、一旦「ステータスバーを表示状態」にした後「メッセージを表示」し、設定時間が経過したら「表示を消し」て「ステータスバーを元の状態に戻す」ような仕様にしています。

200行目「stdStatusBar = Application.DisplayStatusBar」では、現在のステータスバーの状態(表示状態=True、非表示状態=False)を取得し、変数stdStatusBarに保存します。
201行目「Application.DisplayStatusBar = True」では、ステータスバーを表示状態にしています。現状が表示状態であった場合は、そのまま表示状態が続くことになります。気になる方は「If stdStatusBar = False Then Application.DisplayStatusBar = True」と、200行目の結果を使って「非表示の場合のみ表示状態にする」方法でもOKです。

203行目「Application.StatusBar = Word」では、引数で受け取ったメッセージを「ステータスバーに表示」します。

205~208行目では、設定秒数だけカウントをします。
205行目「startT = Timer()」で、現在の時刻(ミリ秒単位)を変数startTに代入します。
206行目「Do While (startT + T) > Timer()」では、設定した「閉じるまでの時間(T)」を経過するまではDo~Loopを繰り返します。

設定時間が経過したら210行目「Application.StatusBar = False」で、ステータスバーの表示を既定状態(図28の右側のように「準備完了・・・」など)に戻します。
最後に211行目「Application.DisplayStatusBar = stdStatusBar」で、ステータスバーの状態を200行目で取得した状態「変数stdStatusBar」に戻します。

なお、ステータスバーは「1行分」しか無いため、メッセージ文字列として「改行」が入っている文字列を指定すると、図29のように「2行目以降が隠れてしま」うことになりますので、注意が必要です。
ステータスバーに複数行を表示させようとした場合
図29


メッセージ表示中のステータスバーの状態は、図30のようになります。
ステータスバーのメッセージ
図30


10.ステータスバー(点滅有り)

8番目のボタンをクリックした時に呼び出されるのが、図31のButton8プロシージャです。
  1. '========== ⇩(17) ボタン8による呼び出し(ステータスバー点滅有り) ============
  2. Sub Button8()
  3.  Call myMsgBox8("おはよう こんにちは", 2)
  4. End Sub
  5. '========== ⇩(18) ステータスバーへの表示(点滅有り) ============
  6. Public Sub myMsgBox8(Word As String, T As Single)
  7.  Dim startT As Single   '←表示開始時の時刻
  8.  Dim stdStatusBar As Boolean   '←現状のステータスバー状態
  9.  Dim i As Integer   '←点滅の回数
  10.  stdStatusBar = Application.DisplayStatusBar
  11.  Application.DisplayStatusBar = True
  12.  For i = 1 To 5
  13.   Application.StatusBar = Chr(32)
  14.   Application.Wait [Now()] + 100 / 86400000
  15.   Application.StatusBar = Word
  16.   Application.Wait [Now()] + 100 / 86400000
  17.  Next i
  18.  startT = Timer()
  19.  Do While (startT + T) > Timer()
  20.   DoEvents: DoEvents
  21.  Loop
  22.  Application.StatusBar = False
  23.  Application.DisplayStatusBar = stdStatusBar
  24. End Sub
図31


222行目「Call myMsgBox8("おはよう こんにちは", 2)」では、226行目のmyMsgBox8プロシージャを呼び出します。引数として、メッセージ文字列と閉じるまでの時間(秒)を渡します。

この仕様も、メッセージを表示するためにはステータスバーが表示されている必要があります。ですので231行目「stdStatusBar = Application.DisplayStatusBar」で現在のステータスバーの状態を取得し、変数stdStatusBarに保存します。
232行目「Application.DisplayStatusBar = True」では、ステータスバーを表示状態にしています。現状が表示状態であった場合は、そのまま表示状態が続くことになります。

234~239行目では、メッセージを点滅させています。
234行目「For i = 1 To 5」では、235~238行目を5回繰り返します。つまりメッセージを5回点滅させています。
235行目「Application.StatusBar = Chr(32)」では、ステータスバーに「半角スペース」を表示させています。実際には図32の左側のように、ステータスバーは空になります。
ステータスバーのメッセージ
図32


ここで「Application.StatusBar = ""」と「長さゼロの文字列」を指定しても同じになりそうですが、残念ながら長さゼロの文字列を指定すると「Application.StatusBar = False」を指定した事になるようです。ですので、図28の左側のような既定の文字列(準備完了・・・など)が表示されてしまいます。

236行目「Application.Wait [Now()] + 100 / 86400000」では、プログラムを0.1秒間ストップさせています。つまりステータスバーが空の状態を「0.1秒間保持」しています。
なお、通常使用している関数 Now() は「秒単位」ですが、 [Now()] と角カッコで囲むことで「ミリ秒単位」で現在の日時を取得することが出来ます。ここで使用している角カッコは「Evaluateメソッド」の代わりになるもので、「引数(文字列)をオブジェクトや値に変換」するものです。ですので [Now()] の部分は、Application.Evaluate("Now()")としてもOKです。

また236行目の後ろ半分の「100 / 86400000」は、100ミリ秒を表しています。日時の値は「1日=1」ですので、1日をミリ秒で表した「24時間 * 60分 * 60秒 * 1000ミリ秒 = 86,400,000 」を分母に持ってきて、分子に設定するミリ秒を指定しています。

237行目「Application.StatusBar = Word」は、引数で受け取ったメッセージをステータスバーに表示し、238行目「Application.Wait [Now()] + 100 / 86400000」で0.1秒間保持します。
この「空白」⇔「メッセージ」を図32のように5回繰り返します。

234~239行目のFor~Nextを抜けた時には「ステータスバーにメッセージが表示」されている状態です。その状態を241~244行目のDo~Loopで設定時間だけ保持します。
241行目「startT = Timer()」で、現在の時刻(ミリ秒単位)を変数startTに代入し、242行目「Do While (startT + T) > Timer()」で、設定した「閉じるまでの時間(T)」を経過するまではDo~Loopを繰り返しています。

設定時間が経過したら、246行目「Application.StatusBar = False」でステータスバーを既定状態に戻します。
最後に247行目「Application.DisplayStatusBar = stdStatusBar」で、ステータスバー表示・非表示を元の状態(231行目で取得した状態)に戻します。

11.まとめ

11ー1.メッセージ表示位置

ステータスバーへの表示は別として、ダイアログやテキスト図形を表示する場合、その表示位置を決める基準は図33のように3種類あると思います。
メッセージが表示される位置
図33


1つ目は「PCの画面枠」で、その中心(図33の緑丸)に表示されるのが「MsgBox」「APIのMessageBoxTimeoutA」「WSHのPopup」です。
2つ目は「Excel外枠」で、その中心(図33の赤丸)に表示されるのが「ユーザーフォーム」です。なお、フォームの表示位置をStartUpPositionプロパティで指定することは可能です。
MsgBox等とユーザーフォームは「スクリーン座標」上のオブジェクトです。

3つ目は「Excelのシート内部」です。テキスト図形はシート上(ドキュメント座標上)に配置する必要があるため、今回は見えているセル範囲として図33の青色(VisibleRange)を基準として図形を表示させました。

11ー2.メリット・デメリット

以上をまとめてみると、図34のようになるかと思います。長所短所を把握して活用すれば、ユーザーへの負担(クリックする手間)少なく、情報も伝わるメッセージとすることが出来ると思います。
APIWSHUserForm図形ステータスバー
視認性
デザイン
自由度

アイコン可

アイコン可

色・サイズ等

形状・色・サイズ等
×
文字のみ
複数行
事前に確保要
×
時間制御ミリ秒Timer関数使用で ミリ秒可
表示
位置
既定画面中央画面中央Excel中央シート上ステータスバー
変更×××
その他不安定
図34


アプリ実例

DVD等の内容・保管場所等管理システム
図形も貼り付けられるスケジュール帳


自動的に閉じるメッセージ(its-022.xlsm)
セキュリティ向上を目的として「インターネット経由でダウンロードしたOfficeファイル(Excel等)のマクロは、既定でブロック」されるようにOfficeアプリケーションの既定動作が変更になりました。(2022年4月より切替開始)
解除の方法については「ダウンロードファイルのブロック解除方法」を参照下さい。