Click here to Skip to main content
Email Password   helpLost your password?
Sample Image - maximum width is 600 pixels

Introduction

Sometimes you may collide with the following problem: a third-party binary component or control embedded into your application displays windows (usually message boxes) that hang your code until they are closed.
If you do not have the source code of this binary and one does not have a good API to change one’s bad behaviour programmatically, it can be a real headache using the one. A famous example of such a binary is the WebBrowser control in .NET.

The following types of problems can occur:

In both these cases, your code execution is suspended and exasperating windows appear on the desktop while you may want your code to work smoothly and silently without stumbling and the need of human intervention.

To solve these problems, you have to manage such windows from your code: close them, or even fill them with data and submit.

Important Notes

In this article, we consider managing windows originated by the WebBrowser control embedded into your application. It is a relatively common case. But actually the solution considered in this article can be applied to any control or binary used by your process.

The code is C#, but it consists of Win32 functions imported via interop. So actually the solution is in C++ and the attached code can be easily translated to C++ if it is needed.

Dialogs Opened by WebBrowser

Using WebBrowser control, you may face the following challenge: Web page downloaded by WebBrowser contains JavaScript which throws an error message. It is one of the reasons why WebBrowser can show a message box. A dialog box may also appear when the browser asks what to do with the navigated file: open or save. The browser can also ask for credentials or throw a security alert, etc.

The problem is that if such a dialog box is opened, WebBrowser object cannot be navigated to the next page and remains paralysed until the dialog is closed. Let’s see how it can be solved programmatically.

Solution

We'll follow a most plump and reliable way for our task that consists of the following steps:

Intercept Dialog Windows

The first thing that we have to do is monitor window creation from our code.
This can be done through Win32 API function SetWindowsHookEx that will invoke our callback method. This function can trace several events in Windows including creation of a window. To do it, we'll invoke it with flag WH_CALLWNDPROCRET that installs a hook procedure that monitors messages after they have been processed by the destination window procedure.

Each time as our callback function is invoked, we only have to filter message WM_SHOWWINDOW message that is sent to a window when the window is about to be hidden or shown.
Please look at the code below to realize the rest:

/// <summary>
/// Intercept creation of window and call a handler
/// </summary>
public class WindowInterceptor
{
    IntPtr hook_id = IntPtr.Zero;

    Win32.Functions.HookProc cbf;

    /// <summary>
    /// Delegate to process intercepted window
    /// </summary>
    /// <param name="hwnd"></param>
    public delegate void ProcessWindow(IntPtr hwnd);
    ProcessWindow process_window;

    IntPtr owner_window = IntPtr.Zero;

    /// <summary>
    /// Start dialog box interception for the specified owner window
    /// </summary>
    /// <param name="owner_window">owner window; if IntPtr.Zero,
    /// any windows will be intercepted</param>
    /// <param name="process_window">custom delegate to process intercepted window
    /// </param>
    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());
    }

    /// <summary>
    /// Stop intercepting. Should be called to calm unmanaged code correctly
    /// </summary>
    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);
    }
}

Pay attention to one important thing: we filter only those messages that are addressed to windows owned by certain window. It is a trick needed since our own code can open windows as well and we do not want them to be intercepted and closed automatically. All windows originated by a control (in our case WebBrowser) will be owned by default by the form where they are placed. Thus window’s owner is a way to distinguish windows opened by WebBrowser from the rest of the windows opened by our code. Notice, to have this working, we should assign another owner to our own windows.
Another way to distinguish the unneeded windows from yours is filtering them by their caption text. Caption of a window can be obtained by Win32 function GetWindowText. It is also a good way since usually you know in advance what captions your windows will have.

Also remember a callback function (in the code Win32.Functions.HookProc cbf) invoked from unmanaged code must be a global variable within the class to avoid disposing.

Each time as the owner of the intercepted window is the specified one, the callback function invokes a delegate method where we defined how to treat the intercepted window, and passes the window’s handle to it.

Manage Window by Handle

Great! Having obtained a handle of the window, we can do what we want with it.

Close Dialog

To demo the ability we have now, I created a form where WebBrowser is placed and also a small HTML page with JavaScript. Upon loading this form, WebBrowser is navigated to this page:

<HTML>
<BODY>

HERE IS STUMBLING PAGE...

<script>
ERROR HERE!
</script>

<script>
alert("Error");
</script>

<br>OK NOW!

</BODY>
</HTML>

It will force our WebBrowser to display an alert message and suspend WebBrowser until the alert box is closed.

We want to close it from our code? No problem now. To do it, we create WindowInterceptor instance and pass a window handler delegate to it:

//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");

This is the window handler that will close a dialog window:

static void ProcessWindow1(IntPtr hwnd)
{
    //close window
    Win32.Functions.SendMessage(hwnd, (uint)Win32.Messages.WM_CLOSE, 0, 0);
}

That’s all about how to shut up WebBrowser in a simple and elegant manner.

Submit Dialog

Now let’s consider a more complex case: when you need to click a certain button in the intercepted window. For demo purposes, I created another HTML:

<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>

Assume we want to click “OK” in this confirm dialog. The code below does it:

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);
    }
}

Fill Controls Within Dialog

If certain fields within a dialog need to be filled with data (say login and password), they can be set by Win32 function SetWindowText. Handles of needed controls can be found in the same manner as we found the button “OK”.
Some controls like dropdown box do not have a text caption that could help us to find them. Instead of it, handles of such controls can be found by its class names passed to FindWindowEx.
But how do we find out their class names or captions that are hidden? To do it easily, use helpful tools like VS Spy++ or Winspector – they can display all information about controls hosted within a certain window.

This code looks for an edit box within an intercepted window and fills it with text:

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); }
    }
}

Some Remarks

Sure, these samples are done for test purposes only. In real life, this approach helped me to play around two tasks: making WebBrowser silent and submitting credential dialog that appears before logging into a site so that WebBrowser was quite automated.

To be complete, the cases considered above could also be solved in other ways (any problem has more than one solution). Some of them can be solved by embedding self-written JavaScript into a downloaded page. Some of them can be solved by changing Internet Explorer user settings in registry. Credential dialog can be avoided by setting credentials using Internet Explorer API within our code, etc. But these ways are narrow and serve only special cases while the considered solution does not depend on third-party software used and so allows writing a reliable and simple code. BTW, in view of WebBrowser, as far as I investigated this problem, even using Internet Explorer API (a number of intricate interfaces) there is no way to suppress dialog boxes that the browser can throw in some cases.

Moreover, the behaviour of a control may be different from version to version. As a case of version incompatibility, you may draw attention to WebBrowser’s property Silent. I suppose it worked years ago in some Internet Explorer versions but seems not to be working under WinXP+SP2 with Internet Explorer 6. As a result, the app developed with confidence in this property may face a problem when Internet Explorer on the machine is upgraded.

Conclusion

The described solution can be successful when you have no civil way to settle windows originated by third-party components. In addition, it may have the following advantages before using the API of such components (if they have an API at all):

Using the Code

In the attached code, you can find:

Be happy!

You must Sign In to use this message board.
 
 
Per page   
 FirstPrevNext
GeneralGreat job
Deufox
5:38 6 Oct '08  
This is a great article.

A suggestion - to simulate the clicking of the alerts buttons, why not use:

Win32.Functions.SendMessage((IntPtr)h, (uint)Win32.Messages.BM_CLICK, 0, 0);


Regards
-Malcolm
GeneralRe: Great job
Sergey Stoyan
23:40 6 Oct '08  
Probably it can be used. I do not remember exactly why I used WM_LBUTTONDOWN + WM_LBUTTONUP instead of BM_CLICK - it seems to have no effect in certain cases.
QuestionA question about webbrowser
heshaoyue
5:03 21 Aug '08  
How can I make my browser control loads local files(http://127.0.0.1/index.php)? I tried Navigate but it asked for internet access (per my firewall). Is there anyway to skip it?
QuestionHow can I find the application that sent any 'Windows Message' to my form window?
poipoeirwerw
15:04 5 Aug '08  
I have a .NET form and I would like to know the source of any windows message (WM_XXXXX) that might be posted to my form by other running applications - is there a way to do this?

Thanks
GeneralCan we get content of message box?
chie78
8:12 2 Jul '08  
Hi,

If the pop-up is a message box (i.e, jsscript/javascript alert), is there any way to get the content of the message box?

Thanks so much.
GeneralRe: Can we get content of message box?
Sergey Stoyan
7:30 4 Jul '08  
Hi,

Yes, you can find handle of the label by FindWindowEx and then get its text by GetWindowText
GeneralRe: Can we get content of message box?
chie78
8:34 4 Jul '08  
Hi,

Thanks so much for your reply.

I wasn't able to get it right...are you able to show me some codes? Appreciate it. Thanks!!
GeneralRe: Can we get content of message box?
Sergey Stoyan
22:19 10 Jul '08  
what is your problem exactly?
GeneralRe: Can we get content of message box?
chie78
4:26 11 Jul '08  
Hi,

I wasn't able to get the content of the message box.

Are you able to show me some code samples on how to get it?

Thanks so much.
Questionhow to use this code to click "Yes" button in case of IE script error dialog
jaideeptelang
6:30 29 Apr '08  
Hello Sergey,

This is really cool and i have been looking for this for a while. i have one question.
in your article there is one jpeg image displaying the IE Script error dialog.i want to find the handle on either Yes or No buttons.i am able to get main window handle though.can you let me know using your code how do i achieve this?

thanks in advance
Jaideep
GeneralRe: how to use this code to click "Yes" button in case of IE script error dialog
Sergey Stoyan
6:55 29 Apr '08  
Hi,

You can find in the article's sources the following code:

//looking for button "OK" within the intercepted window
IntPtr h = (IntPtr)Win32.Functions.FindWindowEx(hwnd, IntPtr.Zero, "Button", "OK");

h is the handle of the button "OK". I think it is what you need.
GeneralRe: how to use this code to click "Yes" button in case of IE script error dialog
jaideeptelang
7:02 8 May '08  
actually i want to kill "Internet Explorer Script Error" dialog which IE some times pops up.
i can get the handle of mani wnidow but i am not able to get handle of "&Yes" or "&No" buttons.
so using your code :
//looking for button "&Yes" within the intercepted window
IntPtr h = (IntPtr)Win32.Functions.FindWindowEx(hwnd, IntPtr.Zero, "Button", ""&Yes");

it is giving me 0 as handle.
even if i search this window using Spy++, it is not displaying those buttons.

i hope you will now understand my problem.

Thanks
Jaideep
GeneralRe: how to use this code to click "Yes" button in case of IE script error dialog
Sergey Stoyan
8:01 8 May '08  
why you cannot simply close it?

static void ProcessWindow1(IntPtr hwnd)
{
//close window
Win32.Functions.SendMessage(hwnd, (uint)Win32.Messages.WM_CLOSE, 0, 0);
}
GeneralRe: how to use this code to click "Yes" button in case of IE script error dialog
jaideeptelang
4:18 9 May '08  
my aplication is web automation one and i am getting "Internet Explorer Script Error" at couple of places and i am closing some of them by simply closing them, however at ceratin places if we close it then rest of the progarm of automation did not go further.it does move further if i manually click "Yes" or "No" button.
but as i told you earlier, "Yes" or "No" button do not have handles associated with them when we see this window in Spy++.

Please advice.

Thanks
Jaideep
GeneralRe: how to use this code to click "Yes" button in case of IE script error dialog
Sergey Stoyan
10:23 9 May '08  
I'm not sure why you cannot find them. Some thought: these buttons actually may be hosted by another control (even another window), not directly by the message dialog. But FindWindowEx should find them though... Try to seek recursively:
child=null; while(child=FindWindowEx(hwnd, child, ‘Button', null)){…} and see what controls are enumerated. Try also to set null instead of ‘Button‘. Also try to find them by Winspector.
If you’ll get a solution please write here.
GeneralRe: how to use this code to click "Yes" button in case of IE script error dialog
BabyMouse
2:00 18 Jun '08  
If IE Option not check [ ]Disable Script Debugging, Error Dialog still show! Why?


Last Updated 5 Mar 2008 | Advertise | Privacy | Terms of Use | Copyright © CodeProject, 1999-2010