System.Windows.Forms.NativeWindowによる、ウインドウメッセージの横取り(サブクラス化)

サンプルコードNativeWindowSubClass.zip 直

NativeWindowクラスは、ウインドウ処理の基本となる処理をまとめたクラスで、ウインドウメッセージ(SendMessage,PostMessage)とウインドウプロシージャ(WndProc)の処理をカプセル化するための処理が集まっています。今回はこの機能を利用してウインドウメッセージの横取り(サブクラス化)を行います。

ここで言うサブクラス化はオブジェクト指向でのサブクラス化とは用語が異なります。オブジェクト指向で言うサブクラス化とは以下の様な事を指しますが、ここでのサブクラス化は単にウインドウメッセージを横取りして処理を行う事を指します。

サブクラス化とは、オブジェクト指向プログラミングにおいて、あるクラスの仕様を継承して新しいクラスを作ること。完全に同じ定義を継承して別の名前をつけるだけでは意味がないので、普通は何らかの追加や改変が行われる。サブクラス化でクラスを定義すれば、元になったクラスと違う部分だけを定義すればよく、それ以外のデータやメソッドは流用できるので、うまく利用すれば開発効率を向上させることができる。なお、元になったクラスを「スーパークラス」(super-class)あるいは「基底クラス」(base class)、新しいクラスを「サブクラス」(sub-class)あるいは「派生クラス」(derived class)と呼ぶ。その定義の通り、一般的にはスーパークラスほどシンプルで機能が少なく、派生すればするほど機能が豊富で「重たい」仕様となる。

NativeWindowによるウインドウのサブクラス化はWin32 ApiではGetWindowLongで既定のウインドウプロシージャを取得し、SetWindowLongでウインドウプロシージャを置き換え、任意の処理を実行後に、CallWindowProcを使って既定のウインドウプロシージャを実行するという一連の作業の.Netでの対応方法です。Win32 APIでの対応方法ではウインドウプロシージャを置き換える時に静的な関数のアドレスを渡す必要がありクラスメソッドのアドレスを渡すことが困難でしたがNativeWindowを使用すれば、サブクラス化する処理をクラスインスタンス毎に管理できます。

メッセージを横取りするMessageStealクラス。コンストラクタで受け取ったFormに対してウインドウハンドル作成時と破棄時のイベントに横取り処理を仕込みます。横取り処理は、NativeWindowにハンドルを割り当てるAssigneHandle、割り当てを解除するReleaseHandleで構成されます。ウインドウハンドルを関連付けるだけなので、ウインドウハンドルは生成されません。


Public Class MessageSteal
Inherits NativeWindow

Private _targetForm As Form = Nothing

Public Sub New(ByVal targetForm As Form)

_targetForm = targetForm

AddHandler _targetForm.HandleCreated, AddressOf Me.OnHandleCreated
AddHandler _targetForm.HandleDestroyed, AddressOf Me.OnHandleDestroyed

End Sub

Public Sub OnHandleCreated(ByVal sender As Object, ByVal e As EventArgs)

Me.AssignHandle(_targetForm.Handle)

End Sub

Public Sub OnHandleDestroyed(ByVal sender As Object, ByVal e As EventArgs)

Me.ReleaseHandle()

End Sub

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

If m.Msg = Form1.WM_TEST_MESSAGE Then

Debug.Print("WM_TEST_MESSAGE を横取りしました。")
Exit Sub

End If

MyBase.WndProc(m)

End Sub

End Class

上で作成したMessageStealクラスを検証するためのテストコードです。ボタンを押した際にPostMessageを行い、FormかMessageStealクラスのどちらでメッセージが処理されるか確認します。サブクラス化はウインドウハンドルが生成される前にMessageStealクラスに関連付けをする必要があるので、タイミングとしてはForm_Loadでは間に合いません。コンストラクタの最初で設定を行います。


Imports System.Runtime.InteropServices

Public Class Form1

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

Public Const WM_USER As UInteger = &H400
Public Const WM_TEST_MESSAGE As UInteger = WM_USER + 1

Private _steal As MessageSteal = Nothing

Public Sub New()

_steal = New MessageSteal(Me)

InitializeComponent()

End Sub

Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click

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

End Sub

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

If m.Msg = Form1.WM_TEST_MESSAGE Then

Debug.Print("Form1でメッセージを処理しました。")
Exit Sub

End If

MyBase.WndProc(m)

End Sub

End Class

結果は、以下のようになります。

WM_TEST_MESSAGE を横取りしました。