Visual Studio.NETのWebBrowserControlにてピンチ等のジェスチャーを禁止する方法(Visual Studio.NET 2013,Windows 8.1 pro)

2015年1月9日

とある要件にて、Visual Studio.NETにてWebブラウザを作成する必要が出てきました。その際、ピンチ、ダブルタップを禁止する必要があったのですが、これがなかなか難解な問題でした。

通常、ピンチ・ダブルタップ等のジェスチャーはCSSにて禁止させることができるようなのですが、WebBrowserControlではこれができませんでした。そのため、WebBrowserControlのハンドルから辿っていき「internet Explorer_Server」のハンドルを取得し、そこからサブクラス化をして「WM_GESTURE」を無視するようにしました。

以下、ソースの一部です。
※ソースを掲載用に加工しているので、動かなかったらすいません。
※私が実装できた方法なので、より良い方法があれば、コメントに書いて頂けますと嬉しいです。

Public Class FrmMain

    ''' <summary>ウィンドウサブクラス化用のデリゲート</summary>
    Delegate Function WindowProcDelegate(ByVal hwnd As Integer, ByVal uMsg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer

    ''' <summary>ウィンドウハンドル探索用関数</summary>
    Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Integer, ByVal hWnd2 As Integer, ByVal lpsz1 As String, ByVal lpsz2 As String) As Integer

    ''' <summary>サブクラス化に使用する関数</summary>
    Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Integer, ByVal nIndex As Integer, ByVal dwNewLong As WindowProcDelegate) As Integer
    Declare Function SetWindowLong2 Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Integer, ByVal nIndex As Integer, ByVal dwNewLong As Long) As Integer
    Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Integer, ByVal hwnd As Integer, ByVal msg As Integer, ByVal wParam As Integer, lParam As Integer) As Integer

    ''' <summary>ウィンドウプロシージャのアドレス</summary>
    Public m_wndprcNext As Integer

    ''' <summary>初回読み込みフラグ</summary>
    Private isFirst As Boolean = True

    ''' <summary>対象ハンドル</summary>
    Private targethandle As Integer = -1

    ''' <summary>WinProc保持用</summary>
    Private winProc As WindowProcDelegate

    ''' <summary>ウィンドウプロシージャのアドレス取得指定</summary>
    Public Const GWL_WNDPROC = (-4)

    ''' <summary>ジェスチャーのメッセージ</summary>
    Private Const WM_GESTURE = &H119


    ''' <summary>画面表示時の処理</summary>
    Private Sub FrmMain_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        winProc = AddressOf WindowProc
    End Sub

    ''' <summary>WEBブラウザ読み込み時の処理</summary>
    Private Sub WBMain_DocumentCompleted(sender As Object, e As WebBrowserDocumentCompletedEventArgs) Handles WBMain.DocumentCompleted

        '初回のみ実施
        If isFirst Then
            '該当ハンドルの取得
            Dim hwnd1 As IntPtr = FindWindowEx(WBMain.Handle, 0, "Shell Embedding", vbNullString)
            Dim hwnd2 As IntPtr = FindWindowEx(hwnd1, 0, "Shell DocObject View", vbNullString)
            targethandle = FindWindowEx(hwnd2, 0, "internet Explorer_Server", vbNullString)
            'サブクラス化
            If targethandle > 0 Then
                m_wndprcNext = SetWindowLong(targethandle, GWL_WNDPROC, winProc)
            Else
                targethandle = -1
            End If
            isFirst = False
        End If
    End Sub

    ''' <summary>サブクラス化用ファンクション</summary>
    Private Function WindowProc(ByVal hWnd As Integer, ByVal uMsg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer
        Select Case uMsg
            Case WM_GESTURE
                Return 0
        End Select
        WindowProc = CallWindowProc(m_wndprcNext, hWnd, uMsg, wParam, lParam)

    End Function
End Class

簡単に説明をします。

必要なWin32APIの宣言を書きます。ウィンドウハンドルの探索に使用するものや、サブクラス化に必要な関数を用意します。

''' <summary>ウィンドウサブクラス化用のデリゲート</summary>
Delegate Function WindowProcDelegate(ByVal hwnd As Integer, ByVal uMsg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer

''' <summary>ウィンドウハンドル探索用関数</summary>
Declare Function FindWindowEx Lib "user32" Alias "FindWindowExA" (ByVal hWnd1 As Integer, ByVal hWnd2 As Integer, ByVal lpsz1 As String, ByVal lpsz2 As String) As Integer

''' <summary>サブクラス化に使用する関数</summary>
Declare Function SetWindowLong Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Integer, ByVal nIndex As Integer, ByVal dwNewLong As WindowProcDelegate) As Integer
Declare Function SetWindowLong2 Lib "user32" Alias "SetWindowLongA" (ByVal hwnd As Integer, ByVal nIndex As Integer, ByVal dwNewLong As Long) As Integer
Declare Function CallWindowProc Lib "user32" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Integer, ByVal hwnd As Integer, ByVal msg As Integer, ByVal wParam As Integer, lParam As Integer) As Integer

定数として、以下の2つを用意します。
ウィンドウプロシージャの置き換えに必要な定数と、Windowsメッセージのジェスチャーメッセージのコードです。

''' <summary>ウィンドウプロシージャのアドレス取得指定</summary>
Public Const GWL_WNDPROC = (-4)

''' <summary>ジェスチャーのメッセージ</summary>
Private Const WM_GESTURE = &H119

「internet Explorer_Server」のハンドルを取得するには、WebBrowserControlがページを読み込み終わっていないとできないようです。そのため、DocumentCompletedイベントにて処理を行っています。実際このハンドルを取得するのに悩みました。辿る順番がわからなかったのです。っということで、「Winspector」というものを使って確認しました。
※本家につながらなくなっているみたいなので、なんとか探してみてください。「spy++」というソフトでもできそうです。

確認したところ、以下のように辿ることがわかりました。
「WebBrowserControlのハンドル」⇒「Shell Embedding」⇒「Shell DocObject View」⇒「internet Explorer_Server」
「internet Explorer_Server」のハンドルが取得できれば、あとはサブクラスにし、処理を挿入?すればOKです。

サブクラスについては、この方のページが分かりやすいと思います。
Visual Basicの限界を広げるサブクラス化の手法

''' <summary>WEBブラウザ読み込み時の処理</summary>
Private Sub WBMain_DocumentCompleted(sender As Object, e As WebBrowserDocumentCompletedEventArgs) Handles WBMain.DocumentCompleted

    '初回のみ実施
    If isFirst Then
        '該当ハンドラの取得
        Dim hwnd1 As IntPtr = FindWindowEx(WBMain.Handle, 0, "Shell Embedding", vbNullString)
        Dim hwnd2 As IntPtr = FindWindowEx(hwnd1, 0, "Shell DocObject View", vbNullString)
        targethandle = FindWindowEx(hwnd2, 0, "internet Explorer_Server", vbNullString)
        'サブクラス化
        If targethandle > 0 Then
            m_wndprcNext = SetWindowLong(targethandle, GWL_WNDPROC, winProc)
        Else
            targethandle = -1
        End If
        isFirst = False
    End If
End Sub

サブクラスのファンクションはこちらです。
単純にウィンドウメッセージの確認し、ジェスチャーメッセージだった場合に処理をしないようにしています。それ以外の場合は、通常通り処理させるため、「CallWindowProc」をコールしています。

''' <summary>サブクラス化用ファンクション</summary>
Private Function WindowProc(ByVal hWnd As Integer, ByVal uMsg As Integer, ByVal wParam As Integer, ByVal lParam As Integer) As Integer
    Select Case uMsg
        Case WM_GESTURE
            Return 0
    End Select
    WindowProc = CallWindowProc(m_wndprcNext, hWnd, uMsg, wParam, lParam)

End Function

一応、これでジェスチャーアクションを無視するWebBrowserControlにはなりました。
ですが、1点問題が。。。

本当はサブクラス化したものをもとに戻す必要があるのですが、戻し方がわからなかたのですよね。通常はSetWindowLongの返り値をつかって戻すのですが、型が違うのでうまくできませんでした。う〜ん、まだまだ勉強不足ですね。

結構難易度が高いので、もし同じ悩みで苦しんでいる方がいましたら参考にしてみてください。