Programming Field

VBAのメッセージボックスやInputBoxで絵文字や特殊漢字・環境依存文字などを使う

半分ネタのTipsです。VBAは文字列がUnicode(BSTR)であるにもかかわらず、MsgBoxやInputBoxに(Shift_JISに変換できない)Unicode文字を渡すと「?」文字に置き換わってしまいます。

このTipsでは、メッセージボックスやInputBoxでUnicode文字、より具体的には絵文字や特殊な漢字などを扱えるようにする方法を紹介します。

※ 以下、Windows環境前提で記述しています。

メッセージボックスでUnicode文字を使う

一番簡単なのは、Win32APIの「MessageBoxW」関数を使うという方法です。MsgBoxと引数が類似しているため、少ない記述で利用することができます。

Public Declare PtrSafe Function MessageBoxW Lib "user32.dll" _
    (ByVal hWndParent As LongPtr, _
    ByVal lpszMessage As LongPtr, _
    ByVal lpszTitle As LongPtr, _
    ByVal dwOptions As VbMsgBoxStyle) As VbMsgBoxResult

Public Sub Test1()
    ' 文字列は StrPtr で渡す必要がある
    Call MessageBoxW(Application.hWnd, _
        StrPtr(ChrW$(55357) + ChrW$(56839)), _
        StrPtr(Application.Name), _
        vbCritical)
End Sub

この「Test1」プロシージャを実行すると、以下のようなメッセージボックスが表示されます。

絵文字「😇」が表示されたメッセージボックス

※ 「ChrW$(55357) + ChrW$(56839)」は、Unicode(UTF-16)で「😇」を表す文字列になります。

ポイントとして、通常VBAで外部関数を呼び出すときに使うようなANSI版(「MessageBoxA」)関数を使うと、ANSI文字列に変換する際にUnicode文字の一部が失われてしまうため、Unicode版関数である「MessageBoxW」を直接利用しています。この際、変換を防止するために文字列を文字列ポインターで渡す必要があります。

※ MessageBoxW の dwOptions 引数は本来は Long (DWORD) ですが、指定すべき定数が VbMsgBoxStyle と完全に一致しているため、VbMsgBoxStyle を用いています。同様に戻り値も VbMsgBoxResult を使っています。

InputBoxでUnicode文字を使う?

ではInputBoxはどうかというと、InputBoxはVBAが独自に用意しているダイアログであり、Win32APIで提供されている機能ではありません。そのため、「Unicode版」という概念が存在せず、そのままではUnicode文字を渡すことができません。(また、InputBoxで絵文字などを入力しても「?」に置き換わってしまいます。)

これを回避するには以下の方法が考えられます。

  1. UserForm (MSForms) でInputBoxを作る
  2. InputBoxをフックしてテキストを置き換える

UserForm (MSForms) でInputBoxを作る

こちらは比較的容易かつ安全に対応できるもので、単にInputBoxに似せたフォームをUserForm(ユーザーフォーム)で表現して使うという方法です。

注意点として、フォームデザイナーで「プロパティ」に直接キャプションなどを設定しようとしても、Unicode文字は入力できない(「?」になる)ため、以下のように Initialize イベントで設定してあげる必要があります。

Private Sub UserForm_Initialize()
    Label1.Caption = "あ" + ChrW$(&HD83D&) + ChrW$(&HDE80&)
End Sub

これの実行例は以下の通りです。

絵文字「🚀」が表示されたフォーム

細かい弱点としては、テキストボックスで右クリックしてもメニューが出ない、自前でハンドルしない限りESCキーを押してもフォームが閉じられない(「キャンセル」ボタンを設定してハンドルする必要がある)、などがあり、よりInputBoxに近づけるにはちゃんと作りこむ必要があります。

InputBoxをフックしてテキストを置き換える

※ 以下の内容はコーディングをミスするとエラーでVBAのホストアプリケーション(Excelなど)そのものが異常終了する可能性があります。未保存のドキュメントは事前に保存しておくことを推奨します。

こちらの方法は、「InputBox関数で表示されるダイアログの作成イベントや『OK』ボタン処理を監視し、Unicode文字を扱えるようにする」といったもので、Win32APIの機能を使って処理をフックすることで実現します。

コードは以下の通りです。(※ 以下に gist.github.com にアップロードしたソースコードを埋め込んでいます。表示されない場合はこちら → https://gist.github.com/jet2jet/dc28123215b0cc7894e7fbf56eb093b4)

これを以下のコードのように(普通のInputBoxと同じように)使用します。

Public Sub Test()
    Dim s As String
    s = InputBoxEx("メッセージだよ" + ChrW$(&HD83D&) + ChrW$(&HDE80&), "タイトル" + ChrW$(&HD83D&) + ChrW$(&HDE07&), "テキスト" + ChrW(&HD83D&) + ChrW(&HDE2E&) + ChrW(&H200D&) + ChrW(&HD83D&) + ChrW(&HDCA8&))
    Range("A1").Value = s
End Sub

この実行結果は以下の通りです。

各種絵文字が表示されたInputBox

コード詳細

このコードでは、オリジナルのInputBoxを呼び出す前に現在のスレッドに対してCBTフックをインストールします。これをすることで、現在のスレッド上でウィンドウが作成されようとしたときに、そのウィンドウに対してWindowProcの横取り(サブクラス化)などの操作を行うことができるようになります。

WindowProcの横取りを行ったら、その中でWM_INITDIALOGメッセージを待ち受け、そのメッセージがやってきたらUnicode版のSetWindowText(SetWindowTextW)を使ってUnicodeテキストを設定します。

※ SetDlgItemTextWを使っても問題ないと考えられますが、コントロールIDが既知のものではないのでFindWindowExを使ってウィンドウを探しています。

また、WM_COMMANDメッセージもハンドルし、OKボタン(※ ウィンドウの設定によりテキストボックスでEnterが押されたときもOKボタン押下扱いになる)が押下されたら今度はテキストボックスからGetWindowText(GetWindowTextW)経由でUnicodeテキストを取得し、一旦グローバル変数に退避させておきます。OKボタン押下でInputBoxが閉じられるため、InputBoxの処理から抜けたらInputBoxExの戻り値として退避させていたテキストを返します。

これにより、InputBoxでもUnicode文字を利用できるようになります。

おまけ: Unicode混じりのテキストをChrWを使った文字列に変換

以下のテキストボックスに絵文字などを混ぜたテキストを入力すると、ChrW 関数を使った文字列表現が出力されます。(Shift_JISで表現できない文字を ChrW 関数に置き換えています。)

結果: