【问题标题】:Pretty key names in C# (Forms)C#(表单)中漂亮的键名
【发布时间】:2012-04-17 11:33:08
【问题描述】:

我有一个由Keys 枚举(winforms)填充的组合框。

问题是对于没有经验的用户来说,键名不是很清楚。例如,普通用户可能不知道“OemPipe”或“HanjaMode”是什么意思。那么,我该如何解决这个问题,并有一些更好的键名呢?

我正在考虑用键及其名称制作字典,但自己填充字典非常耗时。

【问题讨论】:

  • 这就是文档的用途
  • @JustinNiessner Keys 是来自 Windows 窗体 (System.Windows.Forms.Keys) 的枚举。
  • 您将不得不为每个值提出一个描述,这是耗时的部分。如何存储它们并不重要。
  • 为什么要向“最终用户”显示该枚举?他们需要查看所有值吗?也许您可以定义一个简单的规则来过滤枚举(例如,只显示 1 或 2 个字母值,如 A 或 F1)。
  • 考虑允许用户使用他们的键盘键入键而不是从组合框中选择它。我猜你仍然需要在他们键入后弄清楚键的名称(例如“向下箭头”),所以它并不能完全解决你的问题。

标签: c# winforms forms keyboard


【解决方案1】:

我假设您允许用户从您的应用程序中分配键(例如快捷键或游戏控件)。不幸的是,没有简单的方法来获得键的友好描述(Microsoft 不提供一个或等效的 API),因此您需要自己创建一个映射。

正如当前接受的答案所示,使用资源文件是实现应用程序国际化的好方法。

作为参考,这里是我前段时间写的 Keys 枚举的完整蛮力实现:(但仍然建议使用资源文件)

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

您可以通过运行以下程序将其转换为资源文件,然后将 output.resx 作为资源添加到您的应用程序中。

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

这将为您提供一个资源文件,可以按照接受的答案中解释的方式使用。

【讨论】:

    【解决方案2】:

    “Oem”是指原始设备制造商。换句话说,就是那种制造键盘的公司。这些名称很特别,因为在“常规”键盘上,没有专用键来生成| 或打开韩文汉字部首(猜想)。获取| 需要在大多数布局上按住 Shift 键。一些键盘制造商可能会将按键添加到执行此操作的标准布局中。

    这应该让您稍作停顿,这些键不太可能在用户的键盘上可用,因此将它们显示为可能的快捷键是没有用的。更重要的是,使用从 Keys 中得到的字符串本身就是一个坏主意。如果有一天您需要本地化您的应用程序,以便世界上其他 5 到 10 亿人成为付费客户,这会让您头疼不已。

    【讨论】:

    • 是的。我一直讨厌本地化,这是编程中最烦人的事情之一......
    【解决方案3】:

    如果您只想为某些键提供描述,您可以循环 System.Windows.Forms.Keys 并提供一个默认为 Key 枚举名称的方法:

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

    【讨论】:

      【解决方案4】:

      您可以为枚举分配属性。这是最好的方法。否则,您最终将不得不维护并行字典或不断增长的switch-case 语句列表。

      以下是您将如何标记枚举:

      public enum MyEnums
      {
          [Description("OEM Pipe")]
          OemPipe,
      
          [Description("Hanja Mode")]
          HanjaMode
      }
      

      您可以通过扩展方法检索Description 属性:

      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();
      }
      

      要真正检索枚举描述,您可以这样称呼它;

      var enumAsText = theEnum.ToEnumDescription();
      

      你也可以这样做:

      MyEnums.OemPipe.ToEnumDescription();
      

      【讨论】:

      • 他正在使用现有的枚举 (System.Windows.Forms.Keys),因此无法添加 Description 属性。
      【解决方案5】:

      制作一个将键名映射到用户可理解的字符串的资源文件。如果资源文件没有特定键的值,那么只需使用键名称(就像您现在所做的那样),这样您只需要定义那些难以理解的,而您不需要必须提前完成。

      如果您愿意,这还允许您本地化为不同的语言。

      编辑:添加代码示例。假设您有一个名为“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);
      }
      

      【讨论】:

      • 一般来说,这可能是比硬编码用户友好值更好的解决方案,即使您不需要本地化。
      • 这似乎是最好的解决方案。
      【解决方案6】:

      没有办法自己编写代码。不过,您可以使用以下方法,它可能接近所需的最少工作量:

      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();
          }
      }
      

      我不确定您是否需要 & ~Keys.Modifiers 位 - 如果需要,您可能需要编写更多代码来处理修饰符 - 但我以前做过类似的事情。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2011-02-05
        • 1970-01-01
        • 2012-05-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-05-27
        相关资源
        最近更新 更多