【VBA×WindowsAPI】UserFormを右クリックした時にコンテキストメニューを表示させる
VBAのUserForm上で右クリックした時にコンテキストメニューを出したいという場面に出くわすことがあります。WindowsAPIにはメニューバーのメニューをはじめとした、メニューを作成するための機能が存在します。このうちポップアップメニューを作成する機能を使うことで、UserFormに対してのオリジナルのコンテキストメニューを作成することができます。
右クリックの検知自体はUserFormイベントを使っているだけなので、各コントロール別にメニューの内容を切り替えたりその他のイベントを利用してコンテキストメニューを表示させることも可能です。
UserFormにコンテキストメニューを作成する
WindowsAPIを使うことで、上画像のようにUserForm上を右クリックした際に自作のコンテキストメニューを表示させることができます。各メニュー項目には任意の文字列はもちろん水平線やアイコン画像、メニュー項目自体に画像を設定することが可能です。各メニュー項目には任意のIDを付与することができ、このIDによってユーザーが選択した項目を判定することができます。
VBAでUserFormにコンテキストメニューを作成するには下記のWindows APIを利用します。
それぞれ関数のより詳細な使い方の解説は各関数のリンクページを参照下さい。
FindWindow関数 :ウィンドウのハンドルを取得する
CreatePopupMenu関数 :ポップアップメニューを作成する
DestroyMenu関数 :メニューを削除する
AppendMenu関数 :メニュー項目を追加する
GetCursorPos関数 :カーソルの座標を取得する
TrackPopupMenu関数 :ポップアップメニューを表示させる
SetMenuItemBitmaps関数:メニューにアイコン画像を設定する
LoadImage関数 :画像をメモリ上に読み込ませる
DeleteObject関数 :メモリ上の画像を削除する
「そもそもWindows APIって何?」という方はコチラ(メインページ)も併せて参照下さい。
サンプルコード
UserFormを右クリックした時に自作のコンテキストメニューを表示させるためのサンプルコードは下記の通りです。画像パス部分は読み込ませたいビットマップ画像のフルパスを入力して下さい。未入力や存在しないパスを入力した場合は画像の読み込みに失敗し、表示されるコンテキストメニューの画像部分に何も表示されなくなります。 (画像が表示されないだけでその他への影響は特にありません)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 |
Option Explicit Private Declare PtrSafe Function FindWindow Lib "user32" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr Private Declare PtrSafe Function CreatePopupMenu Lib "user32" () As LongPtr Private Declare PtrSafe Function DestroyMenu Lib "user32" (ByVal hMenu As LongPtr) As Long Private Declare PtrSafe Function AppendMenu Lib "user32" Alias "AppendMenuA" (ByVal hMenu As LongPtr, ByVal wFlags As Long, ByVal wIDNewItem As LongPtr, ByVal lpNewItem As Any) As Long Private Declare PtrSafe Function GetCursorPos Lib "user32" (lpPoint As POINTAPI) As Long Private Declare PtrSafe Function TrackPopupMenu Lib "user32" (ByVal hMenu As LongPtr, ByVal wFlags As Long, ByVal X As Long, ByVal Y As Long, ByVal nReserved As Long, ByVal hwnd As LongPtr, lprc As Long) As Long Private Declare PtrSafe Function SetMenuItemBitmaps Lib "user32" (ByVal hMenu As LongPtr, ByVal nPosition As Long, ByVal wFlags As Long, ByVal hBitmapUnchecked As LongPtr, ByVal hBitmapChecked As LongPtr) As Long Private Declare PtrSafe Function LoadImage Lib "user32" Alias "LoadImageA" (ByVal hInst As LongPtr, ByVal lpsz As String, ByVal un1 As Long, ByVal n1 As Long, ByVal n2 As Long, ByVal un2 As Long) As LongPtr Private Declare PtrSafe Function DeleteObject Lib "gdi32" (ByVal hObject As LongPtr) As Long 'AppendMenu関数用定数 Private Const MF_STRING As Long = &H0 '文字列設定フラグ Private Const MF_DISABLED As Long = &H2 '選択不可フラグ Private Const MF_BITMAP As Long = &H4 'BMP画像設定フラグ Private Const MF_CHECKED As Long = &H8 'チェック付与フラグ Private Const MF_POPUP As Long = &H10 'サブメニューフラグ Private Const MF_SEPARATOR As Long = &H800 '水平線設定フラグ 'LoadImage関数用定数 Private Const IMAGE_BITMAP As Long = 0 Private Const LR_LOADFROMFILE As Long = &H10 'TrackPopupMenu関数用定数 Private Const TPM_RETURNCMD As Long = &H100 '戻り値を選択された項目のIDとする 'カーソル座標取得用構造体 Private Type POINTAPI X As Long Y As Long End Type Dim hWndForm As LongPtr 'UserFormウィンドウハンドル Dim hMenu As LongPtr 'ポップアップメニューハンドル Dim hSubMenu As LongPtr 'サブメニューハンドル Dim hBmpIcon As LongPtr 'BMP画像ハンドル Dim hBmpItem As LongPtr 'BMP画像ハンドル '------------------------------------------------------------------ ' UserForm起動時イベント '------------------------------------------------------------------ Private Sub UserForm_Initialize() 'UserFormウィンドウハンドル取得 hWndForm = FindWindow("ThunderDFrame", Me.Caption) 'メニューに設定する画像ファイルパスの指定 Dim sPathImgIcon As String Dim sPathImgItem As String sPathImgIcon = "C:\...\icon.bmp" 'メニューアイコン画像 sPathImgItem = "C:\...\item.bmp" 'メニュー項目画像 '画像をメモリ上に読み込み hBmpIcon = LoadImage(0, sPathImgIcon, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE) hBmpItem = LoadImage(0, sPathImgItem, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE) 'ポップアップメニュー作成 hMenu = CreatePopupMenu() 'メニュー項目に文字列追加 Call AppendMenu(hMenu, MF_STRING, 1, "文字列設定") 'メニュー項目に文字列&アイコン追加 Call AppendMenu(hMenu, MF_STRING, 2, "アイコン付与") Call SetMenuItemBitmaps(hMenu, 2, 0, hBmpIcon, 0) 'メニュー項目に選択不可状態の文字列追加 Call AppendMenu(hMenu, MF_STRING Or MF_DISABLED, 3, "選択不可") 'メニュー項目にチェック付与状態の文字列追加 Call AppendMenu(hMenu, MF_STRING Or MF_CHECKED, 4, "チェック付与") 'サブメニュー追加 hSubMenu = CreatePopupMenu() Call AppendMenu(hSubMenu, MF_STRING, 11, "文字列設定") Call AppendMenu(hSubMenu, MF_STRING, 12, "アイコン付与") Call SetMenuItemBitmaps(hSubMenu, 12, 0, hBmpIcon, 0) Call AppendMenu(hMenu, MF_POPUP, hSubMenu, "サブメニュー") 'メニュー項目に水平線追加 Call AppendMenu(hMenu, MF_SEPARATOR, 0, 0&) 'メニュー項目に画像追加 Call AppendMenu(hMenu, MF_BITMAP, 5, hBmpItem) End Sub '------------------------------------------------------------------ ' UserForm終了時イベント '------------------------------------------------------------------ Private Sub UserForm_Terminate() '読み込んだ画像を解放 Call DeleteObject(hBmpIcon) Call DeleteObject(hBmpItem) 'ポップアップメニュー解放 Call DestroyMenu(hSubMenu) Call DestroyMenu(hMenu) End Sub '------------------------------------------------------------------ ' UserFormマウスアップイベント '------------------------------------------------------------------ Private Sub UserForm_MouseUp(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) '右クリック時 If Button = 2 Then Dim tPos As POINTAPI Dim lRet As Long '現在のカーソル位置の座標取得 Call GetCursorPos(tPos) 'カーソル位置にポップアップメニューを表示 lRet = TrackPopupMenu(hMenu, TPM_RETURNCMD, tPos.X, tPos.Y, 0, hWndForm, 0) If lRet <> 0 Then Call MsgBox("「ID:" & lRet & "」の項目が選択されました", vbInformation) End If End If End Sub |
コード解説
UserFormのウィンドウハンドルを取得
ウィンドウにはそれぞれウィンドウハンドルと呼ばれる、ウィンドウを識別するためのID情報のようなものが付与されています。このウィンドウハンドルを取得することで、”どの”ウィンドウに対して処理を行うのかを簡単に指示することができます。(Windows APIでは頻出ワードです)
UserFormもウィンドウの1つであるため一意のウィンドウハンドルを持ちます。ウィンドウハンドルを取得するための関数はいくつも存在しますがここでは常套手段のFindWindow関数を使ってウィンドウハンドルを取得しています。FindWindow関数は下記のように記載することで指定のクラス名もしくはウィンドウ名から該当のウィンドウへのハンドルを取得することができます。
hWnd = FindWindow(“クラス名”, “ウィンドウ名”)
UserFormはクラス名が”ThunderDFrame”で、ウィンドウ名はMe.Captionとなります。いずれか片方を入力してもう一方の引数をvbNullStringとしても取得可能ですが、サンプルコードではどちらの引数も入力して確実にUserFormが取得できるようにしています。
画像をメモリ上に読み込み
コンテキストメニューに画像を設定したい場合はLoadImage関数を使ってBMP画像をメモリ上に読み込ませておく必要があります。LoadImage関数は引数によって画像を読み込む手段を変更することができますが、サンプルコードではファイルパスから指定のBMP画像を読み込む方法を採用しています。
下記のコードのsPathImg部分を任意のBMP画像のフルパスにすることでメモリ上にその画像を読み込ませることが出来ます。このとき、読み込ませた画像のハンドルが戻り値として取得できます。
hBmp = LoadImage(0, sPathImg, IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE)
読み込んだハンドルはそのプロセスが終了するとメモリ上から自動で解放されますが、DeleteObject関数を使って明示的にメモリから解放することもできます。サンプルコードではUserFormのTerminateイベントで明示的に解放していますが、この処理はなくても自動で解放されるので問題はありません。
ポップアップメニュー作成
コンテキストメニューは利用可能な操作メニューを一覧化したもののことをいい、メニューの種類としてはポップアップメニューに属するものが一般的です。ポップアップメニューはCreatePopupMenu関数を使うことで作成ができます。戻り値は作成したポップアップメニューのハンドルです。
hMenu = CreatePopupMenu()
ここで作成されるポップアップメニューはメモリ上に作成されるものであり画面上には表示されません。また、メニュー項目や画像などが設定されていない”空のメニュー”の状態となっています。そのため、戻り値として取得したポップアップメニューのハンドルを使って、任意のメニュー項目を追加していく必要があります。このときメニュー項目に対してアイコン画像を付与することも可能です。
メニューの設定
作成したポップアップメニューに項目を追加するにはAppendMenu関数を使います。
Call AppendMenu(hMenu, wFlags, wIDNewItem, lpNewItem)
第1引数のhMenuには作成したポップアップメニューのハンドル、第2引数のwFlagsにはメニュー項目の内容(外観や動作)を定義するためのフラグ、第3引数のwIDNewItemには項目の識別ID、第4引数のlpNewItemには項目の内容をそれぞれ入力します。
wFlagsの代表的なフラグは下表の通りです。サンプルコードではこれらすべてのフラグを使って具体的なメニュー項目の設定を行ってるので、各引数の入力方法はサンプルコードを参照下さい。
フラグ (定数) | 説明 |
MF_STRING (&H0) | メニュー項目に文字列を設定する |
MF_DISABLED (&H2) | メニュー項目を選択不可状態にする |
MF_BITMAP (&H4) | メニュー項目にBMP画像を設定する |
MF_CHECKED (&H8) | メニュー項目をチェック付与状態にする |
MF_POPUP (&H10) | メニュー項目にサブメニューを付与する |
MF_SEPARATOR (&H800) | メニュー項目に水平分割線を設定する |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
'メニュー項目に文字列追加 Call AppendMenu(hMenu, MF_STRING, 1, "文字列設定") 'メニュー項目に文字列&アイコン追加 Call AppendMenu(hMenu, MF_STRING, 2, "アイコン付与") Call SetMenuItemBitmaps(hMenu, 2, 0, hBmpIcon, 0) 'メニュー項目に選択不可状態の文字列追加 Call AppendMenu(hMenu, MF_STRING Or MF_DISABLED, 3, "選択不可") 'メニュー項目にチェック付与状態の文字列追加 Call AppendMenu(hMenu, MF_STRING Or MF_CHECKED, 4, "チェック付与") 'サブメニュー追加 hSubMenu = CreateMenu() Call AppendMenu(hSubMenu, MF_STRING, 11, "文字列設定") Call AppendMenu(hSubMenu, MF_STRING, 12, "アイコン付与") Call SetMenuItemBitmaps(hSubMenu, 12, 0, hBmpIcon, 0) Call AppendMenu(hMenu, MF_POPUP, hSubMenu, "サブメニュー") 'メニュー項目に水平線追加 Call AppendMenu(hMenu, MF_SEPARATOR, 0, 0&) 'メニュー項目に画像追加 Call AppendMenu(hMenu, MF_BITMAP, 5, hBmpItem) |
アイコンの設定にはSetMenuItemBitmaps関数が別途必要になります。
Call SetMenuItemBitmaps(hMenu, nPosition, 0, hBmp, 0)
第1引数のhMenuにはメニューハンドル、第2引数のnPositionにはアイコンを設定する項目の識別ID(AppendMenu関数で設定)、第4引数のhBmpには設定するBMP画像のハンドルをそれぞれ設定します。第3, 5引数は今回のケースでは0としていますが、これらはサブメニューを持つメニュー項目へのアイコン付与や、チェック付与時のチェック部分のアイコンを指定したい場合に利用します。
サブメニューはメインのポップアップメニューとは別にポップアップメニューを作成して、親のポップアップメニューに対してAppendMenu関数のMF_POPUPフラグで埋め込むことで作成が可能です。サンプルコードではサブメニュー内の識別IDを11,12としていますが、これは1つ下の階層のサブメニューであるということを表現しているだけであり、特にルールなく好きな値を設定することが可能です。
コンテキストメニューの表示
前項まででメモリ上ではメニューの作成が完了した状態となっているので、ユーザーが操作できるように画面上に表示させる必要があります。コンテキストメニューは多くの場合、右クリックで表示されるためUserFormのMouseUpイベントで右クリックを検知して表示させます。
ポップアップメニューを画面上に表示させるにはTrackPopupMenu関数を使います。
Call TrackPopupMenu(hMenu, wFlags , X, Y, 0, hWnd, 0)
第1引数のhMenuには表示させるポップアップメニューのハンドル、第2引数には関数オプションのフラグ、第3,4引数のX,Yにはメニュー表示するスクリーン座標、第6引数のhWndにはメニューを所持させるウィンドウ(UserForm)のハンドル、第5,7引数には定数値「0」をそれぞれ設定します。
第2引数の関数オプションは様々ありますがここではTPM_RETURNCMDを設定しています。このフラグにより、ユーザーが選択したメニュー項目の識別IDを戻り値として取得することができるようになり、選択さえれた項目ごとに条件分岐処理を行うことができるようになります。
メニューを表示するX,Y座標はスクリーン座標(スクリーンの左上を原点とした座標)での値が必要であるため、GetCursorPos関数を使って右クリックされた際のカーソルの座標値を取得しています。
関連情報
VBA×WindowsAPIまとめページ
その他のWindowsAPI関数は下記ページにまとまっているので合わせて参照下さい。
参考
Microsoft公式:メニューについて – Win32 apps