从一个窗体向另一个窗体发送键盘事件

7
我的问题相当简单:
我们的C#应用程序有一个主窗体,其中包含一个菜单和多个与菜单项关联的键盘快捷键。
现在我们需要从一些子窗体中触发菜单项。但是,由于当一个子窗体处于活动状态时,主窗体是不活动的,因此快捷键无法工作。
有没有一种简单的方法将所有键盘事件从子窗体传播到“Owner”窗体?或者传播到另一个窗体?
啊,我们不能使用一些低级别的Windows东西,因为我们还需要在Mono/Linux上运行应用程序。
编辑: 我确切的问题是如何使用相同的快捷键从另一个窗体触发菜单项。当然,如果菜单更改或添加了新项目,则无需在窗体中更新代码。

我也非常渴望得到这个答案,因为它与我的编辑程序有关。 - Tesserex
IMessageFilter在Mono中可用,您可以为主窗体实现它。 - Hans Passant
嗯...不确定我们是否想要处理这些消息对象...或者有没有一种简单的方法将这样的消息转换为事件? - thalm
7个回答

5
这是解决我的问题的方法:
public class MainForm : Form
{
    public bool ProcessCmdKeyFromChildForm(ref Message msg, Keys keyData)
    {
        Message messageCopy = msg;
        messageCopy.HWnd = this.Handle; // We need to assign our own Handle, otherwise the message is rejected!

        return ProcessCmdKey(ref messageCopy, keyData);
    }
}

public class MyChildForm : Form
{
    private MainForm mMainForm;

    public MyChildForm(MainForm mainForm)
    {
        mMainForm = mainForm;
    }

    // This is meant to forward accelerator keys (eg. Ctrl-Z) to the MainForm
    protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
    {
        if (mMainForm.ProcessCmdKeyFromChildForm(ref msg, keyData))
        {
            return true;
        }

        return base.ProcessCmdKey(ref msg, keyData);
    }
}

这是一个非常好的解决方案,拯救了我的一天,谢谢。但是,如果可以的话,请注意 Message messageCopy = msg; 仅执行消息的浅复制。因此,接下来的行导致原始消息发生更改。我建议创建一个新消息并复制所有字段(除了 HWnd 显然)。 - Couitchy
@Couitchy 你好!嗯,我相信这是一个深拷贝,因为Message是一个值类型(结构体):https://learn.microsoft.com/en-us/dotnet/api/system.windows.forms.message?view=netframework-4.8。如果有证据证明我错了,我很乐意修改我的帖子。 - kostasvs
嗨@kostasvs!我刚刚检查了一下,看起来你是完全正确的:这确实是一个深拷贝。实际上,每次我看到关键字ref,它让我想到引用类型,但我应该想到Message是一个struct - Couitchy

1
你尝试过类似这样的东西吗?
ParentForm : Form
{
    public NotifyKeyPress(KeyPressEventArgs e)
    {
         OnKeyPress(e);
    }
}

ChildForm : Form
{
    ParentForm _parent;
    public ChildForm(ParentForm parent)
    {
       _parent = parent;
       KeyPress += KeyPressHandler;
    }

    public KeyPressHandler(object sender, KeyPressEventArgs e)
    {
       if (_parent != null)
       {
           _parent.NotifyKeyPress(e);
       } 
    }
}

是的,我想这可能可行,我会尝试并报告结果。谢谢! - thalm
你好,再次见面了。它不像那样工作...我可以将键盘事件发送到主窗体,并调用OnKeyXXX事件,但这不会触发菜单项中的事件...这真是一个奇怪的问题...我以为这会更容易... - thalm
也许尝试使用RaiseKeyEvent。 - Adam Driscoll

0

我认为你想要将父窗体的KeyPreview属性设置为true。

当此属性设置为true时,窗体将接收所有KeyPress、KeyDown和KeyUp事件。在窗体的事件处理程序完成处理按键后,按键将被分配给具有焦点的控件。例如,如果KeyPreview属性设置为true并且当前选定的控件是TextBox,则在表单的事件处理程序处理按键后,TextBox控件将接收所按下的键。要仅在窗体级别处理键盘事件并防止控件接收键盘事件,请在窗体的KeyPress事件处理程序中将KeyPressEventArgs.Handled属性设置为true。

编辑:

这个问题中的答案可能会有帮助:


这只会导致表单处理其控件接收到的键事件。 - SLaks
好主意,但是它没有起到帮助的作用,因为 MainForm 只是子窗体的“所有者”,而不是 .Net 中的“父窗体”(我在帖子中的命名并不清楚,抱歉)。当我尝试设置“父窗体”属性时,会出现异常,因为窗体是一个顶级控件.. 有什么其他的想法吗? - thalm
我修改了我的答案,因为我最初提供的内容并不能满足你的需求。 - Sam Holder
您在编辑中提出的解决方案看起来与Adam Driscoll的解决方案类似,我会尝试一下。 - thalm

0

ToolStrip.AllowMerge 属性 "获取或设置[...] 是否可以组合多个 MenuStripToolStripDropDownMenuToolStripMenuItem 和其他类型。" (MSDN)。

这意味着您可以:

"使用 AllowMerge 属性,使多文档界面 (MDI) 子窗体在 MDI 父窗体中组合其各自的菜单。" (AllowMerge property, Remark, MSDN)

另请参阅:

  1. 合并操作
  2. 合并索引

希望这可以帮助你得到你想要的。现在,我不知道这是否适用于 Windows Forms 或者一旦构建也能在 Linux 上运行。


请看另一篇帖子,我们没有其他菜单可供加入或合并...或者您是怎么想到这可以解决问题的? - thalm
你必须在MDI容器和子窗体上都有一个MenuStrip。然后,您允许它们合并,甚至替换彼此的某些等效菜单。 - Will Marcouiller

0

我猜你所说的“inactive”是指它没有焦点?

最干净的方法是让每个窗体公开与其菜单相关的事件。当你创建窗体时,订阅它们彼此(或从子窗体到主窗体,或者任何流程需要的方式)。当菜单被点击时,执行你的额外事件,其他窗体将接收到这个事件。

这有帮助吗?我认为这比试图手动强制发送消息更好,因为它将是自我记录代码,窗体需要相互反应。

一个更“远离问题”的方法是,你是否需要两个窗体,还是可以重构UI设计?


你好,感谢您的回复! 我们只有一个菜单,就在主窗体中,但是每个其他窗体也应该能够使用键盘快捷键触发菜单项。我们不需要在另一个窗体中再添加一个菜单,也不需要通知其他窗体菜单项是否被点击。所以这并没有什么帮助... - thalm
啊..是的..我可以重构UI设计。那你有什么想法呢? - thalm

0

有更简单的方法来做这件事。菜单项应该触发适当的方法调用。 然后你可以在应用程序的任何地方调用这些方法。


是的,我可以做到,但我想用其他表格相同的键盘快捷键来触发它们。当然,我只想定义一次快捷键... - thalm

0

不要将键快捷方式绑定到主窗体上的菜单项,而是可以创建一个自定义的键处理方法来响应这些键快捷方式。将此方法放在主窗体中。然后在所有子窗体上的键事件中调用此方法。@Adam Driscoll的代码与此方法非常兼容。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接