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 IMessageFilter
active, F1 and F2 can be captured
After removing IMessageFilter no F1 and F2 keys are being captured
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 inside 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
don't 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 works 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:
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"));
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)
{
addItem(m);
Keys k = (Keys)m.WParam.ToInt32();
System.Diagnostics.Debug.WriteLine("msghandler2: " +
m.HWnd.ToInt32().ToString("X8") +
", " + ((WindowsMessages.WM_MESG)m.Msg).ToString() +
", " + m.WParam.ToInt32().ToString("X8") +
", " + m.LParam.ToInt32().ToString("X8"));
if (_bForwardKeys == false)
return true; else
return false;
}
return false; }
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 (i.e., menu (F1/F2), Volume Up/Down (F6F7)). There is an
additional 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
{
Keyboard.KeyHook.KeyboardHook _keyboardHook;
public Keytest3AKcs()
{
InitializeComponent();
win32.AllKeys(mnuAllkeys.Checked);
...
try
{
_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.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)
{
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)
win32.AllKeys(True)
End Sub
Public Sub hookProc(ByVal sender As Object, ByVal hookArgs _
As KeybdHook.KeyHook.KeyboardHook.HookEventArgs) Handles _keyhook.HookEvent
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.