介绍 有时,您可能会遇到以下问题:嵌入到应用程序中的第三方二进制组件或控件会显示挂起代码的窗口(通常是消息框),直到它们关闭为止。如果你没有这个二进制代码的源代码,又没有一个好的API来通过编程改变自己的不良行为,那么使用它会是一个真正的麻烦。这类二进制代码的一个著名例子是。net中的web浏览器控件。 可能会出现以下类型的问题: 代码中使用的组件会抛出一堆错误消息,让用户艰难地处理这些消息,以使应用程序进一步工作。这可能是最烦人的情况。该组件显示一个对话框,并等待在那里输入数据。 在这两种情况下,您的代码执行被暂停,令人恼火的窗口出现在桌面上,而您可能希望代码平稳地、悄无声息地工作,而不需要人工干预。 要解决这些问题,您必须从您的代码管理这些窗口:关闭它们,或者甚至用数据填充它们并提交。 重要的笔记 在本文中,我们考虑管理嵌入到应用程序中的WebBrowser控件产生的windows。这是一种相对常见的情况。但实际上,本文中考虑的解决方案可以应用于您的进程使用的任何控件或二进制文件。 代码是c#的,但它由通过互操作导入的Win32函数组成。所以实际上解决方案是用c++,如果需要的话,附加的代码可以很容易地转换成c++。 由浏览器打开的对话框 使用WebBrowser控件,您可能会面临以下挑战:由WebBrowser下载的网页包含JavaScript,它会抛出一个错误消息。这就是为什么WebBrowser可以显示消息框的原因之一。当浏览器询问如何处理导航文件时,也会出现一个对话框:打开或保存。浏览器还可以请求凭证或抛出安全警报,等等。 问题是,如果打开了这样一个对话框,WebBrowser对象就无法导航到下一个页面,并且一直处于瘫痪状态,直到对话框关闭。让我们看看如何通过编程来解决它。 解决方案 我们将遵循最丰满和可靠的方式为我们的任务,包括以下步骤: 截取与窗口创建相对应的窗口消息获取必须管理的窗口句柄,关闭窗口或填充并提交它(单击OK) 拦截对话框窗口 我们要做的第一件事是从代码中监控窗口的创建。这可以通过调用回调方法的Win32 API函数SetWindowsHookEx来完成。此函数可以跟踪窗口中的多个事件,包括窗口的创建。为此,我们将使用标志WH_CALLWNDPROCRET调用它,该标志安装一个钩子子程,该钩子子程在目标窗口子程处理完消息后监视消息。 每次当我们的回调函数被调用时,我们只需要过滤消息WM_SHOWWINDOW消息,当窗口即将被隐藏或显示时发送到窗口。请查看下面的代码来实现其余部分: 隐藏,收缩,复制Code
/// <spanclass="code-SummaryComment"><summary></span> /// Intercept creation of window and call a handler /// <spanclass="code-SummaryComment"></summary></span> public class WindowInterceptor { IntPtr hook_id = IntPtr.Zero; Win32.Functions.HookProc cbf; /// <spanclass="code-SummaryComment"><summary></span> /// Delegate to process intercepted window /// <spanclass="code-SummaryComment"></summary></span> /// <spanclass="code-SummaryComment"><paramname="hwnd"></param></span> public delegate void ProcessWindow(IntPtr hwnd); ProcessWindow process_window; IntPtr owner_window = IntPtr.Zero; /// <spanclass="code-SummaryComment"><summary></span> /// Start dialog box interception for the specified owner window /// <spanclass="code-SummaryComment"></summary></span> /// <spanclass="code-SummaryComment"><paramname="owner_window">owner window; if IntPtr.Zero,</span> /// any windows will be intercepted<spanclass="code-SummaryComment"></param></span> /// <spanclass="code-SummaryComment"><paramname="process_window">custom delegate to process intercepted window</span> /// <spanclass="code-SummaryComment"></param></span> public WindowInterceptor(IntPtr owner_window, ProcessWindow process_window) { if (process_window == null) throw new Exception("process_window cannot be null!"); this.process_window = process_window; this.owner_window = owner_window; cbf = new Win32.Functions.HookProc(dlg_box_hook_proc); //notice that win32 callback function must be a global variable within class //to avoid disposing it! hook_id = Win32.Functions.SetWindowsHookEx(Win32.HookType.WH_CALLWNDPROCRET, cbf, IntPtr.Zero, Win32.Functions.GetCurrentThreadId()); } /// <spanclass="code-SummaryComment"><summary></span> /// Stop intercepting. Should be called to calm unmanaged code correctly /// <spanclass="code-SummaryComment"></summary></span> public void Stop() { if (hook_id != IntPtr.Zero) Win32.Functions.UnhookWindowsHookEx(hook_id); hook_id = IntPtr.Zero; } ~WindowInterceptor() { if (hook_id != IntPtr.Zero) Win32.Functions.UnhookWindowsHookEx(hook_id); } private IntPtr dlg_box_hook_proc(int nCode, IntPtr wParam, IntPtr lParam) { if (nCode < 0) return Win32.Functions.CallNextHookEx(hook_id, nCode, wParam, lParam); Win32.CWPRETSTRUCT msg = (Win32.CWPRETSTRUCT)Marshal.PtrToStructure (lParam, typeof(Win32.CWPRETSTRUCT)); //filter out create window events only if (msg.message == (uint)Win32.Messages.WM_SHOWWINDOW) { int h = Win32.Functions.GetWindow(msg.hwnd, Win32.Functions.GW_OWNER); //check if owner is that is specified if (owner_window == IntPtr.Zero || owner_window == new IntPtr(h)) { if (process_window != null) process_window(msg.hwnd); } } return Win32.Functions.CallNextHookEx(hook_id, nCode, wParam, lParam); } }
请注意一件重要的事情:我们只过滤那些发送到特定窗口所拥有的窗口的消息。这是一个必要的技巧,因为我们自己的代码也可以打开窗口,我们不希望它们被拦截和自动关闭。所有由控件(在我们的例子中是WebBrowser)产生的窗口,默认情况下都由它们所在的窗体拥有。因此,window 's owner是一种区分由WebBrowser打开的窗口和由我们的代码打开的其他窗口的方法。注意,要使其工作,我们应该为我们自己的窗口分配另一个所有者。区分不需要的窗口的另一种方法是通过标题文本过滤它们。窗口的标题可以通过Win32函数GetWindowText获得。这也是一个好方法,因为通常你提前知道你的windows将有什么标题。 还记得一个回调函数(在Win32.Functions代码中)。从非托管代码调用的必须是类中的全局变量,以避免处理。 每当被截获窗口的所有者是指定的人时,回调函数就会调用一个委托方法,在该方法中我们定义了如何处理被截获的窗口,并将窗口的句柄传递给它。 按句柄管理窗口 太棒了!获得了窗口的把手后,我们可以用它做我们想做的事情。 关闭对话框 为了演示我们现在拥有的功能,我创建了一个放置WebBrowser的表单和一个使用JavaScript的小HTML页面。下载此表格后,网页浏览器将会进入此网页: 隐藏,复制Code
<HTML> <BODY> HERE IS STUMBLING PAGE... <script> ERROR HERE! </script> <script> alert("Error"); </script> <br>OK NOW! </BODY> </HTML>
它将迫使我们的网络浏览器显示一个警告消息,并暂停网络浏览器,直到警告框关闭。 我们想在代码中关闭它?现在没有问题。为此,我们创建Window拦截器实例,并传递一个窗口处理程序委托给它: 隐藏,复制Code
//form with WebBrowser Form1 form = new Form1(); //Start intercepting all dialog boxes owned by form WindowInterceptor d = new WindowInterceptor(form.Handle, ProcessWindow1); form.Show(); form.Navigate("http://localhost/test1.htm");
这是关闭对话框窗口的窗口处理程序: 隐藏,复制Code
static void ProcessWindow1(IntPtr hwnd) { //close window Win32.Functions.SendMessage(hwnd, (uint)Win32.Messages.WM_CLOSE, 0, 0); }
以上就是如何以简单而优雅的方式关闭web浏览器的全部内容。 提交对话框 现在让我们考虑一个更复杂的情况:当您需要单击被拦截窗口中的某个按钮时。为了演示的目的,我创建了另一个HTML: 隐藏,复制Code
<HTML> <BODY> HERE IS STUMBLING PAGE #2... <script> if(confirm("Do you really want to go here?")) navigate("http://www.google.com"); </script> </BODY> </HTML>
假设我们想在这个确认对话框中单击“OK”。下面的代码可以做到: 隐藏,复制Code
static void ProcessWindow2(IntPtr hwnd) { //looking for button "OK" within the intercepted window IntPtr h = (IntPtr)Win32.Functions.FindWindowEx(hwnd, IntPtr.Zero, "Button", "OK"); if (h != IntPtr.Zero) { //clicking the found button Win32.Functions.SendMessage ((IntPtr)h, (uint)Win32.Messages.WM_LBUTTONDOWN, 0, 0); Win32.Functions.SendMessage ((IntPtr)h, (uint)Win32.Messages.WM_LBUTTONUP, 0, 0); } }
对话框中的填充控件 如果对话框中的某些字段需要填充数据(比如登录名和密码),可以由Win32函数SetWindowText设置。我们可以像找到“OK”按钮一样找到所需控件的手柄。一些控件,如下拉框,没有文本标题,可以帮助我们找到他们。相反,这些控件的句柄可以通过传递给FindWindowEx的类名来找到。但是我们如何找到它们隐藏的类名或标题呢?要简单地做到这一点,使用有用的工具,如VS Spy++或Winspector -它们可以显示关于托管在某个窗口中的控件的所有信息。 这段代码在一个被拦截的窗口中寻找一个编辑框,并用文本填充: 隐藏,复制Code
static void ProcessWindow3(IntPtr hwnd) { //looking for edit box control within intercepted window IntPtr h = (IntPtr)Win32.Functions.FindWindowEx(hwnd, IntPtr.Zero, "Edit", null); if (h != IntPtr.Zero) { //setting text Win32.Functions.SetWindowText((IntPtr)h, "qwerty"); } //looking for button "OK" within the intercepted window IntPtr h = (IntPtr)Win32.Functions.FindWindowEx(hwnd, IntPtr.Zero, "Button", "OK"); if (h != IntPtr.Zero) { //clicking the found button Win32.Functions.SendMessage ((IntPtr)h, (uint)Win32.Messages.WM_LBUTTONDOWN, 0, 0); Win32.Functions.SendMessage ((IntPtr)h, (uint)Win32.Messages.WM_LBUTTONUP, 0, 0); } else { //close window if OK was not found Win32.Functions.SendMessage(hwnd, (uint)Win32.Messages.WM_CLOSE, 0, 0); } } }
一些评论 当然,这些样本仅用于测试目的。在现实生活中,这种方法帮助我完成了两项任务:使WebBrowser处于静默状态,并在登录到站点之前提交凭据对话框,从而使WebBrowser完全自动化。 综上所述,上述情况也可以通过其他方式解决(任何问题都有不止一种解决方案)。有些问题可以通过在下载的页面中嵌入自己编写的JavaScript来解决。有些问题可以通过在注册表中更改Internet Explorer用户设置来解决。通过在我们的代码中使用Internet Explorer API设置凭证,可以避免凭证对话。但这些方法都很狭窄,只适用于特殊情况,而考虑的解决方案不依赖于使用的第三方软件,因此允许编写可靠而简单的代码。顺便说一句,就WebBrowser而言,就我所研究的这个问题而言,即使使用Internet Explorer API(许多复杂的接口),在某些情况下也无法抑制浏览器抛出的对话框。 此外,控件的行为可能因版本而异。作为版本不兼容的情况,您可以提请注意WebBrowser的沉默属性。我想它在几年前的一些ie版本中可以工作,但似乎在WinXP+SP2和ie6下不能工作。因此,对这一特性有信心开发的应用程序在升级电脑上的Internet Explorer时可能会遇到问题。 结论 当您没有民事方法来解决由第三方组件产生的窗口时,上述解决方案是可以成功的。此外,在使用这些组件的API(如果它们有API的话)之前,它可能还有以下优点: 它简单,它是可靠的,它不依赖于不同版本的第三方软件,它保持第三方软件的设置不变 使用的代码 在附上的代码中,你可以找到: 类窗口拦截器,它监视出现的对话框窗口并调用自定义委托方法。这个类可以作为代码中的DLL使用。用JavaScript测试WebBrowser controltest1.html和test2.html项目。不要忘记将它们放到您的www目录中,并在测试项目中指定它们的URL。 是快乐! 本文转载于:http://www.diyabc.com/frontweb/news5061.html