Introduction
This is my first article here at CodeProject so please excuse the format or
anything that you are used to with the myriads of excellent posts on this site.
This article has been put together to fill a void that I found when trying to
use the Internet Explorer object. I have recently undertaken a project at work
that required me to embed a browser object into an application and display
reports through Microsoft Reporting Services.
There was a need to stop the user from making use of the internet through the
software, but also to stop them from looking at the underlying code as well as
giving them extra options on the context menu.
Background
The Internet Explorer is an odd control. Essentially if you use Spy++
you can see that the object has children that protect the object from people
picking off the handle to the real Internet Explorer easily. There
was a need to iterate through the browser object looking for the child object
called Internet Explorer_Server
.
Much of the child search is done in the API Call 'EnumChildWindow
'. The
snippet of code makes use of some API calls to capture the correct handle for
the Internet Explorer and then override the Context Menu and replace it with
one that I wanted the user to see.
Win32 API in C# & Delegates
private delegate Int32 EnumChildProc(IntPtr hWnd, IntPtr lParam);
private delegate int Win32WndProc(IntPtr hWnd, int Msg,
int wParam, int lParam);
[DllImport("user32")]
public static extern int GetClassName(int hwnd,
StringBuilder lpClassName, int nMaxCount);
[DllImport("user32")]
public static extern int EnumChildWindows(int hwndParent,
Delegate lpEnumFunc, int lParam);
[DllImport("user32")]
private static extern IntPtr SetWindowLong(IntPtr hWnd,
int nIndex, Win32WndProc newProc);
[DllImport("user32")]
private static extern int CallWindowProc(IntPtr lpPrevWndFunc,
IntPtr hWnd, int Msg, int wParam, int lParam);
[DllImport("user32")]
private static extern IntPtr SetWindowLong(IntPtr hWnd,
int nIndex, IntPtr newProc);
[DllImport("user32")]
public static extern int GetCursorPos(ref Point lpPoint);
[DllImport("user32")]
public static extern int ScreenToClient(int hwnd, ref Point lpPoint);
To access these external API functions we need the namespace System.Runtime.InteropServices
.
This namespace provides a collection of classes useful for accessing COM
objects, and native APIs. Shown below is the way the code is written in
C#:
[DllImport("user32")]
public static extern int EnumChildWindows(int hwndParent,
Delegate lpEnumFunc, int lParam);
The
extern
keyword indicates that the
method is implemented externally.
extern
methods must be declared as
static
.
Within this API call you may notice the Delegate reference.
private delegate Int32 EnumChildProc(IntPtr hWnd, IntPtr lParam);
The delegate needs to be created in the same way that we create a class, shown
below.
EnumChildProc myEnumChildProc = new EnumChildProc(EnumChild);
EnumChildWindows(hWnd.ToInt32(), myEnumChildProc, hWnd.ToInt32());
The first line shows the creation of the delegate, we pass to EnumChildProc
a
reference to the EnumChild
function. We then use the
EnumChildWindow
API
call to drill down into the child windows and compare the name of the window
against the one that we are trying to find.
This CallBack feature will iterate through each child until it either runs out
of children or finds the window.
Using the code
To make use of this code and get you on the road to creating your very own web
aware app, you need to add the 'IEHwd.cs' class to your project.
Once this is done, you need to create an instance of the object at a global
level to your form.
private IEHwd o = new IEHwd();
Once this is done you can now access the features within. You will need to
pass to the class an instance of your form and also the context menu that you
want to show when the right click is done.
I use the NavigateComplete2 event that gets fired once a response has been
received from the web site that the page is being produced.
if(o.oldWndProc.ToInt32() == 0)
{
IntPtr s = o.IEFromhWnd(wbBrowser.Handle);
o.IEContextMenu = IECustomContext;
o.CurrForm = this;
o.StartSubclass(s);
}
The above statement goes into the IEHwd.cs class and retrieves the browser
handle. We pass the web browser handle into the object as a starting
point. We then pass the Context Menu into the class via a property
and the same for the current form.
Once that is done we call StartSubclass
where we store away the old handle so
that we can exit the program cleanly at the end (when the form is discarded.)
switch(Msg)
{
case WM_RBUTTONDOWN:
{
Point p = new Point();
GetCursorPos(ref p);
ScreenToClient(currForm.Handle.ToInt32(), ref p);
ieContextMenu.Show(currForm, p);
return 0;
}
case WM_RBUTTONDBLCLK:
return 0;
default:
break;
}
When the user clicks the right mouse button over the Internet Explorer window
we want to capture the right click event. This happens by checking for
the WM_RBUTTONDOWN
. Once pressed we need to get the point where the mouse
is using the the GetCursorPos
. Then we make use of the
ScreenToClient
API
call to make sure that the Context Menu appears under the mouse and not
somewhere else on the screen.
Finally we show the Context Menu that we passed into the class earlier and
return 0 to stop the real Internet Explorer Context Menu from showing.
You may notice that I also trap the WM_RBUTTONDBLCLK
message also. I found
if you repeatedly clicked the right mouse button it would display the real
Internet Explorer Context Menu.
Points of Interest
I leant that there are some interesting features available in the API. I
have not used the API as much as I should and will endeavor to make more use of
it in the future.
Credits
I would like to acknowledge Chris Wilcock for assistance with the API calls.