Is it possible to create a keyboard layout that is identical to the keyboard used?
Asked Answered
W

2

9

If I need to generate a keyboard layout for customization to the user that looks like his/her keyboard, how can I do it?

For instance something like this:

enter image description here

French, Swedish, English, Canadian, etc will have different layouts, right. Is this a lot of work or just a matter of using some sort of built in .NET regional classes?

Wharve answered 27/1, 2011 at 21:46 Comment(6)
Looks like a nice add-on for someone to write ;)Bartholemy
You can get pictures of the international keyboard layouts simply by using Google Images. Try googling for French keyboard layout, for example.Tungstite
Yes but I need to draw it from scratch with a predefined style. Also if I use layouts, then I would need to store all the images, right? I need to do this at runtime dynamically (when the customize dialog is launched).Wharve
@Oded: do you want to do this for the benefit of everyone? :OWharve
@Joan Venge - I was thinking of a VS add-on to map the VS keyboard shortcuts, but since there are combined ones, I don't see how a useful map can be created.Bartholemy
Thanks I see what you mean now.Wharve
S
14

A key press generates a hardware event that reports a "scan code" to the Windows operating system. This scan code is then converted into a "virtual key code" based on the scan code along with other keyboard state factors (Caps Lock state, Shift/Alt/Ctrl keystate, and also any pending dead key strokes). The converted VK value is what is reported by the KeyDown event etc.

The conversion from scan code to VK code is dependent on the current input locale - simplistically, the input locale defines a mapping between scan codes and virtual key codes. See the MSDN documentation for a full description of keyboard input.

By reversing this lookup process, it is possible to determine the scan code that corresponds to each virtual key code (of course, the same scan code will map to multiple VK codes because of shift/ctrl/alt state etc). The Win32 API provides the MapVirtualKeyEx function to perform this mapping, by using the MAPVK_VK_TO_VSC_EX option. You can use this to determine which scan code generates the particular VK code.

Unfortunately, this is as far as you can go programmatically - there is no way to determine the physical layout of the keyboard or the location of the key for each scan code. However, most physical keyboards are wired the same way, so (for example) the top-left key will have the same scan code on most physical keyboard designs. You can use this assumed convention to infer the physical location corresponding to a scan code, depending on the basic physical keyboard layout (101-key, 102-key etc). It's not guaranteed, but it's a pretty safe guess.

The following code is an excerpt from a larger keyboard-handling library that I wrote (I've been intending to open-source it but haven't had the time). The method initializes an array (this._virtualKeyScanCodes) that is indexed by VK code for a given input locale (stored in this._inputLanguage which is declared as a System.Windows.Forms.InputLanguage. You can use the array to determine the scan code that corresponds to the VK code by examining e.g. this._virtualKeyScanCodes[VK_NUMPAD0] - if the scan code is zero, then that VK is not available on the keyboard in the current input locale; if it is non-zero, it is the scan code from which you can infer the physical key.

Unfortunately, matters are slightly more complicated than this when you get into the realms of dead keys (multiple key combinations that produce accented characters, for example). That's all far too complicated to go into right now, but Michael S. Kaplan wrote a detailed series of blog posts back in 2006 if you want to explore this further. Good luck!

private void Initialize()
{
    this._virtualKeyScanCodes = new uint[MaxVirtualKeys];

    // Scroll through the Scan Code (SC) values and get the Virtual Key (VK)
    // values in it. Then, store the SC in each valid VK so it can act as both a 
    // flag that the VK is valid, and it can store the SC value.
    for (uint scanCode = 0x01; scanCode <= 0xff; scanCode++)
    {
        uint virtualKeyCode = NativeMethods.MapVirtualKeyEx(
            scanCode, 
            NativeMethods.MAPVK_VSC_TO_VK, 
            this._inputLanguage.Handle);
        if (virtualKeyCode != 0)
        {
            this._virtualKeyScanCodes[virtualKeyCode] = scanCode;
        }
    }

    // Add the special keys that do not get added from the code above
    for (KeysEx ke = KeysEx.VK_NUMPAD0; ke <= KeysEx.VK_NUMPAD9; ke++)
    {
        this._virtualKeyScanCodes[(uint)ke] = NativeMethods.MapVirtualKeyEx(
            (uint)ke, 
            NativeMethods.MAPVK_VK_TO_VSC, 
            this._inputLanguage.Handle);
    }

    this._virtualKeyScanCodes[(uint)KeysEx.VK_DECIMAL] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_DECIMAL, NativeMethods.MAPVK_VK_TO_VSC, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_DIVIDE] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_DIVIDE, NativeMethods.MAPVK_VK_TO_VSC, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_CANCEL] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_CANCEL, NativeMethods.MAPVK_VK_TO_VSC, this._inputLanguage.Handle);

    this._virtualKeyScanCodes[(uint)KeysEx.VK_LSHIFT] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_LSHIFT, NativeMethods.MAPVK_VK_TO_VSC, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_RSHIFT] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_RSHIFT, NativeMethods.MAPVK_VK_TO_VSC, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_LCONTROL] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_LCONTROL, NativeMethods.MAPVK_VK_TO_VSC, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_RCONTROL] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_RCONTROL, NativeMethods.MAPVK_VK_TO_VSC, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_LMENU] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_LMENU, NativeMethods.MAPVK_VK_TO_VSC, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_RMENU] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_RMENU, NativeMethods.MAPVK_VK_TO_VSC, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_LWIN] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_LWIN, NativeMethods.MAPVK_VK_TO_VSC, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_RWIN] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_RWIN, NativeMethods.MAPVK_VK_TO_VSC, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_PAUSE] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_PAUSE, NativeMethods.MAPVK_VK_TO_VSC_EX, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_VOLUME_UP] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_VOLUME_UP, NativeMethods.MAPVK_VK_TO_VSC_EX, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_VOLUME_DOWN] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_VOLUME_DOWN, NativeMethods.MAPVK_VK_TO_VSC_EX, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_VOLUME_MUTE] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_VOLUME_MUTE, NativeMethods.MAPVK_VK_TO_VSC_EX, this._inputLanguage.Handle);

    this._virtualKeyScanCodes[(uint)KeysEx.VK_MEDIA_NEXT_TRACK] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_MEDIA_NEXT_TRACK, NativeMethods.MAPVK_VK_TO_VSC_EX, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_MEDIA_PREV_TRACK] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_MEDIA_PREV_TRACK, NativeMethods.MAPVK_VK_TO_VSC_EX, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_MEDIA_PLAY_PAUSE] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_MEDIA_PLAY_PAUSE, NativeMethods.MAPVK_VK_TO_VSC_EX, this._inputLanguage.Handle);
    this._virtualKeyScanCodes[(uint)KeysEx.VK_MEDIA_STOP] =
        NativeMethods.MapVirtualKeyEx(
            (uint)KeysEx.VK_MEDIA_STOP, NativeMethods.MAPVK_VK_TO_VSC_EX, this._inputLanguage.Handle);

    this._stateController = new KeyboardStateController();
    this._baseVirtualKeyTable = new VirtualKeyTable(this);
}

Edit: see also this question which is similar to yours.

Semivowel answered 27/1, 2011 at 23:14 Comment(1)
+1 This is better than what we were discussing in the comments on my answer :)Fibrillation
F
3

There is no built-in .NET class that contains the keyboard layouts. The keyboard layouts are a function of the Operating System, usually Windows. By the time .NET gets involved, the key pressed has been converted from a hardware event to a software one. If you want to see this in action, find 2 keyboard layouts where a key has moved between them. Setup a dummy app with an event handler on the Key_Down event, and then note that the event args are identical; if you pressed the - key you pressed the - key regardless of where that - key is located.

Fibrillation answered 27/1, 2011 at 21:58 Comment(7)
Thanks, that makes sense. But how do they do these for say Russian, Chinese keyboards? Like what key returns Keys.A, etc. Also how do some apps do this thing where they are able to recreate the layout of the keyboard you are using on screen. Does windows OSD keyboard do that?Wharve
They may be using the windows OSD; since that is an operating system utility, it IS in fact layout aware; see microsoft.com/windowsxp/using/accessibility/osklayout.mspxFibrillation
Thanks but how can they use it? Isn't it another app like it doesn't seem like it has an API to hook into, right?Wharve
Well, it IS a Microsoft app, which means it PROBABLY can be controlled by either Win32 or (more likely) mucking about in the registry, setting some values, and then using Process.Start() to "display" it. I haven't used it myself; you may want to try elevating that to its own question perhaps.Fibrillation
Thanks, but that's way to hacky than I can accept. My solution uses all WPF with custom styles and logic so hacking the OSD app will look way out of place.Wharve
Oh, I'm not recommending it myself :) I was just saying that if you WERE going to go down that route, we were hitting the limit of what I actually know about how to do that.Fibrillation
Yeah that's what I assumed too :OWharve

© 2022 - 2024 — McMap. All rights reserved.