Mobile Development - 'Support Side Story'

Broadcasting technical support to Windows Mobile\CE Application Developers to help realizing their potential

Broadcasting technical support to Windows Mobile\CE Application Developers to help realizing their potential

January, 2009

  • Mobile Development - 'Support Side Story'

    Disable WebBrowser's Context-Menu in NETCF applications

    • 6 Comments
    • A possible approach valid on WM5\6\6.1, no longer using DTM_BROWSERDISPATCH
    • Appendix: sample code to find child windows (EnumChildWindows not available under Windows CE)

    Undocumented doesn't mean "not achievable". Undocumented means that on future versions that particular detail may change. So if the application doesn't work on new platforms, that particular thing should be the first one to check. This was for example what happened to the ClassName of NETCF applications... see Daniel Moth's post about this: "#NETCF_AGL_".

    Recently one developer I've worked with pointed me to this link, which describes a possible way to disable NETCF's WebBrowser's context-menu, which worked only on Windows Mobile 2003. He needed help on understanding why it no longer worked on WM5\6\6.1 and moreover on finding a suitable way to reach the same goal by other means, if that used in WM2003 really wasn't doable on later platforms.

    Result of our analysis was that the aforementioned solution was based on DTM_BROWSERDISPATCH, which proved to work on WM2003 but is no longer working on WM5\6\6.1 devices, because the IBrowser interface was *deprecated* -- please see documentation here, specifically "IBrowser, IBrowser2 and IBrowser3 are deprecated and will not be supported in future versions. Instead use IWebBrowser2 and DWebBrowserEvents2. ".

    A solution for current OSs may be found by subclassing the window of the native HTML control that is ultimately wrapped by the NETCF's WebBrowser: we used the same approach I described in Subclassing NETCF Applications. Subclassing means intercepting the Win32 messages sent to a window, by modifying its WndProc's address (which is the function that handles all messages, the so-called "message-pump"): once you intercept a message, you can deal with it or simply pass it to the old WndProc.

    However, to be able to intercept messages you must be able to address the window you want to subclass: and here it comes the "undocumented" thing, which is simply the Class Name of the window associated to the native htmlview.dll control: this is not documented anywhere, but proved to be *PIEHTML* on every WM5\6\6.1 platforms I tested Remote Spy with. Furthermore, the undocumented detail has not been retrieved by looking at Microsoft's internal resources: it's publicly available to every developer who can use the "Remote Spy" tool available with VS2008. Here it is a screenshot:

    image

    Interestingly, we found out that there's an easy OEM-specific solution for Motorola devices, while for other other devices\emulators we had to analyze the message-chain to understand a possible pattern, and found one based on WM_NOTIFY and WM_LBUTTONUP, which should work on every scenario (to be thoroughly tested...).

    So, basically we want to avoid the contextmenu to appear: being the WM_CONTEXTMENU the first message sent to the window while pressing&holding the stylus, the 1st test was to use a code like the following:

    private IntPtr NewWndProc(IntPtr hWnd, uint msg, int wParam, int lParam)
    {
        switch (msg)
        {
            case WM_CONTEXTMENU:
                return IntPtr.Zero;
                break;
        } 
        return CallWindowProc(oldWndProc, hWnd, msg, wParam, lParam); 
    }

    As I said, this worked only for Motorola devices: I can't state why since it may depend on whatever customization around the webview.dll native control done by the OEM (?). In any case, that didn't work on WM5\6\6.1 Emulators (so nothing OEM-dependent), and we had  to look at other patterns. An idea was to intercept WM_NOTIFY and pass it to the old WndProc only if the next WM_message was not one of WM_INITMENUPOPUP, WM_ENTERMENULOOP and WM_CONTEXTMENU: by doing so I could prevent the contextmenu to appear, however links on the page could not be clicked. Everything worked apart from this "detail"... Tongue out so, looking at the chain of messages (still by using simply Remote Spy!) I could notice that when user clicks on a link there's a WM_LBUTTONUP followed by a WM_NOTIFY. So I based the NewWndProc on the following and this worked:

    private IntPtr NewWndProc(IntPtr hWnd, uint msg, int wParam, int lParam)
    {
        switch (msg)
        {
            //when clicking on a link on the page, WM_NOTIFY is sent AFTER WM_LBUTTONUP
            //however, reset the bool variable if WM_NOTIFY is not received just after WM_LBUTTONUP
            case WM_LBUTTONUP:
                bLButtonUpHandled = true;
                break;
            case WM_NOTIFY:
                if (bLButtonUpHandled)
                {
                    bLButtonUpHandled = false;
                    break;
                }
                else
                {
                    //block WM_NOTIFY
                    return IntPtr.Zero;
                }
            case WM_CONTEXTMENU:
                //If you need to do something custom, do it here
                DialogResult dlg = MessageBox.Show("Close?", this.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button1);
                if (dlg == DialogResult.Yes)
                { 
                    this.webBrowser1.Navigate(new Uri("http://blogs.msdn.com/raffael"));
                    return IntPtr.Zero;
                }
                break;
    
            //for every other WM_x, if this is coming after WM_LBUTTONUP then reset bLButtonUpHandled
            default:
                if (bLButtonUpHandled)
                    bLButtonUpHandled = false;
    
                break;
            
        }
        return CallWindowProc(oldWndProc, hWnd, msg, wParam, lParam);
    }

    Remember that the code provided is for didactic purposes only: it shows how you can reach the goal, however doesn't contain error-handling. Obviously I do recommend including try\catch and exception-handling!!

    APPENDIX: FindWindow API can be used only for top-level windows. If you need the handle of a child window knowing its ClassName and having the handle of the parent, then you can use for example the following code:

    private IntPtr FindChildWindowByParent(string strChildClassName, IntPtr hWndTopLevel)
    {
        bool bFound = false;
        IntPtr hwndCur = IntPtr.Zero;
        IntPtr hwndCopyOfCur = IntPtr.Zero;
        char[] chArWindowClass = new char[32];
    
        do
        {
            // Is the current child null?
            if (IntPtr.Zero == hwndCur)
            {
                // get the first child
                hwndCur = GetWindow(hWndTopLevel, (uint)GetWindowFlags.GW_CHILD);
            }
            else
            {
                hwndCopyOfCur = hwndCur;
                // at this point hwndcur may be a parent of other windows
                hwndCur = GetWindow(hwndCur, (uint)GetWindowFlags.GW_CHILD);
    
                // in case it's not a parent, does it have "brothers"?
                if (IntPtr.Zero == hwndCur)
                    hwndCur = GetWindow(hwndCopyOfCur, (uint)GetWindowFlags.GW_HWNDNEXT);
            }
    
            //if we found a window (child or "brother"), let's see if it's the one we were looking for
            if (IntPtr.Zero != hwndCur)
            {
                GetClassName(hwndCur, chArWindowClass, 256);
                string strWndClass = new string(chArWindowClass);
                strWndClass = strWndClass.Substring(0, strWndClass.IndexOf('\0'));
    
                bFound = (strWndClass.ToLower() == strChildClassName.ToLower());
            }
            else
                break;
        }
        while (!bFound);
    
        //found!
        return hwndCur;
    }

    If you don't have the handle of the parent or even don't know which top-level window it is, since we don't have EnumChildWindows under Windows CE, you need to use EnumWindows and basically invoke the function above for each of them, to be run only in case the child window hasn't been found yet -- I mean the following:

    private void FindChildWindow(string strChildClassName, string strChildWindowName)
    {
        //Enum all top-level windows
        //for each window, see if it has childs and if among them there's the window we're looking for
        EnumWindows(new EnumWindowsDelegate(EnumWindowsProc), 0);
        return;
    }
    
    private int EnumWindowsProc(IntPtr hWndParent, int lParam)
    {
        IntPtr hwndCur = IntPtr.Zero;
        IntPtr hwndCopyOfCur = IntPtr.Zero;
        char[] chArWindowClass = new char[32];
        
        //for each window, see if it has childs and if among them there's the window we're looking for
        //if already found, don't search again
        if (!bFound)
        {
            do
            {
                // Is the current child null?
                if (IntPtr.Zero == hwndCur)
                {
                    // get the first child
                    hwndCur = GetWindow(hWndParent, (uint)GetWindowFlags.GW_CHILD);
                }
                else
                {
                    hwndCopyOfCur = hwndCur;
                    // at this point hwndcur may be a parent of other windows
                    hwndCur = GetWindow(hwndCur, (uint)GetWindowFlags.GW_CHILD);
    
                    // in case it's not a parent, does it have "brothers"?
                    if (IntPtr.Zero == hwndCur)
                        hwndCur = GetWindow(hwndCopyOfCur, (uint)GetWindowFlags.GW_HWNDNEXT);
                }
    
                //if we found a window (child or "brother"), let's see if it's the one we were looking for
                if (IntPtr.Zero != hwndCur)
                {
                    GetClassName(hwndCur, chArWindowClass, 256);
                    string strWndClass = new string(chArWindowClass);
                    strWndClass = strWndClass.Substring(0, strWndClass.IndexOf('\0'));
    
                    bFound = (strWndClass.ToLower() == strChildClassName.ToLower());
                }
                else
                    break;
            }
            while (!bFound);
    
            //found!
            hWndTarget = hwndCur;
        }
        return 1;
    }
    
    [DllImport("coredll", SetLastError = true)]
    [return: MarshalAs(UnmanagedType.Bool)]
    internal static extern bool EnumWindows(
        [MarshalAs(UnmanagedType.FunctionPtr)]EnumWindowsDelegate lpEnumFunc,
        int lParam);
    
    internal delegate int EnumWindowsDelegate(IntPtr hwnd, int lParam); //NOTE THE RETURN VALUE!!!

    Last hint: when invoking delegates of NATIVE functions, NETCF has a limitation about the type returned: it must be an integer, or in any case a blittable and integer-based datatype. I was using managed Boolean (bool in C#), but this is not blittable to a native BOOL because they are 1 Byte in managed code and 4 bytes in native code!! This limitation was a design decision on V2 because the registers for return values are 4 Byte integer registers on most platforms... and made me waste some time… it sufficed to use private int EnumWindowsProc() instead of private bool EnumWindowsProc() and Win32 and .NET started talking each other. Nerd

    Cheers,

    ~raffaele

  • Mobile Development - 'Support Side Story'

    Great tool to diagnose Memory on Windows Mobile

    • 0 Comments

    If you know my interest on troubleshooting memory issues on Windows Mobile applications (this was the topic of my very first post!), you'll forgive me if this time I simply link an article on CodeProject... Smile Visualizing the Windows Mobile Virtual Memory Monster. Maybe a candidate for Microsoft Windows Mobile Developer Contest 2008?

     

    Cheers,

    ~raffaele

  • Mobile Development - 'Support Side Story'

    NETCF: Improve Startup Performances

    • 1 Comments

    "My NETCF application takes long to start, on both devices and emulators... is there any way to optimize this loading time?".

    Questions like that are arisen by mobile developers from time to time, so similarly to the post about power-efficiency, I have a sort of ready set of suggestions about how to possibly reduce startup time of a NETCF application. In any case, you should be aware that, even if you can use techniques to reduce the startup time, it may be that it won't be able to take less than "a few" seconds, and this is due to the intrinsic nature of the JIT-compilation of the .NET code (JIT = Just In Time). This is something true in general for .NET programming, not simply to the NETCF. And, apart from this, "performance" is a subjective matter, so at design-time be careful on understanding how long your users will accept to "wait" for your main form to appear and start using the app.


    1- Above all, verify if targeting NETCF v3.5 can reduce startup time: even if v3.5 in general is faster than v2.0, it might be that the performances wouldn't be enough appreciated in your case. To run the v2 application on the v3.5 you don't need to re-compile the application by using VS2008 – you can instead use the approach I mentioned in one of my previous posts.

    2- There's a 3RD party tool that I must mention regarding profiling an application. This is not Microsoft's code, therefore we don't offer support about it - in any case I've heard many developers using it with success: EQATEC Profiler.

    3- Because of the JIT-compilation, reducing the startup time may be very difficult. Do you think you may launch the application at device startup and maintain it in background, so that when the user taps on its icon it gains focus and gives the impression of being loaded "immediately"? Yes, this approach would waste a process-slot in the virtual memory, nevertheless it grants you the desired performances when launching the application, so it may be something worth thinking about.

    Some links:

    • The article Developing Well Performing .NET Compact Framework Applications reaches the following conclusions: "[…] A. Reduce the number of method and property calls on controls during startup. For example, Controls.Bounds is a better option than calls to Control.Location and Control.Size. -- B. Create the form from the top down. In nested control hierarchies, set the parent property of containers (using the above rule) before adding controls to the container. As in the BigForm application, the panels had their parent property set to the form before the 40 controls were connected to the panel. If further containers exist lower in the hierarchy, the same changes should be applied. ")
    • If you want to measure performances, you can read Performance and Diagnostics. Specifically, you should read How to: Improve Performance.
    • Finally, this is a quite old article but still applicable for some extents: Optimize Your Pocket PC Development with the .NET Compact Framework.

    4- Since at the end of the day we're talking about *user's perceptions*, a broadly used way to "distract" the user while loading the initial form of your application is to use a Splash form. This is so useful that many resources are available on the topic:

     

    Cheers,

    ~raffaele

Page 1 of 1 (3 items)