WebBrowser彈出窗口之(二)––showModalDialog( ) & showModel

2019-05-23 行業動態 haoservcie

showModalDialog并不創建新的瀏覽器窗口,也不創建新的瀏覽器對象,而是在WebBrowser的同一個線程中創建的窗口,而showModelessDialog( )則是在新的線程中創建的窗口,所以處理方式不相同。

當showModalDialog( )被調用后,瀏覽器線程會創建一個對話框,該對話框包含兩個窗口,父窗口的類為“Internet Explorer_TridentDlgFrame”,子窗口的類為“Internet Explorer_Server”,其子窗口即為IE內核的窗口,可以通過給該窗口發送消息,進行一些自動化操作(如按鍵、鼠標點擊等)。當子窗口創建時,父窗口會收到WM_PARENTNOTIFY消息,hwnd值即為父窗口的值,wParam的值即為Internet Explorer_Server的窗口。我們捕獲窗口后就可以捕捉到IE內核窗口的句柄了。
STEP 1: 建立窗口事件的鉤子函數
// 使用Windows API函數

[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr SetWindowsHookEx(HookType hooktype, HookProcedureDelegate callback, IntPtr hMod, UInt32 dwThreadId);

// Hook Types
public enum HookType
{
    WH_JOURNALRECORD = 0,
    WH_JOURNALPLAYBACK = 1,
    WH_KEYBOARD = 2,
    WH_GETMESSAGE = 3,
    WH_CALLWNDPROC = 4,
    WH_CBT = 5,
    WH_SYSMSGFILTER = 6,
    WH_MOUSE = 7,
    WH_HARDWARE = 8,
    WH_DEBUG = 9,
    WH_SHELL = 10,
    WH_FOREGROUNDIDLE = 11,
    WH_CALLWNDPROCRET = 12,
    WH_KEYBOARD_LL = 13,
    WH_MOUSE_LL = 14
}

[StructLayout(LayoutKind.Sequential)]
        public struct CWPRETSTRUCT
        {
            public IntPtr lResult;
            public IntPtr lParam;
            public IntPtr wParam;
            public UInt32 message;
            public IntPtr hwnd;
        };

        // Delegate for the EnumChildWindows method
        private delegate Boolean EnumerateWindowDelegate(IntPtr pHwnd, IntPtr pParam);

private static Win32API.HookProcedureDelegate _WH_CALLWNDPROCRET_PROC =
    new Win32API.HookProcedureDelegate(WH_CALLWNDPROCRET_PROC);

// 在程序開始處調用該方法
public static void Hook( )
{
    if (_pWH_CALLWNDPROCRET == IntPtr.Zero) {
        _pWH_CALLWNDPROCRET = Win32API.SetWindowsHookEx(
            Win32API.HookType.WH_CALLWNDPROCRET
            ,_WH_CALLWNDPROCRET_PROC
            ,IntPtr.Zero
            ,(uint)AppDomain.GetCurrentThreadId( ));    // current thread
    }
    if (_pWH_CALLWNDPROCRET == IntPtr.Zero)
        throw new ApplicationException("Failed to install window hook via: SetWindowsHookEx( )");
}

// 自定義的消息鉤子函數,在函數中捕捉PARENTNOTIFY消息,然后再檢查該消息是否窗口創建或銷毀窗口
// 然后創建一個個事件WindowLife,在WebBrowser中添加該事件的處理函數即可
public delegate void WindowLifeHandler(Win32Message msg, IntPtr parent, IntPtr child);
public static event WindowLifeHandler WindowLife;
private static Int32 WH_CALLWNDPROCRET_PROC(Int32 iCode, IntPtr pWParam, IntPtr pLParam)
{
    if (iCode < 0)
        return Win32API.CallNextHookEx(_pWH_CALLWNDPROCRET, iCode, pWParam, pLParam);

    Win32API.CWPRETSTRUCT cwp = (Win32API.CWPRETSTRUCT)Marshal.PtrToStructure(pLParam, typeof(Win32API.CWPRETSTRUCT));
    Win32Message msg = Win32Message.WM_NULL;
    try {
        msg = (Win32Message)cwp.message;
    } catch {
        return Win32API.CallNextHookEx(_pWH_CALLWNDPROCRET, iCode, pWParam, pLParam); ;
    }
    if (msg == Win32Message.WM_PARENTNOTIFY) {
        if ((int)cwp.wParam == (int)Win32Message.WM_CREATE || (int)cwp.wParam == (int)Win32Message.WM_DESTROY) {
            System.Diagnostics.Debug.WriteLine("WM_PARENTNOTIFY hwnd=0x{0:x8}  wParam={1} lParam=0x{2:x8}"
                ,(int)cwp.hwnd, (Win32Message)cwp.wParam,(int)cwp.lParam);
            if (WindowLife != null)
                WindowLife((Win32Message)cwp.wParam, cwp.hwnd, cwp.lParam);
        }
    }
    return Win32API.CallNextHookEx(_pWH_CALLWNDPROCRET, iCode, pWParam, pLParam);
}

STEP 2:繼承WebBrowser并在派生類中添加WindowLife事件的處理函數,在派生類的構造函數中增加如下代碼:
public class ExWebBrowser : System.Windows.Forms.WebBrowser
{
     public ExWebBrowser( )
        {
            _windowLifeDelegate = new WindowsMessageHooker.WindowLifeHandler(OnWindowLife);
            WindowsMessageHooker.WindowLife += _windowLifeDelegate;
            this.Disposed += (sender, e) => {
                WindowsMessageHooker.WindowLife -= _windowLifeDelegate;
            };

private IntPtr _webPageDialogHandle = IntPtr.Zero;
private static readonly string IE_WebDialogClassName = "Internet Explorer_TridentDlgFrame";
private static readonly string IE_ServerClassName = "Internet Explorer_Server";

public delegate void WebPageDialogHandler(IntPtr hwnd, string title);
// 創建網頁對話框時觸發的事件
public event WebPageDialogHandler ShowWebDialog;
// 關閉網頁對話框時觸發的事件
public event WebPageDialogHandler CloseWebDialog;
private WindowsMessageHooker.WindowLifeHandler _windowLifeDelegate = null;

private void OnWindowLife(noock.windows.Win32Message msg, IntPtr parent, IntPtr child)
{
    StringBuilder buffer = null;
    string childClass = null;
    string parentClass = null;

        buffer = new StringBuilder(256);
    if (child != _webPageDialogHandle) {
        noock.windows.Win32API.GetClassName(child, buffer, buffer.Capacity);
        childClass = buffer.ToString( );
        System.Diagnostics.Debug.WriteLine("child class:" + childClass);
        if (childClass != IE_ServerClassName)
            return;
        noock.windows.Win32API.GetClassName(parent, buffer, buffer.Capacity);
        parentClass = buffer.ToString( );
        System.Diagnostics.Debug.WriteLine("parent class:" + parentClass);
        if (parentClass != IE_WebDialogClassName)
            return;
    }

    noock.windows.Win32API.GetWindowText(parent, buffer, buffer.Capacity);
    string title = buffer.ToString();

    if (msg == noock.windows.Win32Message.WM_CREATE) {
        _webPageDialogHandle = child;
        System.Diagnostics.Debug.WriteLine(title, "showModalDialog( ) Opening:");
        if (ShowWebDialog != null) {
            ShowWebDialog(_webPageDialogHandle, title);
        }
    } else if (msg == noock.windows.Win32Message.WM_DESTROY) {
        _webPageDialogHandle = IntPtr.Zero;
        System.Diagnostics.Debug.WriteLine(title, "showModalDialog( ) Closing:");
        if (CloseWebDialog != null) {
            CloseWebDialog(child, title);
        }
    }
}
        }

這樣就擴展了WebBrowser的事件,可以觸發showModalDialog( )彈出對話框的彈出事件。
STEP3: 因為函數的鉤子是使用API實現,是系統級的,所有必須在程序退出時釋放鉤子函數占用的資源

public static bool Hooked
{
    get { return _pWH_CALLWNDPROCRET != IntPtr.Zero; }
}
public static void Unhook( )
{
    if ( ! Hooked) {
        Win32API.UnhookWindowsHookEx(_pWH_CALLWNDPROCRET);
    }
}

但是,還有一個問題,這樣只能捕捉到showModalDialog( )彈出的對話框,而不能捕捉到showModelessDialog( )彈出的非模態對話框,因為鉤子函數在上面的代碼中只捕捉主線程的消息,而非模態對話框則是單獨的線程。遺憾的是SetWindowsHookEx( )不支持面向進程的鉤子函數,除了面向線程就是面向全局的,捕捉整個桌面(一般相當于整個用戶)的所有消息,雖然這樣做也可以捕捉到相應的事件,但顯然效率是比較低的。而且,非模態對話框在實際應用中并不多見。

我們還可以通過一個折衷的方法,使用API來搜索WebBrowser窗口關系樹中附近的窗口,看有沒有其所有者是WebBrowser父窗口的網頁對話框,例如代碼:
public IntPtr WebPageDialogHandle
        {
            get
            {
                InvokeMethod invoker = new InvokeMethod(( ) =>
                {
                    if (_webPageDialogHandle != IntPtr.Zero && Win32API.IsWindow(_webPageDialogHandle)) {
                    } else {
                        _webPageDialogHandle = SearchWebDialog(ParentForm.Handle, Win32API.GW_HWNDPREV);
                        if (_webPageDialogHandle == IntPtr.Zero)
                            _webPageDialogHandle = SearchWebDialog(ParentForm.Handle, Win32API.GW_HWNDNEXT);
                    }
                }
                );
                this.Invoke(invoker);
                return _webPageDialogHandle;
            }
        }

private IntPtr SearchWebDialog(IntPtr start, uint direction)
    {
        int processId, nextProcId;
        int threadId = Win32API.GetWindowThreadProcessId(ParentForm.Handle, out processId);
        StringBuilder sb = new StringBuilder(256);

        IntPtr nextWin = Win32API.GetNextWindow(ParentForm.Handle, direction);
        int nextTh = Win32API.GetWindowThreadProcessId(nextWin, out nextProcId);
        while (nextProcId == processId) {
            if (ParentForm.Handle == Win32API.GetParent(nextWin)) {
                Win32API.GetClassName(nextWin, sb, sb.Capacity);
                if (sb.ToString() == IE_WebDialogClassName) {
                    _webPageDialogHandle = Win32API.FindWindowExxx(nextWin, IntPtr.Zero, IE_ServerClassName, null);
                    return _webPageDialogHandle;
                }
            }
            nextWin = Win32API.GetNextWindow(nextWin, direction);
            nextTh = Win32API.GetWindowThreadProcessId(nextWin, out processId);
        }
        return IntPtr.Zero;
    }

public static IntPtr FindWindowExxx(IntPtr parentHandle, IntPtr childAfter, string lclassName, string windowTitle)
        {
            IntPtr res = FindWindowEx(parentHandle, childAfter, lclassName, windowTitle);

            if (res != IntPtr.Zero)
                return res;
            while ( (res = FindWindowEx(parentHandle, res, null, null)) != IntPtr.Zero) {
                IntPtr aim = FindWindowExxx(res, IntPtr.Zero, lclassName, windowTitle);
                if (aim != IntPtr.Zero)
                    return aim;
            }
            return IntPtr.Zero;
        }
其實窗口的句柄只是內核對象中的一個地址,同一個進程的窗口句柄一般也是連續,正如代碼中所示,我們不需要搜索所有的窗口,而只需要進進程ID為邊界在WebBrowser父窗口的附近搜索。非模態對話框的父窗口夠本是桌面窗口,而不是WebBrowser所在的窗口,所以在SearchWebDialog函數中調用了GetParent函數,而沒有使用GetAncestor,因為前者返回的不一定是父窗口,也可能是所有者,這正是非模態對話框與WebBrowser窗口的關系,瀏覽器窗口是模態對話框的父窗口,而只是非模態框的所有者,而不是其父窗口。
獲取非模態對話框的句柄以后,就隨便你對它做什么了,發送消息模擬按鍵、模擬鼠標點擊、關閉等,都是可以的了。

 

補上Win32API類的部分代碼

// Delegate for a WH_ hook procedure
public delegate Int32 HookProcedureDelegate(Int32 iCode, IntPtr pWParam, IntPtr pLParam);

[DllImport("user32.dll")]
public static extern IntPtr GetWindow(IntPtr hWnd, uint uCmd);


/// <summary>
/// Retrieves the identifier of the thread that created the specified window and, optionally,
/// the identifier of the process that created the window.
/// </summary>
/// <param name="handle"></param>
/// <param name="processId">if not null, fill the process id</param>
/// <returns>thread id</returns>
[DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)]
public static extern int GetWindowThreadProcessId(IntPtr handle, out int processId);

[DllImport("user32.dll", CharSet = CharSet.Auto, ExactSpelling = true)]
public static extern IntPtr GetParent(IntPtr hWnd);

 

[DllImport("user32.dll", SetLastError = true, EntryPoint="GetWindow")]
public static extern IntPtr GetNextWindow(IntPtr hWnd, uint Command);
---------------------
作者:noock
來源:CSDN
原文:https://blog.csdn.net/Nocky/article/details/6103875
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!

聯系客服

010-58436659

天天捕鱼电玩城正版赢话费
彩城3218计划网 欢乐生肖彩票走势图 极速赛车是什么东西 北京pk10安卓下载 时时彩全天计划 港澳3肖6码 竞彩足球比分即时比分 为什么lg赛车一下大就输 快三怎么推算和值大小单双 百赢棋牌真人赌博下载 1000块时时彩倍投方式 北京pk是最稳全天计划助 老时时彩历史开奖记录 一分快三大小单双技巧 牛牛赚钱1元永久提现 足彩串中奖规则