【VBA×WindowsAPI】UserFormをフォーム内(クライアント領域)ドラッグで移動させる

UserFormはタイトルバーをドラッグすることで移動することができますが、場合によってはタイトルバーだけでなくフォームそのものをドラッグして移動させたい場合があります。ここではWindows APIを使ってUserFormのクライアント領域(タイトルバーではない部分)をドラッグすることで、UserFormを移動できるようにする方法を解説していきます。

UserFormのフォーム内ドラッグによる移動

通常、UserFormの位置はタイトルバーをドラッグして移動しますが、「タイトルバーがクリックされている」という情報をウィンドウに送信することで、上画像のようにクライアント領域(タイトルバーではない部分)をドラッグして移動させることが可能になります。これによりユーザビリティを向上させたり、UserFormのタイトルバーを消したとしても移動させたりすることができるようになります。

VBAでUserFormをクライアント領域のドラッグで移動させるには下記のWindow APIを利用します。
それぞれ関数のより詳細な使い方の解説は各関数のリンクページを参照下さい。

icon-check-square FindWindow関数  :ウィンドウハンドルを取得する
icon-check-square ReleaseCapture関数 :マウス入力のキャプチャを解放する
icon-check-square SendMessage関数    :ウィンドウメッセージを送信する

「そもそもWindows APIって何?」という方はコチラ(メインページ)も併せて参照下さい。

サンプルコード

UserFormをクライアント領域のドラッグで移動させるためのサンプルコードは下記の通りです。
UserFormを1つ作成し、下記コードをUserFormのコードに直接コピペします。これによりUserFormドラッグ時にUserForm_MouseDownイベントが発動しドラッグ移動が可能になります。

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

コード解説

icon-edit ウィンドウハンドルを取得する

ウィンドウにはそれぞれを識別するためにウィンドウハンドルと呼ばれるIDが割り当てられています。WindowsAPIでウィンドウの情報を取得したり操作する際には、このウィンドウハンドルを使って行います。今回のケースでは、プログレスバーを作成する場所の情報として必要になります。

ウィンドウハンドルを取得する方法はいくつも存在しますがここでは常套手段のFindWindow関数を使ってウィンドウハンドルを取得しています。FindWindow関数は下記のように記載することで指定のクラス名もしくはウィンドウ名から該当のウィンドウへのハンドルを取得することができます。(UserFoemの場合はクラス名に「"ThunderDFrame"」、ウィンドウ名に「Me.Caption」で取得可能)

icon-code FindWindow関数 

hWnd = FindWindow(クラス名", “ウィンドウ名")

 

icon-edit ドラッグ時にウィンドウメッセージ送信

Windowsのウィンドウは、常に何らかのアクションが発生すると「ウィンドウメッセージ」という信号を送信します。そして、その信号を受け取って適切な処理が振り分けられます。

たとえば、タイトルバーがクリックされると「タイトルバーがクリックされている」というメッセージが送信され続けます。そしてこのメッセージを受け取った場合は、現在のカーソル位置に追従するようにウィンドウの位置を変更するというような処理が内部で実行されるため、タイトルバーのドラッグでウィンドウを移動することができるような仕組みになっています。

このウィンドウメッセージを利用して、UserFormのクライアント領域がクリックされた場合に「タイトルバーがクリックされている」というメッセージを送信します。これにより、内部的にはタイトルバーがクリックされた際の処理が実行され、UserFormのドラッグ移動が可能になります。このウィンドウメッセージを送信するにはSendMessage関数を使い下記のように記載します。

icon-code SendMessage関数 

Call SendMessage(hWnd, WM_NCLBUTTONDOWN, HTCAPTION, 0)

第1引数の「hWnd」には対象のUserFormのウィンドウハンドル、第2,第3引数には「タイトルバーがクリックされている」というメッセージを表す定数値である「WM_NCLBUTTONDOWN」「HTCAPTION」をそれぞれ入力します。第4引数は今回は使用しないので0を入力します。これにより、この処理が実行されると「タイトルバーがクリックされている」というウィンドウメッセージを送信することができます。つまりはドラッグ移動の処理を実行させるための命令文というわけです。

このメッセージはクライアント領域がクリックされている間は常に送信し続ける必要があるためUserForm_MouseDownイベントに記載しておく必要があります。また、ウィンドウメッセージを送信する前に、ReleaseCapture関数を実行してマウスのキャプチャを解放する必要があります。マウスが何かをドラッグしている(キャプチャしている)場合、現在の処理以外の処理は全て受け付けなくなるため、この関数を実行しないとウィンドウメッセージを送信しても無効になってしまうためです。
 

関連情報

icon-share-square VBA×WindowsAPIまとめページ

その他のWindowsAPI関数は下記ページにまとまっているので合わせて参照下さい。

icon-share-square 参考

Microsoft公式:ウィンドウ メッセージ (Win32 と C++ の概要)

Excel,VBA,Windows API