如何防止我的C# WinForms应用程序在将Visible属性设置为true时窃取焦点?

7
我有一个C#的WinForms应用程序,后台运行并监听热键的按下。当热键被按下时,我的窗体会短暂地出现。窗体一直在运行,但是设置为隐藏状态,直到我收到热键事件时,然后将可见属性设置为true。代码如下:
void hook_volumeDown(object sender, KeyPressedEventArgs e)
{
    this.Visible = true;
}

值得注意的是,该表单的最高属性被设置为true。
真正奇怪的部分是,我的C#应用程序从另一个应用程序中窃取焦点后,它将永远不会再次这样做。例如:我启动我的应用程序,然后启动一些全屏应用程序,如《团队要塞2》。然后我按下我的热键。《团队要塞2》最小化,我看到我的表单。然后,无论如何,我都可以恢复TF2,并再次按下我的热键(具有所需的效果),并且TF2将保持聚焦状态。
无论如何,我正在寻找一种解决方法。我在这里找到了很多涵盖类似问题的问题,但所有这些问题都与创建/启动新表单有关,而不是使现有表单可见(除非我错过了什么)。我可以重新设计应用程序,每次需要一个新表单时创建一个新表单,但那将意味着创建另一个始终不可见的表单,只是为了等待热键事件,因此我宁愿将其保留为原样。
有什么想法吗?

我不确定我理解了...你的意思是它正常工作,只是在第一次使用时出现问题吗? - Dan Byström
2个回答

7
我认为你的问题与Visible = true在第一次和后续调用之间的行为不同有关。第一次调用visible并且窗口句柄尚未创建时,通过调用CreateWindowEx创建窗口,该函数具有控制窗口行为的一些样式参数。我认为你需要确保使用样式WS_EX_NOACTIVATE创建窗口,可以通过重写CreateParams来实现。
其他尝试方法:
1)ShowWindow函数(由Visible = true使用)在第一次调用时忽略焦点参数(http://msdn.microsoft.com/en-us/library/ms633548%28VS.85%29.aspx),如果程序提供了STARTUPINFO结构体。深入研究反射器,并查找Form类是否提供STARTUPINFO结构体,如果是,则如何操作它。
2)Form具有ShowWithoutActivation属性,可以被覆盖并设置为true,你已经覆盖了吗?
很抱歉没有“确切答案”,但我希望这至少为进一步调查提供了一些起点。祝好运。

1
谢谢,覆盖ShowWithoutActivation方法证明是完美的解决方案。只需一行代码,问题就解决了。 - Fopedush
覆盖 ShowWithoutActivation 已经成为我也非常完美的解决方案。 - beppe9000

1

在您的函数中使用KeyPressedEventArgs看起来非常奇怪。可以通过P/Invoke RegisterHotKey() API函数实现热键。当按下热键时,它会向您的窗口发送一条消息。这是一个在启动时不可见的窗体示例,在按下热键后变得活跃。在此示例中为Ctrl+Alt+U:

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;

namespace WindowsFormsApplication1 {
    public partial class Form1 : Form {
        private const int MYKEYID = 0;    // In case you want to register more than one...
        public Form1() {
            InitializeComponent();
            this.FormClosing += (s, args) => UnregisterHotKey(this.Handle, MYKEYID);
        }
        protected override void SetVisibleCore(bool value) {
            if (value && !this.IsHandleCreated) {
                this.CreateHandle();
                RegisterHotKey(this.Handle, MYKEYID, MOD_CONTROL + MOD_SHIFT, Keys.U);
                value = false;
            }
            base.SetVisibleCore(value);
        }
        protected override void WndProc(ref Message m) {
            if (m.Msg == WM_HOTKEY && m.WParam.ToInt32() == MYKEYID) {
                this.Visible = true;
                if (this.WindowState == FormWindowState.Minimized)
                    this.WindowState = FormWindowState.Normal;
                SetForegroundWindow(this.Handle);
            }
            base.WndProc(ref m);
        }
        // P/Invoke declarations
        private const int WM_HOTKEY = 0x312;
        private const int MOD_ALT = 1;
        private const int MOD_CONTROL = 2;
        private const int MOD_SHIFT = 4;
        [DllImport("user32.dll")]
        private static extern int RegisterHotKey(IntPtr hWnd, int id, int modifier, Keys vk);
        [DllImport("user32.dll")]
        private static extern bool UnregisterHotKey(IntPtr hWnd, int id);
        [DllImport("user32.dll")]
        private static extern bool SetForegroundWindow(IntPtr hWnd);
    }
}

请注意,SetForegroundWindow()函数是关键,可能也是您在问题描述中所述问题的根源。当用户正在使用另一个窗口时,Windows不允许应用程序将窗口推到用户面前。必须经过至少几秒钟的不活动时间,才能允许窗口夺取焦点。通过给定的代码,很容易看到,您的窗体的任务栏按钮将会闪烁。避免将ShowInTaskbar属性设置为false。对于这个代码来说,没有必要这样做,直到按下热键,任务栏按钮才会显示出来。

虽然我没有尝试过,但我会为你的回答的全面性点赞。我正在使用一个包装类来处理热键,它本质上做了你展示的相同的事情,然后触发一个事件。对于这个应用程序来说,重要的是即使按下热键,任务栏条目也不会显示出来。 - Fopedush
我不明白为什么弹出窗口会被更大的前景窗口遮挡,而用户又没有任何办法激活它。唉。 - Hans Passant
topmost被设置为true,因此除非有异常情况(全屏应用程序),否则它将可见。该窗口充当系统音量的OSD,我的热键可以升高/降低/静音它。这个表单实际上只是一个没有边框的小东西,带有显示音量%的进度条和标签。 - Fopedush

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