The remainder of this article provides a fix to receive otherwise hidden events such
as the
famous OnBeforeNavigate2 from MS Internet Explorer.
Reusing the code
Namely, if you wish to use the Internet Explorer control in a .NET app, what you usually do is
go in the Toolbox Window, Customize, search for the Microsoft Web Browser Control (shdocvw.dll),
drop it on a form, and start exploring the object model.
That is simple, but does not work as expected. You never get notified of several events.
If you are not interesting in the programming details, just reuse the source code. Don't be
afraid of the size (54kb), it is somewhat big because there are two interop assemblies,
but the real addition is just 10 lines of code. The project was obtained by doing the
following:
- dropped the Web Browser control on a form named Form1
- added the code snippet to fix the Web Browser control (see below)
- added a text box to manage URL typing
- added an event handler on the TextBox to catch keyboard returns and ask
IE
to do a simple Navigate(url) on it.
Explanations
There is a problem when you IE in a .NET environment. Due to a .NET 1.0 limitation, marshaling cannot handle complex
variant types, which are at
the core of most events triggered by the DWebBrowserEvents2 dispatch interface.
Unfortunately, the OnBeforeNavigate2 event is triggered through this interface. This
event is often used by programmers to be notified any time the user clicked on a link or
submitted a form, providing them with very valuable information including
URL, posted data,
headers, and even the ability to cancel the navigation depending on the application logic.
Now we know that we can't use this event, as is.
But, by carefully watching the core Internet Explorer interfaces
(by using OLEView on shdocvw.dll, or by looking at the redistributed
IDL
interface located in Platform SDK\Include\ExDisp.idl) we can see the
DWebBrowserEvents interface (older but backward supported) provides events
such as OnBeforeNavigate (note the missing 2).
Here is a extract of these interfaces in IDL:
[
uuid(8856F961-340A-11D0-A96B-00C04FD705A2),
helpstring("WebBrowser Control"),
control
]
coclass WebBrowser
{
[default] interface IWebBrowser2; interface IWebBrowser;
[default, source] dispinterface DWebBrowserEvents2;
[source] dispinterface DWebBrowserEvents;
};
[
uuid(34A715A0-6587-11D0-924A-0020AFC7AC4D),
helpstring("Web Browser Control events interface"),
hidden
]
dispinterface DWebBrowserEvents2
{
properties:
methods:
[id(0x000000fa)]
void BeforeNavigate2(
[in] IDispatch* pDisp,
[in] VARIANT* URL,
[in] VARIANT* Flags,
[in] VARIANT* TargetFrameName,
[in] VARIANT* PostData,
[in] VARIANT* Headers,
[in, out] VARIANT_BOOL* Cancel);
...
}
[
uuid(EAB22AC2-30C1-11CF-A7EB-0000C05BAE0B),
helpstring("Web Browser Control Events (old)"),
hidden
]
dispinterface DWebBrowserEvents {
properties:
methods:
[id(0x00000064)]
void BeforeNavigate(
[in] BSTR URL,
long Flags,
BSTR TargetFrameName,
VARIANT* PostData,
BSTR Headers,
[in, out] VARIANT_BOOL* Cancel);
...
}
The important thing to note is that the IDL defines DWebBrowserEvents2 as the
default event source, not DWebBrowserEvents. Because of that, the interop wrapper
generator
(tlbimp.exe) will provide us with marshaling code reflecting just that, namely
AxInterop.SHDocVw.dll (ActiveX layer) and Interop.SHDocVw.dll
(shdocvw.dll wrapper). As a result, if you type axWebBrowser1. (notice the dot),
then intellisense will show you methods from this interface, not from
DWebBrowserEvents. Casting is of no help here : the compiler would be ok, but it
would fail at run-time. Looks like we are a bit stuck here.
To go on, we are actually going to ask the interop marshaler to produce at run-time a wrapper
for the DWebBrowserEvents interface. Let's show some code now:
public class Form1 : System.Windows.Forms.Form
{
private AxSHDocVw.AxWebBrowser axWebBrowser1;
private SHDocVw.WebBrowserClass ie_events;
private System.Windows.Forms.TextBox textBox1;
public Form1()
{
InitializeComponent();
ie_events = (SHDocVw.WebBrowserClass)
Marshal.CreateWrapperOfType(
axWebBrowser1.GetOcx(),
typeof(SHDocVw.WebBrowserClass)
);
...
}
}
The CreateWrapperOfType call performs the magic of creating an RCW (layer to execute
COM interfaces and methods) for us. Instead of passing the
SHDocVw.DWebBrowserEvents interface type we want, we pass the
SHDocVw.WebBrowserClass instead. Why ? That's a trick again, the marshaler expects
a coclass type to build the RCW, instead of a simple interface. WebBrowserClass is
the .NET name of coclass WebBrowser declared in the IDL.
The resulting RCW is stored in a member of our Form. Now we have the right interface to play
with. By virtue of the IDL COM declaration, if we use intellisense on ie_events,
we are going to see both interface's methods and events. And there we have
BeforeNavigate.
We are done, let's show how we use this event to get the actual notification. In .NET, we just
create a delegate, and attach an event handler to it:
public Form1()
{
InitializeComponent();
ie_events = (SHDocVw.WebBrowserClass) Marshal.CreateWrapperOfType(
axWebBrowser1.GetOcx(),
typeof(SHDocVw.WebBrowserClass)
);
SHDocVw.DWebBrowserEvents_BeforeNavigateEventHandler BeforeNavigateE =
new SHDocVw.DWebBrowserEvents_BeforeNavigateEventHandler(
OnBeforeNavigate
);
ie_events.BeforeNavigate += BeforeNavigateE;
...
}
public void OnBeforeNavigate(string url,
int flags,
string targetFrame,
ref object postData,
string headers,
ref bool Cancel)
{
int c = 0; }
A demo app
Just to see something happen on screen, we immediately ask the web browser to show CodeProject
(face of relief...):
textBox1.Text = "http://www.codeproject.com";
OnNewUrl(null,null);
private void OnNewUrl(object sender, KeyEventArgs e)
{
object o = null;
if (e==null || e.KeyCode==Keys.Enter)
axWebBrowser1.Navigate(textBox1.Text, ref o, ref o,
ref o, ref o);
}
Eh voilà.
Stephane Rodriguez - Oct 28 2002.
History
-
October 28, 2002 - Initial Posting