Tibi
Tibi

Reputation: 3875

Pretty key names in C# (Forms)

I have a combobox which is populated by the Keys enumeration (winforms).

The problem is that the key names are not very clear for inexperienced users. For example, the average user may not know what 'OemPipe', or 'HanjaMode' means. So, how can I solve this issue, and have some better key names?

I'm thinking of making a dictionary with the keys and their names, but populating the dictionary myself is very time consuming.

Upvotes: 6

Views: 6916

Answers (6)

Kevin McCormick
Kevin McCormick

Reputation: 2398

I assume you are allowing the user to assign keys from within your application (such as shortcut keys or game controls). Unfortunately, there is no easy way to get friendly descriptions for keys (Microsoft does not provide one or equivalent API), so you will need to create a mapping on your own.

As the currently accepted answer shows, using a resource file is a great way to do this to allow for internationalization of your application.

For reference, here is a complete brute-force implementation of the Keys enumeration I wrote awhile ago: (using a resource file is still recommended, however)

public static string GetDescription(Keys key)
{
    switch (key)
    {
        //letters
        case Keys.A: case Keys.B: case Keys.C: case Keys.D: case Keys.E: case Keys.F:
        case Keys.G: case Keys.H: case Keys.I: case Keys.J: case Keys.K: case Keys.L:
        case Keys.M: case Keys.N: case Keys.O: case Keys.P: case Keys.Q: case Keys.R:
        case Keys.S: case Keys.T: case Keys.U: case Keys.V: case Keys.W: case Keys.X:
        case Keys.Y: case Keys.Z:
            return Enum.GetName(typeof(Keys), key);

        //digits
        case Keys.D0: 
            return "0";
        case Keys.NumPad0:
            return "Number Pad 0";
        case Keys.D1: 
            return "1";
        case Keys.NumPad1:
            return "Number Pad 1";
        case Keys.D2: 
            return "2";
        case Keys.NumPad2:
            return "Number Pad 2";
        case Keys.D3: 
            return "3";
        case Keys.NumPad3:
            return "Number Pad 3";
        case Keys.D4: 
            return "4";
        case Keys.NumPad4:
            return "Number Pad 4";
        case Keys.D5: 
            return "5";
        case Keys.NumPad5:
            return "Number Pad 5";
        case Keys.D6: 
            return "6";
        case Keys.NumPad6:
            return "Number Pad 6";
        case Keys.D7: 
            return "7";
        case Keys.NumPad7:
            return "Number Pad 7";
        case Keys.D8: 
            return "8";
        case Keys.NumPad8:
            return "Number Pad 8";
        case Keys.D9: 
            return "9";
        case Keys.NumPad9:
            return "Number Pad 9";

        //punctuation
        case Keys.Add:
            return "Number Pad +";
        case Keys.Subtract:
            return "Number Pad -";
        case Keys.Divide:
            return "Number Pad /";
        case Keys.Multiply:
            return "Number Pad *";
        case Keys.Space:
            return "Spacebar";
        case Keys.Decimal:
            return "Number Pad .";

        //function
        case Keys.F1:   case Keys.F2:   case Keys.F3:   case Keys.F4:   case Keys.F5:
        case Keys.F6:   case Keys.F7:   case Keys.F8:   case Keys.F9:   case Keys.F10:
        case Keys.F11:  case Keys.F12:  case Keys.F13:  case Keys.F14:  case Keys.F15:
        case Keys.F16:  case Keys.F17:  case Keys.F18:  case Keys.F19:  case Keys.F20:
        case Keys.F21:  case Keys.F22:  case Keys.F23:  case Keys.F24:
            return Enum.GetName(typeof(Keys), key);

        //navigation
        case Keys.Up:
            return "Up Arrow";
        case Keys.Down:
            return "Down Arrow";
        case Keys.Left:
            return "Left Arrow";
        case Keys.Right:
            return "Right Arrow";
        case Keys.Prior:
            return "Page Up";
        case Keys.Next:
            return "Page Down";
        case Keys.Home:
            return "Home";
        case Keys.End:
            return "End";

        //control keys
        case Keys.Back:
            return "Backspace";
        case Keys.Tab:
            return "Tab";
        case Keys.Escape:
            return "Escape";
        case Keys.Enter:
            return "Enter";
        case Keys.Shift:    case Keys.ShiftKey:
            return "Shift";
        case Keys.LShiftKey:
            return "Shift (Left)";
        case Keys.RShiftKey:
            return "Shift (Right)";
        case Keys.Control: case Keys.ControlKey:
            return "Control";
        case Keys.LControlKey:
            return "Control (Left)";
        case Keys.RControlKey:
            return "Control (Right)";
        case Keys.Menu: case Keys.Alt:
            return "Alt";
        case Keys.LMenu:
            return "Alt (Left)";
        case Keys.RMenu:
            return "Alt (Right)";
        case Keys.Pause:
            return "Pause";
        case Keys.CapsLock:
            return "Caps Lock";
        case Keys.NumLock:
            return "Num Lock";
        case Keys.Scroll:
            return "Scroll Lock";
        case Keys.PrintScreen:
            return "Print Screen";
        case Keys.Insert:
            return "Insert";
        case Keys.Delete:
            return "Delete";
        case Keys.Help:
            return "Help";
        case Keys.LWin:
            return "Windows (Left)";
        case Keys.RWin:
            return "Windows (Right)";
        case Keys.Apps:
            return "Context Menu";

        //browser keys
        case Keys.BrowserBack:
            return "Browser Back";
        case Keys.BrowserFavorites:
            return "Browser Favorites";
        case Keys.BrowserForward:
            return "Browser Forward";
        case Keys.BrowserHome:
            return "Browser Home";
        case Keys.BrowserRefresh:
            return "Browser Refresh";
        case Keys.BrowserSearch:
            return "Browser Search";
        case Keys.BrowserStop:
            return "Browser Stop";

        //media keys
        case Keys.VolumeDown:
            return "Volume Down";
        case Keys.VolumeMute:
            return "Volume Mute";
        case Keys.VolumeUp:
            return "Volume Up";
        case Keys.MediaNextTrack:
            return "Next Track";
        case Keys.Play:
        case Keys.MediaPlayPause:
            return "Play";
        case Keys.MediaPreviousTrack:
            return "Previous Track";
        case Keys.MediaStop:
            return "Stop";
        case Keys.SelectMedia:
            return "Select Media";

        //IME keys
        case Keys.HanjaMode:    case Keys.JunjaMode:    case Keys.HangulMode:
        case Keys.FinalMode:    //duplicate values: Hanguel, Kana, Kanji  
        case Keys.IMEAccept:    case Keys.IMEConvert:   //duplicate: IMEAceept
        case Keys.IMEModeChange: case Keys.IMENonconvert:
            return null;

        //special keys
        case Keys.LaunchMail:
            return "Launch Mail";
        case Keys.LaunchApplication1:
            return "Launch Favorite Application 1";
        case Keys.LaunchApplication2:
            return "Launch Favorite Application 2";
        case Keys.Zoom:
            return "Zoom";

        //oem keys 
        case Keys.OemSemicolon: //oem1
            return ";";
        case Keys.OemQuestion:  //oem2
            return "?";
        case Keys.Oemtilde:     //oem3
            return "~";
        case Keys.OemOpenBrackets:  //oem4
            return "[";
        case Keys.OemPipe:  //oem5
            return "|";
        case Keys.OemCloseBrackets:    //oem6
            return "]";
        case Keys.OemQuotes:        //oem7
            return "'";
        case Keys.OemBackslash: //oem102
            return "/";
        case Keys.Oemplus:
            return "+";
        case Keys.OemMinus:
            return "-";
        case Keys.Oemcomma:
            return ",";
        case Keys.OemPeriod:
            return ".";

        //unsupported oem keys
        case Keys.Oem8:
        case Keys.OemClear:
            return null;

        //unsupported other keys
        case Keys.None:     case Keys.LButton:  case Keys.RButton:  case Keys.MButton:
        case Keys.XButton1: case Keys.XButton2: case Keys.Clear:    case Keys.Sleep:
        case Keys.Cancel:   case Keys.LineFeed: case Keys.Select:   case Keys.Print:
        case Keys.Execute:  case Keys.Separator: case Keys.ProcessKey: case Keys.Packet:
        case Keys.Attn:     case Keys.Crsel:    case Keys.Exsel:    case Keys.EraseEof:
        case Keys.NoName:   case Keys.Pa1:      case Keys.KeyCode:  case Keys.Modifiers:
            return null;

        default:
            throw new NotSupportedException(Enum.GetName(typeof(Keys), key));
    }
}

You can convert this to a resource file by running the following program, and then adding output.resx to your application as a resource.

static void Main(string[] args)
{
    using(ResXResourceWriter writer = new ResXResourceWriter("output.resx"))
    {
        //since there are duplicate values, we need to clumsily look at each name, then parse
        foreach (string name in Enum.GetNames(typeof(Keys)))
        {
            object value = Enum.Parse(typeof(Keys), name);
            string description = GetDescription((Keys)value); 

            if (description != null)
                writer.AddResource(new ResXDataNode(name, description));
        }
    }
}

This will give you a resource file that can be used in the manner explain in the accepted answer.

Upvotes: 5

Hans Passant
Hans Passant

Reputation: 941873

"Oem" means Original Equipment Manufacturer. In other words, the kind of company that makes keyboards. These names are special because on a 'regular' keyboard, there is no dedicated key to generate | or turning on Hanja radicals in Korean (guess). Getting a | requires holding down the Shift key on most layouts. Some keyboard manufacturers might add keys to the standard layout that do this.

Which should give you some pause, these keys are unlikely to be available on the user's keyboard so presenting them as possible shortcut keystrokes is not useful. More importantly, using the string you get out of Keys is a bad idea in itself. It will give you a heck of a headache when you need to localize your application some day so the other 5-some billion people in this world become paying customers.

Upvotes: 1

Marcus
Marcus

Reputation: 2480

If you only want to supply description for some keys, you can loop the System.Windows.Forms.Keys and supply a method that defaults to the Key enum name:

private void Form1_Load(object sender, EventArgs e)
{
   foreach (System.Windows.Forms.Keys key in Enum.GetValues(typeof(System.Windows.Forms.Keys)))
   {
       comboBoxKeys.Items.Add(new { Value = key, Description = GetDescription(key) });
   }

   comboBoxKeys.DisplayMember = "Description";
}

private string GetDescription(System.Windows.Forms.Keys key)
{
    switch(key)
    {
        case Keys.OemPipe:
            return "Better oem pipe description";

        case Keys.HanjaMode:
            return "Ninja mode";

        default:
            return key.ToString(); // default name
    }
}

Upvotes: 1

code4life
code4life

Reputation: 15794

You can assign attributes to the enumerations. This is the best way. Otherwise you will end up having to maintain parallel dictionaries or a growing list of switch-case statements.

Here's how you would mark up the enum:

public enum MyEnums
{
    [Description("OEM Pipe")]
    OemPipe,

    [Description("Hanja Mode")]
    HanjaMode
}

You can retrieve the Description attribute via an extension method:

public static string ToEnumDescription(this Enum value)
{
    FieldInfo fi = value.GetType().GetField(value.ToString());

    DescriptionAttribute[] attributes =
        (DescriptionAttribute[])fi.GetCustomAttributes(
        typeof(DescriptionAttribute),
        false);

    if (attributes != null &&
        attributes.Length > 0)
        return attributes[0].Description;
    else
        return value.ToString();
}

To actually retrieve the enumeration description, you would call it this way;

var enumAsText = theEnum.ToEnumDescription();

You could also do this:

MyEnums.OemPipe.ToEnumDescription();

Upvotes: 0

Dr. Wily's Apprentice
Dr. Wily's Apprentice

Reputation: 10280

Make a resource file that maps key names to a user-understandable string. If the resource file does not have a value for a particular key, then just go with the Key name (as you are doing now), so that way you only have to define the ones that are difficult to understand, and you don't have to do them all up front.

This also allows you to localize to different languages, if you like.

EDIT: Added code example. Assumption is that you have a resource file named "KeyNames.resx"

foreach (var key in Enum.GetValues(typeof(Keys)))
{
    var keyName = KeyNames.ResourceManager.GetString(key.ToString());
    if (keyName == null)
        keyName = key.ToString();

    comboBox1.Items.Add(keyName);
}

Upvotes: 7

We Are All Monica
We Are All Monica

Reputation: 13344

There's no way around writing the code yourself. Here's an approach you could use which is probably close to the minimal effort required, though:

string GetBaseKeyDescription(Keys k) {
    switch (k & ~Keys.Modifiers) {
        case Keys.OemPipe:
            return "Pipe |";
        case Keys.OemPeriod:
            return "Dot .";
        case Keys.HanjaMode:
            return "(Description of HanjaMode key)";
        default:
            return k.ToString();
    }
}

I'm not sure if you need the & ~Keys.Modifiers bit or not - if you do, you'll probably want to write more code to handle the modifiers - but I've done similar things before.

Upvotes: 0

Related Questions