とある要件にて、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の返り値をつかって戻すのですが、型が違うのでうまくできませんでした。う〜ん、まだまだ勉強不足ですね。
結構難易度が高いので、もし同じ悩みで苦しんでいる方がいましたら参考にしてみてください。