Search code examples
c#winformswinapikeyboardpinvoke

Get correct keyboard scan code for arrow keys


I am trying to obtain keyboard scan codes from virtual key codes in a Windows Forms application using the MapVirtualKey Windows function. The declaration of the P/Invoke stuff is:

private const uint MAPVK_VK_TO_VSC = 0;

[DllImport("user32.dll", 
    CallingConvention = CallingConvention.StdCall, 
    CharSet = CharSet.Unicode, 
    EntryPoint = "MapVirtualKey", 
    SetLastError = true, 
    ThrowOnUnmappableChar = false)]
private static extern uint MapVirtualKey(
    uint uCode, 
    uint uMapType);

I have overridden the OnPreviewKeyDown method of my application main window class (derived from System.Windows.Forms.Form); in here I take the value of the KeyCode property of the given PreviewKeyDownEventArgs object and pass it to the MapVirtualKey method (I assume that the value is actually a virtual key code).

protected override void OnPreviewKeyDown(PreviewKeyDownEventArgs e)
{
    uint uCode = (uint)e.KeyCode;
    uint scanCode = MapVirtualKey(uCode, MAPVK_VK_TO_VSC);

    this.HandleScanCode(scanCode);

    base.OnPreviewKeyDown(e);
}

The problem is that I receive wrong scan codes for the arrow keys (I recieve scan codes for the num-pad arrow keys instead). For instance, if the up-arrow key is pressed, I expect the scan code to be 200 instead of 72.


Solution

  • It seems, that this problem belongs to enhanced keyboards only. The MapVirtualKey method is agnostic to left- and right-hand key codes. This means, the key codes 38 Up and 104 NumPad8 result in the same scan code because the Up key is an enhanced key.

    I decided to intercept the WM_KEYDOWN message directly because the lParam argument of that message contains both the wanted scan code and a bit indicating whether the key is an enhanced key, or not. This solved my problem...

    protected override void WndProc(ref Message m)
    {
        base.WndProc(ref m);
    
        switch (m.Msg)
        {
            case WM_KEYDOWN:
            {
                int lParam = m.LParam.ToInt32();
                int scanCode = (lParam >> 16) & 0x000000ff; // extract bit 16-23
                int ext = (lParam >> 24) & 0x00000001; // extract bit 24
                if (ext == 1) 
                    scanCode += 128;
    
                this.HandleScanCode(scanCode);
                break;
            }
        }
    }
    

    If a key is an enhanced key the wanted scan code can be calculated by adding 128.