如何让子窗口重新发送密钥?

时间:2014-04-22 18:41:58

标签: c# winforms keyboard-events

我几乎肯定错过了一些完全明显的东西。我有一个单独的主窗体,带有各种带快捷键的菜单项。我有儿童窗户,可以嵌入主表格或自己浮动。主窗体类有一个静态成员,指向一个现存的主窗体,因此子窗口可以访问其公共函数。我希望通常链接到工具栏条目的热键可以在子窗口中工作,我宁愿不复制代码。我知道我必须在主窗体上调用一个按键事件,但我今天正在运行一个完整的空白。

举一个简单的例子,有一些菜单项可以保存当前文件,并将窗口置于鼠标所在位置的中心位置,分别由Ctrl+SCtrl+E触发。它们在我的主窗体中被设置为键盘快捷键,但实际的繁重工作是由我的子窗口完成后一个命令。我通过在子窗口中捕获KeyDown事件来暂时修复它,但这意味着同一个快捷方式显示的两个不同的位置。

2 个答案:

答案 0 :(得分:1)

似乎很少有解决方案来处理密钥快捷方式。 其中一个可能是按照建议here安装键盘钩子。 您也可以尝试按照建议here添加自定义邮件过滤器来处理此问题,但是,我还没有验证在那里发布的代码。

带钩子的解决方案似乎有点棘手,所以您可能想先尝试自定义消息过滤器。

答案 1 :(得分:0)

根据我对上面的 Lukasz M 的评论,我需要保留当前的菜单结构,原因很遗憾,即所有工程师都习惯于更改菜单,他们想要菜单快捷方式自动工作。我可以使用该自定义消息过滤器修改中央热键位置,以便为菜单项生成快捷方式文本,但这会增加下一次有人介入添加快捷菜单项时可能会撤消的额外复杂性。因此,我选择了我在评论中提到的解决方案,为子窗口提供了一个隐形菜单。

令我惊讶的是,如果菜单的Visible属性设置为false,则热键可以正常工作。并且,如果从子表单调用,则与主表单中的菜单项相关联的事件完全正常,因为它们相对于定义它们的窗口而不是被调用它们的窗口执行。因此,我的第一个解决方案是从主窗体中获取MenuStrip并将其添加到子窗体。这不起作用,因为在儿童形式中将其隐形,使其在主要形式中也不可见。我的下一次尝试涉及创建新的隐藏MenuStrip并将主要表单ToolStripMenuItem中的MenuStrip项添加到其中。这打破了热键功能,可能因为菜单项目现在存在于多个地方。最后,我创建了仅包含快捷键的菜单项的浅表副本,Tag属性(使用它的一些菜单项所必需的)和事件处理程序(最后一个通过方法完成) How to clone Control event handlers at run time?中描述的。经过一番摆弄后,我意识到我不需要维护菜单的结构,我只需要带有快捷方式的项目。这就是我最终的结果:

主要表格:

  /// <summary>
  /// Returns copies of all menu shortcut items in the main form.
  /// </summary>
  /// <returns>A list containing copies of all of the menu items with a keyboard shortcut.</returns>
  public static List<ToolStripMenuItem> GetMenuShortcutClones()
  {
     List<ToolStripMenuItem> shortcutItems = new List<ToolStripMenuItem>();

     Stack<ToolStripMenuItem> itemsToBeParsed = new Stack<ToolStripMenuItem>();

     foreach (ToolStripItem menuItem in mainForm.menuStrip.Items)
     {
        if (menuItem is ToolStripMenuItem)
        {
           itemsToBeParsed.Push((ToolStripMenuItem)menuItem);
        }
     }

     while (itemsToBeParsed.Count > 0)
     {
        ToolStripMenuItem menuItem = itemsToBeParsed.Pop();

        foreach (ToolStripItem childItem in menuItem.DropDownItems)
        {
           if (childItem is ToolStripMenuItem)
           {
              itemsToBeParsed.Push((ToolStripMenuItem)childItem);
           }
        }

        if (menuItem.ShortcutKeys != Keys.None)
        {
           shortcutItems.Add(CloneMenuItem(menuItem));
        }
     }

     return shortcutItems;
  }

  /// <summary>
  /// Returns an effective shortcut clone of a ToolStripMenuItem. It does not copy the name
  /// or text, but it does copy the shortcut and the events associated with the menu item.
  /// </summary>
  /// <param name="menuItem">The MenuItem to be cloned</param>
  /// <returns>The newly generated clone.</returns>
  private static ToolStripMenuItem CloneMenuItem(ToolStripMenuItem menuItem)
  {
     ToolStripMenuItem copy = new ToolStripMenuItem();
     copy.ShortcutKeys = menuItem.ShortcutKeys;
     copy.Tag = menuItem.Tag;

     var eventsField = typeof(Component).GetField("events", BindingFlags.NonPublic | BindingFlags.Instance);
     var eventHandlerList = eventsField.GetValue(menuItem);
     eventsField.SetValue(copy, eventHandlerList);

     return copy;
  }

儿童表格:

  private void OnRefresh(object sender, EventArgs e)
  {
     // Refresh the hiddenShortcutMenu.
     List<ToolStripMenuItem> shortcutList = MainForm.GetMenuShortcutClones();
     hiddenShortcutMenu.Items.Clear();
     hiddenShortcutMenu.Items.AddRange(shortcutList.ToArray());
  }

在子表单的构造函数中,我实例化hiddenShortcutMenu,将Visible设置为false,将其分配给我的子表单控件,然后设置事件。最后一点是因为菜单有时会根据上下文改变而不得不定期刷新。目前,我已将其设置为Paint事件以获得最大的偏执,但我想我会尝试找到主要表单的方式来表示它已更改菜单结构。