Reputation: 3875
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
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
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
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
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
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
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