Home > 2. Windows Programming, Managed Code, Windows Mobile > Mouse Events for WebBrowser control in .NET CF

Mouse Events for WebBrowser control in .NET CF

This is my first contribution on codeproject. If you like it, feel free to comment and… vote here.

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 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 defaut 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 first three requirements, but it still can not 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 user click on the WebBrowser control, he actually click 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 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 implemented the IMessageFilter interface. This interface will help us to hook and catch WM_LBUTTONUP, WM_LBUTTONDOWN and WM_MOUSEMOVE meassage. (If you haven’t been farmiliar with IMessageFiter, you should go to the OpenNETCF’s website and have a look).

But, when click on the WebBrowser, which is the child window receive 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 mouse messages.

Remotespy.jpg

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
{
    /// <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 declerations ------------

        /// <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
    }
}

The implementation is 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, in stead of System.Windows.Forms.Application. This will help receiving and processing messages in the PreFilterMessage function. We place this code in 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());
    }
}

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
    /// <summary>
    /// An extended WebBrowser, can handle Mouse Events.
    /// </summary>
    public class WebBrowserEx : OpenNETCF.Windows.Forms.WebBrowser, IMessageFilter
    {
        //....
    }
    

    Derived from OpenNETCF’s WebBrowser, you can use WebBrowserEx in .NET CF 1.0, and it have built-in functions such as: disabled the default context menu, doesn’t have the progress bar…

    Through the years, I learn 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.

     

 

Advertisements
  1. 25/12/2008 at 8:03 AM

    This is a fantastic plug-in and has helped me solve what had been a difficult problem.

    I am curious though… is there a way that once the web browser has been loaded for it to scroll down to the bottom of the page automatically?

    Either way – great stuff !!!

  2. phvu
    25/12/2008 at 10:45 AM

    @Chan: you can simply use GetScrollInfo, SetScrollInfo and send some message to the PIEHTML window. Here is the functions I used to add a string to the webBrowser, and after that scrolling it.

    private SCROLLINFO m_si;

    protected override void OnDocumentCompleted(WebBrowserDocumentCompletedEventArgs e)
    {
    base.OnDocumentCompleted(e);
    RedrawAfterAddString();
    }

    // Get the handle of PIEHTML
    private static IntPtr GetPIEHTMLHandle(IntPtr hWnd)
    {
    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
    while ((hWnd = GetWindow(ret, (int)GetWindowFlags.GW_HWNDNEXT)) != IntPtr.Zero)
    {
    ret = hWnd;
    }

    return ret;
    }

    protected void AddString(string sText)
    {
    IntPtr hPIEHTML = GetPIEHTMLHandle(Handle);

    if (hPIEHTML != IntPtr.Zero)
    {
    //Get current position, isBottom
    m_si.fMask = (int)ScrollInfoMask.SIF_ALL;
    GetScrollInfo(hPIEHTML, (int)ScrollBarDirection.SB_VERT, ref m_si);

    //Set REDRAW = false
    Win32Window.SendMessage(hPIEHTML, (int)WM.SETREDRAW, 0, IntPtr.Zero);
    //Win32Window.SendMessage(Handle, (int)WM.SETREDRAW, 0, IntPtr.Zero);

    //Set document
    DocumentText = sText;
    }
    }

    protected void RedrawAfterAddString()
    {
    IntPtr hPIEHTML = GetPIEHTMLHandle();

    if (hPIEHTML != IntPtr.Zero)
    {
    const int delta = 2;
    long iRealPos = m_si.nPage + m_si.nPos;

    //user is being bottom -> scroll to bottom
    if (iRealPos >= m_si.nMax – delta && iRealPos <= m_si.nMax + delta)
    {
    //Scroll till the end
    m_si.fMask = (int)ScrollInfoMask.SIF_ALL;
    GetScrollInfo(hPIEHTML, (int)ScrollBarDirection.SB_VERT, ref m_si);

    m_si.nPos = m_si.nMax – (int)m_si.nPage – 1;

    m_si.fMask = (int)ScrollInfoMask.SIF_PAGE | (int)ScrollInfoMask.SIF_POS |
    (int)ScrollInfoMask.SIF_DISABLENOSCROLL;
    SetScrollInfo(hPIEHTML, (int)ScrollBarDirection.SB_VERT, ref m_si, false);

    Win32Window.SendMessage(hPIEHTML, (int)WM.VSCROLL, (int)ScrollBarScrollCode.SB_LINEDOWN, hPIEHTML);
    }
    else
    {
    m_si.fMask = (int)ScrollInfoMask.SIF_PAGE | (int)ScrollInfoMask.SIF_POS |
    (int)ScrollInfoMask.SIF_DISABLENOSCROLL;
    SetScrollInfo(hPIEHTML, (int)ScrollBarDirection.SB_VERT, ref m_si, false);

    //Win32Window.SendMessage(hPIEHTML, (int) WM.VSCROLL, (int) ScrollBarScrollCode.SB_LINEUP, hPIEHTML);
    Win32Window.SendMessage(hPIEHTML, (int)WM.VSCROLL, (int)ScrollBarScrollCode.SB_LINEDOWN, hPIEHTML);
    }

    //Set REDRAW = true
    Win32Window.SendMessage(hPIEHTML, (int)WM.SETREDRAW, -1, IntPtr.Zero);

    //”fake” scroll for redrawing
    Win32Window.SendMessage(hPIEHTML, (int)WM.PAINT, 0, IntPtr.Zero);
    }
    }

    here is P/Invoke declerations:
    #region ————– PInvoke declerations ——-

    [DllImport(“coredll.dll”)]
    [return: MarshalAs(UnmanagedType.Bool)]
    private static extern bool GetScrollInfo(IntPtr hwnd, int fnBar, ref SCROLLINFO lpsi);

    [DllImport(“coredll.dll”)]
    static extern int SetScrollInfo(IntPtr hwnd, int fnBar, [In] ref SCROLLINFO lpsi, bool fRedraw);

    [StructLayout(LayoutKind.Sequential)]
    struct SCROLLINFO
    {
    public uint cbSize;
    public uint fMask;
    public int nMin;
    public int nMax;
    public uint nPage;
    public int nPos;
    public int nTrackPos;
    }

    private enum ScrollBarDirection
    {
    //SB_HORZ = 0,
    SB_VERT = 1,
    //SB_CTL = 2,
    //SB_BOTH = 3
    }

    private enum ScrollInfoMask
    {
    SIF_RANGE = 0x1,
    SIF_PAGE = 0x2,
    SIF_POS = 0x4,
    SIF_DISABLENOSCROLL = 0x8,
    SIF_TRACKPOS = 0x10,
    SIF_ALL = SIF_RANGE + SIF_PAGE + SIF_POS + SIF_TRACKPOS
    }

    private enum ScrollBarScrollCode
    {
    SB_LINEUP = 0,
    //SB_LINELEFT = 0,
    SB_LINEDOWN = 1,
    //SB_LINERIGHT = 1,
    //SB_PAGEUP = 2,
    //SB_PAGELEFT = 2,
    //SB_PAGEDOWN = 3,
    //SB_PAGERIGHT = 3,
    //SB_THUMBPOSITION = 4,
    //SB_THUMBTRACK = 5,
    //SB_TOP = 6,
    //SB_LEFT = 6,
    SB_BOTTOM = 7,
    //SB_RIGHT = 7,
    //SB_ENDSCROLL = 8
    }

    Although I sent WM_SETREDRAW to PIEHTML, but it still flash a little. So tell me if it work or not, and if you find another way to solve the problem.

    Kind Regards.

  3. sam
    07/06/2011 at 6:45 PM

    I am running a Windows Mobile Phone (WM 6.5, .NET CT 2.0). The default IE browser can scroll from finger flicks . . .
    I would like to bring the same scrolling within a webbrowser control in my form. I am writing an app in VB.NET for this phone.
    If you can add any inputs, that will be great.
    Thank you very much.

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: