6

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
    }
}
Lieven Cardoen
  • 25,140
  • 52
  • 153
  • 244
  • 1
    Even stackoverflow gets its part of belgian politics :-) Funny thing people get frustrated by the use of second country language vs English :-) – Laurent S. Apr 11 '14 at 09:28
  • Well, the fact is that SHIFT is displayed on the keyboard, while MAJ is displayed on the screen... Dutch speaking Belgium doesn't know what MAJ means... – Lieven Cardoen Apr 11 '14 at 09:30
  • And they really should be using QWERTY instead of AZERTY because AZERTY was created for French speaking people... (but even I am using AZERTY, that's what is used in schools to learn how to type so...). – Lieven Cardoen Apr 11 '14 at 09:32
  • I just found it funny but must admit I didn't think about the usability problem. Which is quite relative though: when a letter is underlined it means you can access it using `alt` in combination with the letter, that's a convention you have to know. Seems to me remembering MAJ is actually SHIFT shouldn't be that much of a problem, especially cause "shift" isn't always written on the keyboad neither and sometimes replaced by the "big up-arrow" sign. – Laurent S. Apr 11 '14 at 09:42
  • True that. I'll send them your email address ;-) if they want further details. But it isn't all that simple. The first time I saw MAJ, I didn't know either what it was... I could as well have been ALT or CTRL for all I knew... – Lieven Cardoen Apr 11 '14 at 10:02
  • @Lieven please explain what is the further goal of the question. Why is it worth another bounty to you. Solutions how to fix your application is IMHO already available. Bug by Microsoft vs bug by Infragistics seems useless question to me. Voting -1 – xmojmr May 02 '14 at 09:57
  • Because I would like to know why the user32.dll gives back MAJ while there's no MAJ in the klc file to be found... Is there a way to know the code in the user32.dll? Is it a bug or intended? – Lieven Cardoen May 02 '14 at 11:30
  • Also, is there a way to contact Microsoft to get further information, file a bug report, ...? – Lieven Cardoen May 02 '14 at 11:31
  • I think Hans meant that that tool only produces keyboard layouts with English key names. Only really for arranging keys? So is it a bug in user32.dll? I doubt it. On Windows 8.1 there are three AZERTY keyboard layouts for Belgium: Period, Comma and French. Of those, starting an application with either Period or French AZERTY resulted in MAJ, but Comma gave SHIFT. So the key names do come from the keyboard layout. Period is the layout installed by default when Dutch (Belgium) language input is installed. – kjbartel May 02 '14 at 14:23
  • 1
    You can't expect your users to have the keyboard layout with "SHIFT" in it if indeed there are multiple layouts available on previous versions of Windows. You also shouldn't provide your users with a new keyboard layout just to use your software. So I don't think Microsoft can or will fix this problem for you. Infragistics can fix it by changing to using the KeysConverter. Should be a really simple change. If they won't then you can either try using the standard WinForms menus or try build your own Infragistics compatible menu deriving from their base classes and implementing the interfaces. – kjbartel May 02 '14 at 14:29
  • 1
    @Lieven full source code of user32.dll is copyrighted by Microsoft. You can find out what is "probably" inside by studying source codes of [Wine](http://www.winehq.org) or [ReactOS](http://www.reactos.org) projects. Disassembly will not tell you much (I guess). There is surely a way how to file bug reports to Microsoft, providing you are their paying customer. Use their website to find support phone numbers – xmojmr May 04 '14 at 11:26
  • I don't think you need to know what user32.dll actually does. You can infer what it does by the fact that the key name changes by changing the keyboard layout and nothing else (same culture). Therefore user32.dll gets the key name from the keyboard layout. – kjbartel May 08 '14 at 02:46
  • kjbartel It doesn't get the correct key name from the keyboard layout. – Lieven Cardoen May 26 '14 at 09:40
  • That's the whole point. – Lieven Cardoen May 26 '14 at 09:42

3 Answers3

5

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.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
  • Hi Hans, if I open up Text Services and Input Languages on Windows, then there is a Dutch(Belgium) --> Keyboard --> Belgian (Period) entry. If I ask for the properties of this keyboard, I can clearly see that it mentions Shift (and not MAJ), so I'm still not sure what this whole problem is about. http://i.imgur.com/iffLysQ.png http://i.imgur.com/kwbDgww.png – Lieven Cardoen Apr 25 '14 at 12:22
  • Hi Hans, If I use your tool, and save my existing layout, then in the klc file I can only find Shift. There's no Maj in there. So why does the user32.dll return MAJ instead of SHIFT? – Lieven Cardoen Apr 28 '14 at 14:10
  • With your tool I mean the tool you mentioned off course. – Lieven Cardoen Apr 28 '14 at 14:11
  • I specifically documented that, "defaults to English names". And gave the recommendation "edit the key names". Strange disconnect, maybe I used the wrong keyboard layout to type the answer :) – Hans Passant Apr 28 '14 at 14:26
  • Hans, I'm not getting you. I opened up the klc file and it says: 2a Shift. So why does the user32.dll return MAJ? – Lieven Cardoen Apr 28 '14 at 14:33
  • This is getting weirder and weirder. If I make a copy of the klc file and change Shift by CustomShiftText, then, after doing the setup of the new keyboard layout, CustomShiftText does appear. – Lieven Cardoen Apr 28 '14 at 15:07
  • The next thing I've done is create a copy of saved existing keyboard layout. I took that klc, made a copy, changed nothing and installed the new keyboard. With the new keyboard, that is in fact exactly the same as my initial one, Shift appears. With my initial one, MAJ appears. – Lieven Cardoen Apr 28 '14 at 15:12
4

Assuming that you're using Windows Forms have you tried using the Windows.Forms.KeysConverter class for your conversion? Looking at the source code for the normal winforms MenuItem control, the string for shortcut keys is obtained by calling the KeysConverter.ConvertToString(Object value) method. The KeysConverter in-turn gets the string from System.Windows.Forms.resources ("toStringShift" resource key for Shift). Those resource strings are all localized depending on which .Net language packs are installed on the computer and the culture used to run the app. So from what I can see Windows Forms doesn't actually use user32.dll.

Try the following code to test and try both nl-BE and fr-BE (you'll of course need both .Net language packs installed):

static void Main()
{
    var culture = new CultureInfo("nl-BE");
    Thread.CurrentThread.CurrentCulture = culture;
    Thread.CurrentThread.CurrentUICulture = culture;
    var keys = Keys.Shift | Keys.N;
    var keysstring = new KeysConverter().ConvertToString(keys);
}

If this does work correctly then I'd say that Infragistics is using the wrong API. I'd expect them to use the normal .Net KeysConverter rather than the call to user32.dll.

Update: I couldn't install the language packs on my computer but I extracted the files from the installers and can confirm that the System.Windows.Forms.resources.dll assemblies for each language do contain the correct key names which you're looking for.

System.Windows.Forms.fr.resx:

<data name="toStringShift" xml:space="preserve">
  <value>Maj</value>
</data>

System.Windows.Forms.nl.resx:

<data name="toStringShift" xml:space="preserve">
  <value>Shift</value>
</data>
kjbartel
  • 10,381
  • 7
  • 45
  • 66
  • do you have any links to support your statement that KeyConverter gets the string from...? – xmojmr Apr 29 '14 at 16:49
  • 1
    @xmojmr Unfortunately no. I used JetBrains dotPeek to open the System.Windows.Forms.dll file and looked at the source file. It downloaded the public symbols from Microsoft's symbol servers. You could enable .Net source debugging, following the instructions [here](http://msdn.microsoft.com/en-us/library/cc667410.aspx), and step through the code to make sure yourself. – kjbartel Apr 29 '14 at 17:00
  • 2
    I see, you were right. the http://referencesource.microsoft.com/#System.Windows.Forms/ndp/fx/src/winforms/Managed/System/WinForms/KeysConverter.cs#44 initializes the keyNames table from resources – xmojmr Apr 29 '14 at 17:15
  • 1
    +1 even though it does not answer the question "is it a bug in user32.dll..Does Infragistics use the correct user32.dll api?" – xmojmr Apr 29 '14 at 17:36
  • 2
    @xmojmr Well it effectively answers the question. No it doesn't answer whether user32.dll has a bug or not but it does show that normal WinForms controls do not use that code so it could be argued that neither should the Infragistics controls. So if the KeysConverter does work I'd say that yes it is up to Infragistics to also get this right. Perhaps there is some reason they use the native call rather than normal .Net? I can't actually test if this works or not as I'm using Win8.1 which would require me to install an entire Windows language pack to get a different .Net language pack. – kjbartel Apr 29 '14 at 23:37
  • This really looks promising. Still wondering though what's wrong with that user32.dll call. Can't seem to find support, forums or a site to get some Microsoft support regarding this... Asked Infragistics to have a look at your answer. Thx. – Lieven Cardoen May 02 '14 at 09:42
2

If you are looking for solution of a problem rather then answer to question who should be blamed? Microsoft Kernel developers, Microsoft Office developers, Infragistics developers.. and you are using WPF then this

KeyGesture gesture;
MenuItem menuItem;
...
menuItem.InputGestureText = gesture.GetDisplayStringForCulture(System.Threading.Thread.CurrentThread.CurrentUICulture);

Works for me to display shortcuts the way I need them. I did not test it with esoteric or Van-Damme cultures but this code should be language neutral.

How exactly does the code work can be analyzed using Visual Studio debugger, source code of the KeyGesture.GetDisplayStringForCulture is also browsable online → http://referencesource.microsoft.com/#PresentationCore/src/Core/CSharp/System/Windows/Input/Command/KeyGesture.cs#fbe5780461e3961d

EDIT BTW: the WPF code seems to end in ModifierKeyConverters

internal static string MatchModifiers(ModifierKeys modifierKeys) { string modifiers = String.Empty; switch (modifierKeys) { case ModifierKeys.Control: modifiers="Ctrl";break; case ModifierKeys.Shift : modifiers="Shift";break; case ModifierKeys.Alt : modifiers="Alt";break; case ModifierKeys.Windows: modifiers="Windows";break; } return modifiers; }

Community
  • 1
  • 1
xmojmr
  • 8,073
  • 5
  • 31
  • 54