ウインドウメッセージのリフティング処理(PostMessageの隠れた使い方)

ウインドウアプリケーションでは、ウインドウそれぞれがメッセージキューを持っていて順番にメッセージを処理します。処理が終わるまでは、次のメッセージは処理されません。この特性を利用して、処理のタイミングを調整するプログラミングテクニックをウインドウメッセージのリフティングと呼びます。


一般的な使い道は、画面の描画後に処理を行いたい場合などに使います。例えば、画面の初期表示時に行う処理で描画が終わってから処理を行いたい場合にForm_Loadでは早すぎる場合があります。その場合に、Form_Loadイベントの末尾でウインドウメッセージをPostMessageで送信しておくとWM_PAINTなど描画系のメッセージを処理した後に、メッセージを受信して任意の処理を行うことができます。


ウインドウメッセージを送信すると、すぐに処理されたり並行して処理されると思いがちですが、PostMessageで送信したメッセージはキューの末尾に入るので、他のすべてのメッセージ処理が終わるまで処理が開始しませんし、ウインドウを所有するスレッドが同じである限り並行して処理されることはありません。キューの処理は、同一スレッド内では完全にシーケンシャルに実行されます。


以下は、Form_Load時のイベント送信のサンプルです。

C#


private const Int32 WM_USER = 0x400;

private const Int32 WM_IPC_MESSAGE = WM_USER + 1;
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
private static extern bool PostMessage(IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam);


private void Form1_Load(object sender, System.EventArgs e)
{
PostMessage(this.Handle, WM_IPC_MESSAGE, IntPtr.Zero, IntPtr.Zero);

}


protected override void WndProc(ref System.Windows.Forms.Message m)
{
switch (m.Msg) {
case WM_IPC_MESSAGE:

if (ReceiveMessage != null) {
ReceiveMessage(_saveTransferMessage);
}


break;
}

base.WndProc(m);

}

VB.NET

Private Const WM_USER As Int32 = &H400
Private Const WM_IPC_MESSAGE As Int32 = WM_USER + 1

_
Private Shared Function PostMessage(ByVal hWnd As IntPtr, ByVal Msg As UInteger, ByVal wParam As IntPtr, ByVal lParam As IntPtr) As Boolean
End Function

Private Sub Form1_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load

PostMessage(Me.Handle, WM_IPC_MESSAGE, IntPtr.Zero, IntPtr.Zero)

End Sub

Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)

Select Case m.Msg
Case WM_IPC_MESSAGE

RaiseEvent ReceiveMessage(_saveTransferMessage)

End Select

MyBase.WndProc(m)

End Sub

処理のタイミングを遅延させたり、描画処理が完全に終わった後に処理をさせたり。といった事に利用できます。また、関連する2つ以上のイベントハンドラの相互作用で問題が発生する場合などの対処にも使えます。(例えば、Aを動かすと意図せずBが動いてしまうなど)


イベントのチェーン(連動)が問題になるわけですから、Aの処理を動かす際にBのイベントハンドラを外しておけば良いわけです。Aの処理では、BをRemoveHandlerしAの処理を動かして最後にPostMessageする。PostMessage受信後にBをAddHandlerする。そうするとBの処理をAの処理から分離することが出来ます。