针对 .NET CF 中 WebBrowser 控件的鼠标事件
扩展 .NET CF 2.0 的 WebBrowser 以处理鼠标事件。

引言
几乎在我的一个 .NET Compact Framework 2.0 项目中,我希望使用 WebBrowser
控件来显示富媒体内容。 不幸的是,.NET CF 中的 WebBrowser
控件不像 .NET Framework 的控件那么强大。 它没有一些有用的接口,尤其是它不支持 DOM 模型... 我对该控件的要求是
- 底部没有进度条。
- 不使用默认的上下文菜单(禁用它,或者更好的是,我可以自定义上下文菜单)。
- 有一种方法可以将 HTML 文本附加到其中(
DocumentText
或类似的东西)。 - 可以处理鼠标事件(
MouseUp
、MouseDown
、MouseMove
)。
OpenNETCF 的 SDF WebBrowser
控件确实满足了前三个要求,但它仍然无法处理鼠标事件。 所以我决定扩展标准的 WebBrowser
控件,以便它可以处理鼠标事件。
背景
在 .NET Framework 中,使用 Document
属性,我们可以轻松处理 WebBrowser
控件的鼠标事件; 但 .NET CF 不行。 因为当用户点击 WebBrowser
控件时,他实际上点击了该控件托管的 Document
,所以 WebBrowser
不知道此事件,当然它也不会引发相关事件。
解决问题的思路:派生 WebBrowser
控件,并钩住该控件内部发生的所有鼠标事件。 感谢 OpenNETCF 的 ApplicationEx
类,我们可以在托管代码中实现这一点。 具体来说,我们编写一个名为 WebBrowserEx
的类,它派生自 .NET CF 的 WebBrowser
控件,并实现 IMessageFilter
接口。 此接口将帮助我们钩住并捕获 WM_LBUTTONUP
、WM_LBUTTONDOWN
和 WM_MOUSEMOVE
消息。 (如果您不熟悉 IMessageFiter
,您应该去 OpenNETCF 的网站看看)。
但是,当您单击 WebBrowser 时,哪个子窗口接收这些消息? 没问题! 使用 Remote Spy 实用程序(在 Visual Studio Remote Tools 中),我们可以看到 WebBrowser
控件的孙窗口。 此窗口属于 PIEHTML
类,并接收所有鼠标消息。

实现
WebBrowserEx
类中大约只有 100 行代码,所以我在下面全部粘贴出来。
using System;
using System.Runtime.InteropServices;
using System.Windows.Forms;
using OpenNETCF.Windows.Forms;
using OpenNETCF.Win32;
using Microsoft.WindowsCE.Forms;
namespace webBrowserEx
{
/// <summary>
/// An extended WebBrowser, can handle Mouse Events.
/// </summary>
public class WebBrowserEx : System.Windows.Forms.WebBrowser, IMessageFilter
{
public WebBrowserEx()
{
//Initialize Message Filter, using OpenNETCF's ApplicationEx
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 -----------------
/// <summary>
/// Check whether <see cref="hCheck"/>
/// is one of <see cref="hWnd"/>'s grandchildren.
/// </summary>
/// <param name="hWnd"></param>
/// <param name="hCheck"></param>
/// <returns></returns>
private static bool IsChildestWindow(IntPtr hWnd, IntPtr hCheck)
{
IntPtr ret = hWnd;
//Find the first "smallest" child
while ((hWnd = GetWindow(hWnd, (int)GetWindowFlags.GW_CHILD)) != IntPtr.Zero)
{
ret = hWnd;
}
//goes through all of "smallest" grandchildren
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 ------------
/// <summary>
/// Get relative window with a given window.
/// </summary>
/// <param name="hwnd">the Given window</param>
/// <param name="cmd">an <see cref="GetWindowFlags"/> value,
/// indicates the relation.</param>
/// <returns></returns>
[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
}
}
实现非常简单。 每当收到 WM_MOUSEMOVE
、WM_LBUTTONUP
、WM_LBUTTONDOWN
消息时,我们将检查 m.HWnd
是否是 WebBrowser
的孙子窗口(即 PIEHTML
窗口)并引发相关事件。
我们要做的最后一件事是使用 OpenNETCF.Windows.Forms.
ApplicationEx
类来运行应用程序,而不是 System.Windows.Forms.Application.
这将有助于在 PreFilterMessage
函数中接收和处理消息。 我们将此代码放在 Program.cs 中
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[MTAThread]
static void Main()
{
// Instead of using Application class,
// we use OpenNETCF's ApplicationEx which enables MessageFilter.
OpenNETCF.Windows.Forms.ApplicationEx.Run(new Form1());
//Application.Run(new Form1());
}
}
关注点
- 我在 VS 2008 中构建了示例代码,使用 .NET Compact Framework 2.0 上的智能设备项目。 但在 .NET CF 3.5 中应该没问题。
- 您可以使用
OpenNETCF.Windows.Forms.WebBrowser
类,而不是使用System.Windows.Forms.WebBrowser
。 在这种情况下,您只需将System.Windows.Forms.WebBrowser
替换为OpenNETCF.Windows.Forms.WebBrowser
/// <summary> /// An extended WebBrowser, can handle Mouse Events. /// </summary> public class WebBrowserEx : OpenNETCF.Windows.Forms.WebBrowser, IMessageFilter { //.... }
派生自 OpenNETCF 的
WebBrowser
,您可以在 .NET CF 1.0 中使用WebBrowserEx
,它具有内置功能,例如:禁用默认的上下文菜单,没有进度条...
多年来,我从 CodeProject 了解到很多有趣的事情,但这是我的第一篇文章,附带了一些代码... 所以,请随意评论和... ^^。
抱歉我的英语,这不是我的母语。
历史
- 2008 年 7 月 13 日:文章提交