无论活动窗口如何,都可以在应用程序中获得任何击键?

时间:2014-11-24 14:18:04

标签: .net winforms keypress mdi

我的MDI应用程序使用KeyPreview = true处理主窗口的处理程序中的常规击键(A-Z,0-9等)。它会过滤一些传入字符序列,将它们隐藏在后续的KeyPress处理程序中。这在MDI窗口中工作正常,但如果模态窗口处于活动状态(因为主窗体不再聚焦),则不会调用主窗口的KeyPress。我通过简单的ShowDialog()打开模态窗口。

是否有某种方法可以普遍捕获和过滤KeyPress事件,无论哪个应用程序窗口具有焦点(包括模态窗口?)

我正在搜索在应用程序级别处理KeyPresses,而不是全局级别。 =>如果应用程序没有焦点,我不希望收到有关KeyPresses的通知。

3 个答案:

答案 0 :(得分:2)

问题是,当您调用ShowDialog时,您的主UI线程将停止调度消息。看看这个简化的消息循环:

while(true) 
{
    Message m;
    GetMessage(out m);
    // if close button pressed etc.
    if (m.Msg == WM_QUIT) break;
    DispatchMessage(m);
}

激活对话框时,会在DispatchMessage中执行此操作并启动类似的新消息循环。由于这个新循环,主窗口中的循环在DispatchMessage调用上被阻止,并且不会处理任何键盘消息。使用多个UI循环很棘手,因此我建议使用常规Show()消息(不会阻止主应用程序循环)并找到另一种方法将用户焦点指向该窗口。

我过去使用的ap / invoke调用将父窗口设置为禁用,并将用户焦点指向子窗口,它几乎与ShowDialog调用的行为相同,但没有阻止线程。您要做的是设置子表单的所有者,调用常规Show()方法,最后将父窗口的启用状态设置为false。您可以使用以下代码执行更新操作:

const int GWL_STYLE   = -16;
const int WS_DISABLED = 0x08000000;

[DllImport("user32.dll")]
static extern int GetWindowLong(IntPtr hWnd, int nIndex);

[DllImport("user32.dll")]
static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);

void SetWindowEnabled(Form form, bool enabled)
{
    SetWindowLong(form.Handle, GWL_STYLE, GetWindowLong(form.Handle, GWL_STYLE) &
                  ~WS_DISABLED | (enabled ? 0 : WS_DISABLED));
}

关于堆叠模型窗口的评论,我认为这是可能的,但确保在关闭对话框时调用本机方法重新启用父窗口非常重要。我会实现一个看起来像这样的扩展方法:

public static class FormsExtensions
{
    public static Task<bool> ShowNativeDialog(this Form child, Form owner)
    {
        var tcs = new TaskCompletionSource<bool>();
        child.Show();
        SetWindowEnabled(owner, false);
        child.Closed += (sender, args) => { 
            SetWindowEnabled(owner, true);
            tcs.SetResult(true);
        }
        return tcs.Task;
    }
}

用法看起来像这样:

DialogForm dialog = new DialogForm();
await dialog.ShowNativeDialog(this);

通过使用await,您可以在不阻止UI消息传递循环的情况下停止执行流程。

答案 1 :(得分:1)

根据msdn,系统只向前台线程发送键盘消息,模态窗口是否在另一个线程中启动? 如果是的话,您可以使用您在主窗口窗体中使用的相同句柄,使用一些简单的线程消息向模态窗口线程发送主消息, 现在如果你不知道什么是线程......你可以随时学习

这里有一些可以帮助你的好文章 about keyboards

about threads and msgs

附加说明: 如果需要,您可以随时使用sendkeys函数将密钥发送到另一个目标

编辑:Showdialog是一个阻塞异步调用,它&#34;冻结&#34;线程和其他窗口仅在启动的窗口关闭时返回,这就是当您激活模态窗口时句柄不响应的原因

答案 2 :(得分:1)

  

有没有办法普遍捕获和过滤KeyPress事件   无论哪个应用程序窗口具有焦点(包括模态   windows?)我正在寻找应用程序上的KeyPress处理   等级...

这可以通过IMessageFilter()完成。

这是一个简单的例子:

public partial class MainMdiParent : Form
{

    private MyFilter MF = new MyFilter();

    public MainMdiParent()
    {
        InitializeComponent();
    }

    private void MainMdiParent_Load(object sender, EventArgs e)
    {
        MF.EscapeKey += MF_EscapeKey;
        Application.AddMessageFilter(MF);

        childA a = new childA();
        a.MdiParent = this;
        a.Show();

        a = new childA();
        a.MdiParent = this;
        a.Show(); 
    }

    private void MF_EscapeKey()
    {
        Console.WriteLine("Escape Key Trapped in Main Form");
    }

    private void button1_Click(object sender, EventArgs e)
    {
        Form dlg = new Form();
        dlg.ShowDialog();
    }

}

public class MyFilter : IMessageFilter
{

    public delegate void EscapeKeyDelegate();
    public event EscapeKeyDelegate EscapeKey;

    private const int WM_KEYDOWN = 0x100;

    public bool PreFilterMessage(ref Message m)
    {
        switch (m.Msg )
        {
            case WM_KEYDOWN:
                switch (m.WParam.ToInt32())
                {
                    case (int)Keys.Escape:
                        if (EscapeKey != null)
                        {
                            EscapeKey();
                        }
                        return true; // suppress it?
                        break;
                }
                break;

        }

        return false; // returning false allows messages to be processed normally
    }

}