Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

SHIFT Shortcut being displayed as MAJ for culture "nl-BE" when it should be SHIFT (C#.NET)

After a long discussion with Infragistics it appears that ShortCuts with SHIFT are displayed as MAJ in my culture "nl-BE". First of all, the culture "nl-BE" and AZERTY is somewhat of a strange thing. Read http://en.wikipedia.org/wiki/AZERTY if want to know more. The important quote is:

The other keys are identical, even though traditionally the names of special keys are printed on them in English. This is because Belgium is predominantly bilingual (French-Dutch) and officially trilingual (a third language, German, is spoken in the East Cantons).

So MAJ is printed as SHIFT. In Office for instance, Shortcuts with SHIFT are displayed as SHIFT. In the Infragistics controls however they are displayed as MAJ. And this frustrates our customers.

So, after a discussion with Infragistics they claim that it's a Windows Api call that is returning the MAJ instead of SHIFT. I have gotten a sample project from them which shows the behavior. So now my question is why the Windows Api call doesn't return SHIFT, and if it's normal, then how does Office do it to display it correct?

The code to get the text of the key is :

NativeWindowMethods.GetKeyNameText((int)scanCode, sb, 256);

and

class NativeWindowMethods
{
    #region MapVirtualKey
    [DllImport("user32.dll")]
    internal static extern int MapVirtualKey(uint uCode, uint uMapType);
    #endregion //MapVirtualKey

    #region GetKeyNameText
    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    internal static extern int GetKeyNameText(
        int lParam, 
        [MarshalAs(UnmanagedType.LPWStr), Out]System.Text.StringBuilder str, 
        int size);
    #endregion //GetKeyNameText
}

In case of the Shiftkey, the scancode is 2752512 (2a) and MAJ is returned.

So, what are my questions?

  • Is it normal that MAJ is returned for the culture "nl-BE"? Or is it a bug in user32.dll?
  • If Office gets it right, isn't it up to Infragistics to also get it right?
  • Does Infragistics use the correct user32.dll api call?

For completeness I'll paste the full code for the Utilities class. From the Form next call is done:

systemLocalizedString = Utilities.GetLocalizedShortcutString(shortcut);

With shortcut = ShiftF12. After the call, systemLocalizedString is equal to "MAJ+F12".

UPDATE: With the help of Hans Passant I downloaded the Microsoft Keyboard Layout Creator and exported my current Keyboard Layout. In the .klc file there's no MAJ to be found, only Shift (2a Shift for instance). So why does the user32.dll return MAJ? Even weirder is that when I make a copy of the .klc file and install it as a new keyboard, then suddenly the user32.dll does return Shift for that newly installed keyboard (while it's an exact copy).

Utilities.cs:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WindowsFormsApplication1
{
    class Utilities
    {

        #region GetLocalizedShortcutString

        /// <summary>
        /// Returns the localized string for the specified <b>Shortcut</b>
        /// </summary>
        /// <param name="shortcut">Shortcut to localize</param>
        /// <param name="separator">Character used to separate multiple keys in the shortcut</param>
        /// <returns>A string containing the localized description of the shortcut based on the currently mapped keyboard layout</returns>
        public static string GetLocalizedShortcutString(Shortcut shortcut, char separator = '+')
        {
            if (shortcut == Shortcut.None)
                return string.Empty;

            return GetLocalizedKeyString((Keys)shortcut, separator);
        }
        #endregion //GetLocalizedShortcutString

        #region GetLocalizedKeyString

        /// <summary>
        /// Returns the localized string for the specified <b>Keys</b>
        /// </summary>
        /// <param name="keys">Keys to localize</param>
        /// <param name="separator">Character used to separate multiple keys</param>
        /// <returns>A string containing the localized description of the keys based on the currently mapped keyboard layout</returns>
        public static string GetLocalizedKeyString(Keys keys, char separator)
        {
            bool alt = ((long)keys & (long)Keys.Alt) != 0;
            bool ctrl = ((long)keys & (long)Keys.Control) != 0;
            bool shift = ((long)keys & (long)Keys.Shift) != 0;

            // get the key involved
            long value = (long)keys & 0xffff;

            Keys key = (Keys)Enum.ToObject(typeof(Keys), value);
            System.Text.StringBuilder sb = new System.Text.StringBuilder();

            if (alt && key != Keys.Menu)
            {
                sb.Append(GetLocalizedKeyStringHelper(Keys.Menu));
                sb.Append(separator);
            }

            if (ctrl && key != Keys.ControlKey)
            {
                sb.Append(GetLocalizedKeyStringHelper(Keys.ControlKey));
                sb.Append(separator);
            }

            if (shift && key != Keys.ShiftKey)
            {
                sb.Append(GetLocalizedKeyStringHelper(Keys.ShiftKey));
                sb.Append(separator);
            }

            sb.Append(GetLocalizedKeyStringHelper(key));
            return sb.ToString();
        }
        #endregion //GetLocalizedKeyString

        #region GetLocalizedKeyStringHelper
        private static string GetLocalizedKeyStringHelper(Keys key)
        {
            string localizedKey = GetLocalizedKeyStringUnsafe(key);

            if (localizedKey == null || localizedKey.Length == 0)
                return key.ToString();

            return localizedKey;
        }
        #endregion //GetLocalizedKeyStringHelper

        #region GetLocalizedKeyStringUnsafe
        private static string GetLocalizedKeyStringUnsafe(Keys key)
        {
            // strip any modifier keys
            long keyCode = ((int)key) & 0xffff;

            System.Text.StringBuilder sb = new System.Text.StringBuilder(256);

            long scanCode = NativeWindowMethods.MapVirtualKey((uint)keyCode, (uint)0);

            // shift the scancode to the high word
            scanCode = (scanCode << 16);

            if (keyCode == 45 ||
                keyCode == 46 ||
                keyCode == 144 ||
                (33 <= keyCode && keyCode <= 40))
            {
                // add the extended key flag
                scanCode |= 0x1000000;
            }

            NativeWindowMethods.GetKeyNameText((int)scanCode, sb, 256);
            return sb.ToString();
        }
        #endregion //GetLocalizedKeyStringUnsafe
    }

    class NativeWindowMethods
    {
        #region MapVirtualKey
        [DllImport("user32.dll")]
        internal static extern int MapVirtualKey(uint uCode, uint uMapType);
        #endregion //MapVirtualKey

        #region GetKeyNameText
        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        internal static extern int GetKeyNameText(int lParam, [MarshalAs(UnmanagedType.LPWStr), Out]System.Text.StringBuilder str, int size);
        #endregion //GetKeyNameText
    }
}
like image 558
Lieven Cardoen Avatar asked Apr 11 '14 09:04

Lieven Cardoen


1 Answers

but even I am using AZERTY, that's what is used in schools to learn how to type so...

So yes, that's the problem. The only way you can get an AZERTY layout is to select keyboard layout titled "français (Belgique)" in Control Panel + Language + Add a language. As opposed to the "Nederlands (België)" layout, it has a QWERTY arrangement. The GetKeyNameText() winapi function returns strings that are encoded in the keyboard layout file. Which are of course in French for a keyboard layout named français so MAJ is the expected result. Windows does not have a keyboard layout available that speaks Dutch with an AZERTY arrangement.

All is not lost, Windows users often ask for custom keyboard layouts. So Microsoft made a tool available to create your own, the Microsoft Keyboard Layout Creator. It was primarily designed to re-arrange keys, a little extra elbow grease is required to make it do what you want. The tool doesn't let you directly edit the key descriptions and defaults to English names. You'll want to start with File + Load Existing Keyboard. Then File + Save Layout to save the layout to a .klc file. Open it in a text editor, Notepad is fine, locate the sections named KEYNAME and KEYNAME_EXT and edit the key names the way you want them.

Restart the utility (don't skip) and reload the .klc file. And build the setup package with Project + Build DLL.

like image 107
Hans Passant Avatar answered Oct 05 '22 04:10

Hans Passant