Windows Mobile: CF how to catch F1 and F2 in WEH
Before WEH and Windows Mobile 6 it was easy to catch all keys including the function keys in a Compact Framework (CF) application, you simply had to use Allkeys(true) and Form.Keypreview=true.
Actually, with Windows Embedded Handheld (WEH) or Windows Mobile 6.5.3 the above will not work for the F1 and F2 key. There are simply no KeyDown and KeyUp events reaching your CF application.
Windows Mobile 6.1 demo
With WEH Microsoft moved the Start button from the taskbar at top to the menu bar at bottom. Further on, the menu bar is now using graphic tiles to display the top menu, the Close and OK/Done option. The OK/Close button also moved from taskbar to menu bar. Additionally the menu bar is higher than in Windows Mobile 6.1. That leaves less space for your client window.
Due to the above changes in the UI, the window messages are routed in another unknown way and normally a CF application does not get F1 and F2 key messages. Possibly the CF main message queue gets notification messages but these are handled internally by the CF runtime, you only see the menus are working.
Windows message routing
How to overcome this restriction
I tried several approaches to get the F1 and F2 keystrokes in a test CF application.
- Using a Microsoft.WindowsCE.Forms MessageWindow with a WndProc fails. [CF MessageWindow approach]
- Using SetWindowLong with a WndProc replacement fails, if used only on the form handle. But you may use that and implement it for every child element in the form. It looks like the messages are not routed thru the main fom WndProc (see native winapi below), or lets say, the F1 and F2 keys do not bubble from child to parent as seen in a Win32 API window application. [Subclassing approach]
- using OpenNetCF Application2/ApplicationEx and an IMessageFilter works. If you already use or see other needs for OpenNetCF/SmartDeviceFramework, you may use this approach. [SmartDeviceFramework Application2/IMessageFilter approach]
- using a global keyboard hook works. That is the one I prefer, as it easy to use and gives all keyboard messages. [KeybdHook approach]
Native Win32 window application
In a native window application you create a new window class, provide a WndProc and a message queue handler and init the
class. If you need a textbox or a button, you create a window insde the main window using a pre-defined windows class (let
me call them stock window classes).
All these windows have there own WndProc. The a stock window classes handles standard messages itself. Messages that are not
handled by a window should call DefWindowProc. So unhandled messages are transfered from a focused child window to its
parent window and it’s WndProc. Stock window classes provide much functionality itself, for example an edit stock window
handles cursor movement, cut copy and paste of text, painting and much more.
If a stock window class does not handle a message like the F1 keydown message, it returns it to its parent window, normally your main window. Now your main window WndProc can react on this message with a WM_KEYDOWN handler and inform the OS that it handled the message. The message is then removed from the global message queue. If your code let windows know, that you dont handled the message (return FALSE), the message is returned and can be handled by the OS. If no window handles the message, the OS discards the message of the message queue.
The above works fine with function keys, if your app uses Allkeys(true). If you do not use Allkeys(true), the OS (the Windows Mobile GWES (Graphic Windowing Event System)) handles the message BEFORE it is dispatched to other windows. As GWES handles the message itself, for example to do Volume Up/Down with F6/F7 key presses, it is NOT placed in the message queue for any other window. When you press F1 or F2 GWES opens the left or right main menu of the foreground app (if it has a menu).
Using Allkeys workes fine with native window apps. The window app’s WndProc gets keyup and keydown messages for the function keys too, even for F1 and F2.
Allkeys works also fine with CF apps before WEH, for example within Windows Mobile 6.1. But in WEH a CF app does not get the F1 and F2 key stroke. Thanks to Microsoft (also for malfunction of SHFullScreen in WEH, but this is another story).
The different approaches
Keytest3AKsdf
This example shows the usage of IMessageFilter of the Smart Device Framework (SDF) and its Application2/Ex class. In your program starter you simply have to change the Run function to:
//Application.Run(new Keytest3AKsdfForm()); Application2.Run(new Keytest3AKsdfForm());
You see, instead of starting your app with Application.Run you use the SDF Application2.Run call. Additionally you have to use:
public Keytest3AKsdfForm() { InitializeComponent(); _hwnd = new List<IntPtr>(); _hwnd.Add(this.Handle); _hwnd.Add(this.listView1.Handle); Application2.AddMessageFilter(this); win32.AllKeys(true); ... List<IntPtr> _hwnd; ... public bool PreFilterMessage(ref Message m) { System.Diagnostics.Debug.WriteLine("msghandler1: " + m.HWnd.ToInt32().ToString("X8") + ", " + m.Msg.ToString("X8") + ", " + m.WParam.ToInt32().ToString("X8") + ", " + m.LParam.ToInt32().ToString("X8")); //only process msg to our windows if (_hwnd.Contains(m.HWnd)) return MsgHandler(m); else return false; } private bool MsgHandler(Message m) { if (m.Msg == WindowsMessages.WM_KEYDOWN || m.Msg == WindowsMessages.WM_KEYUP) { //add the msg to our listview addItem(m); Keys k = (Keys)m.WParam.ToInt32(); System.Diagnostics.Debug.WriteLine("msghandler2: " + m.HWnd.ToInt32().ToString("X8") + //", " + m.Msg.ToString("X8") + ", " + ((WindowsMessages.WM_MESG)m.Msg).ToString() + ", " + m.WParam.ToInt32().ToString("X8") + ", " + m.LParam.ToInt32().ToString("X8")); if (_bForwardKeys == false) return true;//let windows now that we handled the message else return false; } return false;//let windows now that we DID NOT handled the message }
The first lines use a list of handles (IntPtr) to later filter messages by GUI components.
Then the line Application2.AddMessageFilter(this); adds an IMessageFilter for the form. Now, your form’s code has to implement a function “public bool PreFilterMessage(ref Message m)”. This function is called, when a message is sent to your form (mouse movements, clicks, keyboard events etc). In the above code, I first check the provided window handle against the list of handles to filter. Then MsgHandler is called and processes WM_KeyDown/Up messages only.
The line win32.AllKeys(true); prevents the OS to process keyboard messages itself (ie menu (F1/F2), Volume Up/Down (F6F7)). There is an additinol win32 class that implements a set of native windows API calls.
Internally SDF uses a custom message pump (see also the native message pump using while(GetMessage){…). This message pump is invoked before the compact framework form’s message pump and gives access to F1 and F2, whereas the compact framework itself did not.
The keyboard hook approach
This example uses a keyboard hook library. In the form’s code you use it this way:
public partial class Keytest3AKcs : Form { //HookKeyboard Keyboard.KeyHook.KeyboardHook _keyboardHook; public Keytest3AKcs() { InitializeComponent(); win32.AllKeys(mnuAllkeys.Checked); ... try { //test with keyhook _keyboardHook = new KeyHook.KeyboardHook(this); _keyboardHook.HookEvent += new KeyHook.KeyboardHook.HookEventHandler(_kHook_HookEvent); ... } catch (SystemException ex) { ... _keyboardHook = null; } } void _kHook_HookEvent(object sender, KeyHook.KeyboardHook.HookEventArgs hookArgs) { addLog("HookEvent: " + //win32.helpers.hex8(hookArgs.Code) + ", " + win32.helpers.hex8(hookArgs.wParam) + ", " + win32.helpers.hex8(hookArgs.hookstruct.vkCode) + ", " + win32.helpers.hex8(hookArgs.hookstruct.scanCode) ); addItem(hookArgs); }
First we define a new object of type KeyboardHook. Then we need to initialize this class with the form and then add an event handler for the keyboard messages.
The keyboard class is located in a library project of its own and in general uses SetWindowsHookEx. Here is only a small snippet of the libs code:
public void Start() { if (_hook != IntPtr.Zero) { //Unhook the previouse one this.Stop(); } _hookDeleg = new HookProc(HookProcedure); _hook = NativeMethods.SetWindowsHookEx( NativeMethods.WH_KEYBOARD_LL, _hookDeleg, NativeMethods.GetModuleHandle(null), 0); if (_hook == IntPtr.Zero) { throw new SystemException("Failed acquiring of the hook."); } }
I wrote the code as a lib to be able to make it usable for VB programmers to.
Keytest3AKvb
Here is the code to use the keybd lib in compact framework VB:
Imports KeybdHook Public Class Form1 Dim WithEvents _keyhook As KeybdHook.KeyHook.KeyboardHook Dim _bForwardKeys As Boolean = False Private Sub Form1_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load initListView() _keyhook = New KeybdHook.KeyHook.KeyboardHook(Me) 'AddHandler _keyhook.HookEvent, AddressOf hookProc 'do not use or you will get messages twice win32.AllKeys(True) End Sub Public Sub hookProc(ByVal sender As Object, ByVal hookArgs As KeybdHook.KeyHook.KeyboardHook.HookEventArgs) Handles _keyhook.HookEvent 'System.Diagnostics.Debug.WriteLine("hookProc: " + hookArgs.wParam.ToInt32().ToString("X8")) addItem(hookArgs) Select Case hookArgs.wParam.ToInt32() Case WindowsMessages.WM_KEYDOWN System.Diagnostics.Debug.WriteLine("WM_KEYDOWN: " + hookArgs.hookstruct.vkCode.ToString("X8")) Case WindowsMessages.WM_KEYUP System.Diagnostics.Debug.WriteLine("WM_KEYUP: " + hookArgs.hookstruct.vkCode.ToString("X8")) End Select End Sub
Here is the screen of Keytest3AKvb:
Downloads
Code and binaries at: CodePlex
Update 11/2020: Code now also in my github account: github.com/hjgode
Hi, I have tried to use your code for the keyboard approach but after creating the hook I have problem using MessageBoxes in my application, I can displat them but I cannot close them in any way, it seems that my application lost any focus.
Best Regards
Lorenzo
@Lorenzo:
try a message box in the KeyTest3AKcs project. Does this work?
You may have produced a dead lock. Are you using a modal (ShowDialog()) or modeless (Show()) messagebox?
Your question is too generic. Try the example and extend it and learn.
~Josef
Hi
I have the same problem as Lorenzo. if in the Keytest3AKcs project, if after the line
addItem(hookArgs);
you add the line
MessageBox.Show(“Key Pressed”);
you then can’t dismiss the message
Thanks
Pete
Hi Josef,
I have solved the problem using a non modal showdialog, the only strange thing that I have now is that after a while working the program stops catching the hooks, if I close the form and reopen it start working again
Best Regards
Lorenzo
Hello Lorenzo
I am sorry, but I normally use these hooks only in native apps. CF is a mess in that…
You should really avoid using function keys in your app.
~Josef
Hi Josef,
I know but it was a customer mandatory requirement, if I found a solution I will post it back to you
Thank you again
Lorenzo