GPS Programming Tips for Windows Mobile - Part 1
NETCF: Memory leak... now what??
Supporting Kiosk-Applications on Windows Mobile (Technically achievable vs. supported)
Wireless Programming on Windows Mobile: supported or not supported?
Establishing GPRS Connection on Windows CE and Windows Mobile: Sample Codes
Disable WebBrowser's Context-Menu in NETCF applications
MAPI on Windows Mobile 6: Programmatically retrieve mail BODY (sample code)
Microsoft released a HotFix for NETCF v3.5 on Windows Mobile 6.1.4 onwards, to address basic functionalities of WebBrowser control
The right approach to get a Contact’s last communication (IItem’s PIMPR_SMARTPROP)
Remote Desktop Mobile (RDP Client) disconnects after 10 minutes of inactivity
Support Boundaries for Windows Mobile Programming (Developing Drivers, for example... Or even WiFi Programming)
Miei post in italiano sul team-blog del Supporto Tecnico agli Sviluppatori
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:
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"... 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.
Cheers,
~raffaele
Excellent article Raffael, congrats.
Very good tips ! but it seems that your function do not explore all the childs tree.
Here is my recursive method to enumerate all branches of windows into my app :
private IntPtr FindWindow(string strChildClassName, IntPtr hWndTopLevel)
{
IntPtr hwndCur = IntPtr.Zero;
hwndCur = GetWindow(hWndTopLevel, (uint)GW_CHILD);
return RecurseFindWindow(strChildClassName, hwndCur);
}
private IntPtr RecurseFindWindow(string strChildClassName, IntPtr hWndParent)
char[] chArWindowClass = new char[32];
if (hWndParent == IntPtr.Zero)
return IntPtr.Zero;
else
//check if we get the searched class name
GetClassName(hWndParent, chArWindowClass, 256);
string strWndClass = new string(chArWindowClass);
strWndClass = strWndClass.Substring(0, strWndClass.IndexOf('\0'));
bFound = (strWndClass.ToLower() == strChildClassName.ToLower());
if (bFound)
return hWndParent;
//recurse into first child
IntPtr hwndChild = GetWindow(hWndParent, (uint)GW_CHILD);
if (hwndChild != IntPtr.Zero)
hwndCur = RecurseFindWindow(strChildClassName, hwndChild);
if(!bFound)
IntPtr hwndBrother = IntPtr.Zero;
//enumerate each brother windows and recurse into
do
hwndBrother = GetWindow(hWndParent, (uint)GW_HWNDNEXT);
hWndParent = hwndBrother;
if (hwndBrother != IntPtr.Zero)
hwndCur = RecurseFindWindow(strChildClassName, hwndBrother);
break;
while (hwndBrother != IntPtr.Zero);
return hwndCur;
Merci Lionel! You may well be right, so thanks for posting your sample code!
… so how can I grab the handle of a particular child window, considering that FindWindow retrieves all
Hi
is there anyway to get HtmlDocument from SHDOCVW-> INTERNET EXPLORER_SERVER or Shell DocObject VIEW Window handle
Cheers
Hi Vijay, afaik there's no way to retrieven such COM interfaces, that's why I showed how to play with windows functions in the post. But pls feel free to add any solution you may have found, thanks!