【VBA×WindowsAPI】UserFormをフォーム内(クライアント領域)ドラッグで移動させる
UserFormはタイトルバーをドラッグすることで移動することができますが、場合によってはタイトルバーだけでなくフォームそのものをドラッグして移動させたい場合があります。ここではWindows APIを使ってUserFormのクライアント領域(タイトルバーではない部分)をドラッグすることで、UserFormを移動できるようにする方法を解説していきます。
UserFormのフォーム内ドラッグによる移動
通常、UserFormの位置はタイトルバーをドラッグして移動しますが、「タイトルバーがクリックされている」という情報をウィンドウに送信することで、上画像のようにクライアント領域(タイトルバーではない部分)をドラッグして移動させることが可能になります。これによりユーザビリティを向上させたり、UserFormのタイトルバーを消したとしても移動させたりすることができるようになります。
VBAでUserFormをクライアント領域のドラッグで移動させるには下記のWindow APIを利用します。
それぞれ関数のより詳細な使い方の解説は各関数のリンクページを参照下さい。
FindWindow関数 :ウィンドウハンドルを取得する
ReleaseCapture関数 :マウス入力のキャプチャを解放する
SendMessage関数 :ウィンドウメッセージを送信する
「そもそもWindows APIって何?」という方はコチラ(メインページ)も併せて参照下さい。
サンプルコード
UserFormをクライアント領域のドラッグで移動させるためのサンプルコードは下記の通りです。
UserFormを1つ作成し、下記コードをUserFormのコードに直接コピペします。これによりUserFormドラッグ時にUserForm_MouseDownイベントが発動しドラッグ移動が可能になります。
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 |
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 ReleaseCapture Lib "user32" () As Long Private Declare PtrSafe Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As LongPtr, ByVal wMsg As Long, ByVal wParam As LongPtr, lParam As Any) As LongPtr Private Const WM_NCLBUTTONDOWN As Long = &HA1 Private Const HTCAPTION As Long = 2 Private hWndForm As LongPtr '------------------------------------------------------------------ '- UserForm起動時イベント '------------------------------------------------------------------ Private Sub UserForm_Initialize() 'UserFormのウィンドウハンドル取得 hWndForm = FindWindow("ThunderDFrame", Me.caption) End Sub '------------------------------------------------------------------ '- マウスダウンイベント '------------------------------------------------------------------ Private Sub UserForm_MouseDown(ByVal Button As Integer, ByVal Shift As Integer, ByVal X As Single, ByVal Y As Single) If Button = 1 Then '左クリックのみ対象 Call ReleaseCapture Call SendMessage(hWndForm, WM_NCLBUTTONDOWN, HTCAPTION, 0) End If End Sub |
コード解説
ウィンドウハンドルを取得する
ウィンドウにはそれぞれを識別するためにウィンドウハンドルと呼ばれるIDが割り当てられています。WindowsAPIでウィンドウの情報を取得したり操作する際には、このウィンドウハンドルを使って行います。今回のケースでは、プログレスバーを作成する場所の情報として必要になります。
ウィンドウハンドルを取得する方法はいくつも存在しますがここでは常套手段のFindWindow関数を使ってウィンドウハンドルを取得しています。FindWindow関数は下記のように記載することで指定のクラス名もしくはウィンドウ名から該当のウィンドウへのハンドルを取得することができます。(UserFoemの場合はクラス名に「”ThunderDFrame”」、ウィンドウ名に「Me.Caption」で取得可能)
hWnd = FindWindow(“クラス名”, “ウィンドウ名”)
ドラッグ時にウィンドウメッセージ送信
Windowsのウィンドウは、常に何らかのアクションが発生すると「ウィンドウメッセージ」という信号を送信します。そして、その信号を受け取って適切な処理が振り分けられます。
たとえば、タイトルバーがクリックされると「タイトルバーがクリックされている」というメッセージが送信され続けます。そしてこのメッセージを受け取った場合は、現在のカーソル位置に追従するようにウィンドウの位置を変更するというような処理が内部で実行されるため、タイトルバーのドラッグでウィンドウを移動することができるような仕組みになっています。
このウィンドウメッセージを利用して、UserFormのクライアント領域がクリックされた場合に「タイトルバーがクリックされている」というメッセージを送信します。これにより、内部的にはタイトルバーがクリックされた際の処理が実行され、UserFormのドラッグ移動が可能になります。このウィンドウメッセージを送信するにはSendMessage関数を使い下記のように記載します。
Call SendMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, 0)
第1引数の「hWnd」には対象のUserFormのウィンドウハンドル、第2,第3引数には「タイトルバーがクリックされている」というメッセージを表す定数値である「WM_NCLBUTTONDOWN」「HTCAPTION」をそれぞれ入力します。第4引数は今回は使用しないので0を入力します。これにより、この処理が実行されると「タイトルバーがクリックされている」というウィンドウメッセージを送信することができます。つまりはドラッグ移動の処理を実行させるための命令文というわけです。
このメッセージはクライアント領域がクリックされている間は常に送信し続ける必要があるためUserForm_MouseDownイベントに記載しておく必要があります。また、ウィンドウメッセージを送信する前に、ReleaseCapture関数を実行してマウスのキャプチャを解放する必要があります。マウスが何かをドラッグしている(キャプチャしている)場合、現在の処理以外の処理は全て受け付けなくなるため、この関数を実行しないとウィンドウメッセージを送信しても無効になってしまうためです。
関連情報
VBA×WindowsAPIまとめページ
その他のWindowsAPI関数は下記ページにまとまっているので合わせて参照下さい。
参考
Microsoft公式:ウィンドウ メッセージ (Win32 と C++ の概要)