Windows Mobile: CF how to catch F1 and F2 in WEH

Print This Post Print This Post

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

Leave a Reply