I recently worked on an application that needed to automate a
website in the background. It fills out some forms, invokes some
buttons and checks if all the navigation works fine.
Needless to say, the user of the application should not even
notice that there is a website in the background.
I needed to use the webbrowser control as the website is a
really fantastic example of how to NOT implement a web site.
Everything the user does causes JavaScripts to execute and it's a
nightmare (if not impossible) to solve this by sending custom POST
requests to the webserver.
So I went with the webbrowser control which works really fine
when used to fill out forms and invoke buttons in context of the
website. It's still a lot of spaghetti code that comes together but
it actually works.
For V2 of the application, i switchted from WinForms to WPF. The
first thing I've learned was that it is really no good idea to use
the WPF webbrowser control as it lacks some features of the
WinForms control. One of them was the
ScriptErrorsSuppressed-Property that prevents
webpages from displaying MessageBoxes (not only caused by
ScriptErros).
The solution is of course to use a WinFormsHost inside your WPF
window and place a WinForms webbrowser control inside of it. But
while you now have access to a much more complete API and can SET
the ScriptErrorsSuppressed property, this property has actually no
effect.
No matter what you are told in some dev forums (for example: it
only works when the application runs in full trust) - it never
works. Never ever. Don't waste your time. I spent hours trying
dozens of different "solutions" to get this working, still each and
every MessageBox popped up and remindet the user that there is some
browser automation happening in the background.
As there is no clean solution on this and everything looks like
Microsoft is not interested in changing this situation, I decided
to go the ugly way with a dirty hack - I use COM Interop to the
Win32 API call GetLastActivePopup that returns the window handle of
the last popup a given window has created. This also works for
MessageBoxes that are created by webbrowser control.
The trick now is to launch a worker thread every time you do
some browsing and let this thread check frequently if your browser
window raises any popups. If so, use another COM call to destroy
this popup immediately.
I have very good results using this technique. That means: it
works reliable and the end-user doesn't even notice that there's
something going one most of the time as the check happens every 10
ms in my configuration. This short interval does not cause any
performance issues or more than regular CPU load during
execution.
Everything you need is here
The good news is: it is possible to use this out-of-the-box and
you can use my code 1:1 in your project.
Firstly, here's the class that contains all the logic. Include
it just like below in your project:
private class PopupHandler
{
#region Interop
[DllImport("user32.dll")]
static extern IntPtr GetLastActivePopup(IntPtr hWnd);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
static extern IntPtr SendMessage(IntPtr hWnd, UInt32 Msg, IntPtr wParam, IntPtr lParam);
static uint WM_CLOSE = 0x10;
#endregion
public IntPtr OriginalWindowHandle { get; private set; }
private bool isCanceled = false;
public PopupHandler(IntPtr originalWindowHandle)
{
this.OriginalWindowHandle = originalWindowHandle;
}
public void Cancel()
{
isCanceled = true;
}
public void Execute(object windowHandle)
{
while (!isCanceled)
{
// Find a possible open IE popups/alert and close it
IntPtr lastPopupHandle = GetLastActivePopup(OriginalWindowHandle);
if (lastPopupHandle.ToString() != "0" & OriginalWindowHandle != lastPopupHandle)
SendMessage(lastPopupHandle, WM_CLOSE, IntPtr.Zero, IntPtr.Zero); // Sends close Message to popup
// Sleep a little to prevent full CPU load
Thread.Sleep(10);
}
}
}
Next, here is an example how to call this class in a
multithreaded way:
// Launch Popup Handler Thread
WindowInteropHelper wih = new WindowInteropHelper(this);
PopupHandler popupHandler = new PopupHandler(wih.Handle);
Thread popupHandlerThread = new Thread(popupHandler.Execute);
popupHandlerThread.Start();
// Now do your navigation
// this.browser.Navigate(@"http://www.google.at");
And that's it. This will do all the work.
Short explanation: this in the above context is the WPF window.
To get the window handle of a WPF window, we need to use the
WindowInteropHelper class. And with wih.Handle we pass the window
handle to the PopupHandler class, and actually this information is
all the PopupHandler class needs to do its job.
Just one important notice: a WPF window does not get a
window handle until it was showed at least one time. So you *will*
have to call myWindow.Show() at least once before using this class.
It's ok if you call myWindow.Hide() immediately after that.
Consider setting the default window state to minimized to prevent
the user seeing some flickering on the screen.
What is left for you to do? Of course, you want to stop this
thread after you have completed browsing. The PopupHandler class
has a handy Cancel-Method for that. Just call it when you are
finished.
I hope this helps some fellow developers to save many valuable
hours of their lives :)
Of course if anyone out there can come up with a cleaner
solution, please let me know!