Introduction
Nearly, in one of my .NET Compact Framework 2.0 projects, I want to use the WebBrowser
control to display rich-media content. Unfortunately, the WebBrowser
control in the .NET CF is not as powerful as the .NET Framework's control. It does not have some of the useful interfaces, especially it does not support DOM model... My requirements for the control are:
- Doesn't have the progress bar at the bottom.
- Doesn't use the default context menu (disable it, or even better, I can customize the context menu).
- Has a way to append HTML text into it (
DocumentText
or something like that). - Can handle Mouse events (
MouseUp
, MouseDown
, MouseMove
).
The OpenNETCF's SDF WebBrowser
control does satisfy the first three requirements, but it still cannot handle mouse events. So I decided to expand the standard WebBrowser
control so that it can play with mouse events.
Background
In the .NET Framework, using the Document
property, we can easily handle mouse events for the WebBrowser
control; but the .NET CF doesn't. Because when the user clicks on the WebBrowser
control, he actually clicks on the Document
that the control hosts, so the WebBrowser
does not know this event, and of course it doesn't raise the correlative events.
The idea to solve the problem: derive the WebBrowser
control, and hook all of the mouse events which occur inside the control. Thanks to OpenNETCF's ApplicationEx
class, we can achieve this in managed code. Concretely, we write a class called WebBrowserEx
, which is derived from .NET CF's WebBrowser
control, and implement the IMessageFilter
interface. This interface will help us to hook and catch WM_LBUTTONUP
, WM_LBUTTONDOWN
and WM_MOUSEMOVE
message. (If you aren't familiar with IMessageFiter
, you should go to the OpenNETCF's website and have a look).
But, when you click on the WebBrowser, which is the child window receives these messages? Not a problem! Using the Remote Spy utility (in the Visual Studio Remote Tools), we can see a grandchild window of the WebBrowser
control. This window belongs to PIEHTML
class and receives all of the mouse messages.
Implementation
There are only approximate 100 lines of code in the WebBrowserEx
class, so I paste all here.
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using OpenNETCF.Windows.Forms;
using OpenNETCF.Win32;
using Microsoft.WindowsCE.Forms;
namespace webBrowserEx
{
public class WebBrowserEx : System.Windows.Forms.WebBrowser, IMessageFilter
{
public WebBrowserEx()
{
OpenNETCF.Windows.Forms.ApplicationEx.AddMessageFilter(this);
}
#region ------ IMessageFilter implementation ------
public bool PreFilterMessage(ref Message m)
{
if ((m.Msg == (int)WM.LBUTTONDOWN || m.Msg == (int)WM.LBUTTONUP ||
m.Msg == (int)WM.MOUSEMOVE)
&& IsChildestWindow(this.Handle, m.HWnd))
{
int xPos = (int)m.LParam & 0xFFFF;
int yPos = ((int)m.LParam >> 16) & 0xFFFF;
MouseEventArgs e = new MouseEventArgs(MouseButtons.Left, 1,
xPos, yPos, 0);
switch (m.Msg)
{
case (int)WM.LBUTTONUP:
base.OnMouseUp(e);
break;
case (int)WM.LBUTTONDOWN:
base.OnMouseDown(e);
break;
case (int)WM.MOUSEMOVE:
base.OnMouseMove(e);
break;
}
}
return false;
}
#endregion
#region ------- Private functions -----------------
private static bool IsChildestWindow(IntPtr hWnd, IntPtr hCheck)
{
IntPtr ret = hWnd;
while ((hWnd = GetWindow(hWnd, (int)GetWindowFlags.GW_CHILD)) != IntPtr.Zero)
{
ret = hWnd;
}
hWnd = ret;
while ((ret != hCheck) &&
((hWnd = GetWindow(ret, (int)GetWindowFlags.GW_HWNDNEXT)) != IntPtr.Zero))
{
ret = hWnd;
}
return (hWnd != IntPtr.Zero);
}
#endregion
#region -------- P/Invoke declarations ------------
[DllImport("coredll.dll")]
private static extern IntPtr GetWindow(IntPtr hwnd, int cmd);
private enum GetWindowFlags : int
{
GW_HWNDFIRST = 0,
GW_HWNDLAST = 1,
GW_HWNDNEXT = 2,
GW_HWNDPREV = 3,
GW_OWNER = 4,
GW_CHILD = 5,
GW_MAX = 5
}
#endregion
}
}
The implementation is so easy. Whenever receiving a WM_MOUSEMOVE
, WM_LBUTTONUP
, WM_LBUTTONDOWN
message, we will check if the m.HWnd
is a grandchild of the WebBrowser
(i.e. the PIEHTML
window) and raise the correlative events.
The last thing we have to do is use the OpenNETCF.Windows.Forms.
ApplicationEx
class to run the application, instead of System.Windows.Forms.Application.
This will help in receiving and processing messages in the PreFilterMessage
function. We place this code in Program.cs:
static class Program
{
[MTAThread]
static void Main()
{
OpenNETCF.Windows.Forms.ApplicationEx.Run(new Form1());
}
}
Points of Interest
- I built the sample code in VS 2008, using Smart Device Project on .NET Compact Framework 2.0. But in .NET CF 3.5, it should be fine.
- Instead of using
System.Windows.Forms.WebBrowser
, you can use the OpenNETCF.Windows.Forms.WebBrowser
class. In that case, you only have to replace System.Windows.Forms.WebBrowser
by OpenNETCF.Windows.Forms.WebBrowser
:
public class WebBrowserEx : OpenNETCF.Windows.Forms.WebBrowser, IMessageFilter
{
}
Derived from OpenNETCF's WebBrowser
, you can use WebBrowserEx
in .NET CF 1.0, and it has built-in functions such as: disabled the default context menu, doesn't have the progress bar...
Through the years, I learnt a lot of interesting things from CodeProject, but this is my first article, with a shy code... So, feel free to comment and... ^^.
Sorry for my English, it's not my mother language.
History
- July 13, 2008: Article submitted