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