【问题标题】:Notification Window - Preventing the window from ever getting focus通知窗口 - 防止窗口获得焦点
【发布时间】:2011-12-06 17:20:45
【问题描述】:

我在让通知框在 c# 中正常运行时遇到了一些问题。基本上,我在屏幕的右下角显示了一个无边界表格,它会显示一条消息几秒钟然后消失。问题是我需要它出现在其他窗口的顶部,而它却无法窃取焦点。理想情况下,我希望它是纯托管代码,尽管查看类似示例我怀疑这是否可能。

目前我正在阻止它在调用 Form.Show() 时窃取焦点:

protected override bool ShowWithoutActivation // stops the window from stealing focus
{
    get { return true; }
}

然后忽略鼠标点击:

    private const int WM_MOUSEACTIVATE = 0x0021;
    private const int MA_NOACTIVATEANDEAT = 0x0004;

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_MOUSEACTIVATE)
        {
            m.Result = (IntPtr)MA_NOACTIVATEANDEAT;
            return;
        }
        base.WndProc(ref m);
    }

但是我发现,如果我将这些与 TopMost = true (我需要)结合使用,它无论如何都会获得焦点,如果所有其他窗口都被最小化,它也会获得焦点。

那么,有什么方法可以完全阻止表单获得焦点(无论是通过鼠标单击、alt-tab 等),同时仍然是最顶部/第二顶部的表单?即使只是将焦点立即返回到它窃取它的窗口也可以工作(尽管会引入闪烁)。

任何建议将不胜感激,我真的坚持这一点。

编辑:

好的,所以我终于设法使用:

protected override bool ShowWithoutActivation // stops the window from stealing focus
{
    get { return true; }
}

// and

const int WS_EX_NOACTIVATE = 0x08000000;
const int WS_EX_TOPMOST = 0x00000008;

protected override CreateParams CreateParams
{
    get
    {
        CreateParams param = base.CreateParams;
        param.ExStyle |= WS_EX_TOPMOST; // make the form topmost
        param.ExStyle |= WS_EX_NOACTIVATE; // prevent the form from being activated
        return param;
    }
}

// and

[DllImport("user32.dll")]
private extern static IntPtr SetActiveWindow(IntPtr handle);
private const int WM_ACTIVATE = 6;
private const int WA_INACTIVE = 0;

private const int WM_MOUSEACTIVATE = 0x0021;
private const int MA_NOACTIVATEANDEAT = 0x0004;

protected override void WndProc(ref Message m)
{
    if (m.Msg == WM_MOUSEACTIVATE)
    {
        m.Result = (IntPtr)MA_NOACTIVATEANDEAT; // prevent the form from being clicked and gaining focus
        return;
    }
    if (m.Msg == WM_ACTIVATE) // if a message gets through to activate the form somehow
    {
        if (((int)m.WParam & 0xFFFF) != WA_INACTIVE)
        {

            if (m.LParam != IntPtr.Zero)
            {
                SetActiveWindow(m.LParam);
            }
            else
            {
                // Could not find sender, just in-activate it.
                SetActiveWindow(IntPtr.Zero);
            }

        }
    }

我还在 GotFocus 事件中添加了 Form.Hide(),这样即使它确实以某种方式获得焦点,它也会尽快关闭并离开用户。

另外,如果有人想知道,所有窗口样式等的常量都可以在 WINUSER.H 中找到,如果你找不到它,它的在线地址为 http://www.woodmann.com/fravia/sources/WINUSER.H。

但是,如果有人能看到更优雅的方法,将不胜感激。

【问题讨论】:

    标签: c# forms notifications focus


    【解决方案1】:

    我不是在这里寻找要点,因为原始发布者已经发布了一个对他们有用的解决方案,但我想分享我在这个问题上的经验。使用上面的解决方案(在问题的底部而不是在答案形式中)在使用WndProc 代码时给我一个Win32Exception: Error creating window handle error.,因为它张贴在那里。 ShowWithoutActivation 和 CreateParams 部分可以很好地防止表单激活并仍然保持在最上面。

    我想出了一个替代解决方案来防止表单被点击使用SetWindowLong 使表单透明,因此可以单击它,SetLayeredWindowAttributes 将透明度设置为正常,因此您可以看到表单再次,但仍保留点击表单的能力。

    注意:在这种状态下,您根本无法与表单交互,甚至尝试单击“X”按钮只会单击该位置的表单后面的任何内容,因此您需要使用代码关闭表单:

    public partial class Form1 : Form
    {
        private enum GWL : int
        {
            ExStyle = -20
        }
    
        private enum WS_EX : int
        {
            Transparent = 0x20,
            Layered = 0x80000
        }
    
        public enum LWA : int
        {
            ColorKey = 0x1,
            Alpha = 0x2
        }
    
        [DllImport("user32.dll")]
        static extern int SetWindowLong(IntPtr hWnd, int nIndex, int dwNewLong);
    
        [DllImport("user32.dll")]
        static extern bool SetLayeredWindowAttributes(IntPtr hwnd, uint crKey, byte bAlpha, uint dwFlags);
    
        protected override bool ShowWithoutActivation
        {
            get { return true; }
        }
    
        const int WS_EX_NOACTIVATE = 0x08000000;
        const int WS_EX_TOPMOST = 0x00000008;
    
        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams param = base.CreateParams;
                param.ExStyle |= WS_EX_TOPMOST; // make the form topmost
                param.ExStyle |= WS_EX_NOACTIVATE; // prevent the form from being activated
                return param;
            }
        }
    
        public Form1()
        {
            InitializeComponent();
        }
    
        private void Form1_Load(object sender, EventArgs e)
        {
            // Prevent form from showing up in taskbar which also prevents activation by Alt+Tab 
    
            this.ShowInTaskbar = false;
    
            // Allow the form to be clicked through so that the message never physically interferes with work being done on the computer 
    
            SetWindowLong(this.Handle, (int)GWL.ExStyle, (int)WS_EX.Layered | (int)WS_EX.Transparent);
    
            // Set the opacity of the form
    
            byte nOpacity = 255;    // 0 = invisible, 255 = solid, anything inbetween will show the form with transparency
            SetLayeredWindowAttributes(this.Handle, 0, nOpacity, (uint)LWA.Alpha);
        }
    }
    

    我还能够通过对上面的 WndProc 代码进行小幅更改来使原始方法发挥作用。

    注意:这也使表单无法点击,但行为有所不同,因为您实际上可以单击最小、最大和“X”按钮,但当您这样做时没有任何反应。当您在表单边缘时,鼠标光标也会发生变化,好像要调整大小但它不允许调整大小:

    public partial class Form1 : Form
    {
        protected override bool ShowWithoutActivation
        {
            get { return true; }
        }
    
        const int WS_EX_NOACTIVATE = 0x08000000;
        const int WS_EX_TOPMOST = 0x00000008;
    
        protected override CreateParams CreateParams
        {
            get
            {
                CreateParams param = base.CreateParams;
                param.ExStyle |= WS_EX_TOPMOST; // make the form topmost
                param.ExStyle |= WS_EX_NOACTIVATE; // prevent the form from being activated
                return param;
            }
        }
    
        [DllImport("user32.dll")]
        private extern static IntPtr SetActiveWindow(IntPtr handle);
        private const int WM_ACTIVATE = 6;
        private const int WA_INACTIVE = 0;
    
        private const int WM_MOUSEACTIVATE = 0x0021;
        private const int MA_NOACTIVATEANDEAT = 0x0004;
    
        protected override void WndProc(ref Message m)
        {
            if (m.Msg == WM_MOUSEACTIVATE)
            {
                m.Result = (IntPtr)MA_NOACTIVATEANDEAT; // prevent the form from being clicked and gaining focus
                return;
            }
            if (m.Msg == WM_ACTIVATE) // if a message gets through to activate the form somehow
            {
                if (((int)m.WParam & 0xFFFF) != WA_INACTIVE)
                {
    
                    if (m.LParam != IntPtr.Zero)
                    {
                        SetActiveWindow(m.LParam);
                    }
                    else
                    {
                        // Could not find sender, just in-activate it.
                        SetActiveWindow(IntPtr.Zero);
                    }
    
                }
            }
            else
            {
                base.WndProc(ref m);
            }
        }
    
        public Form1()
        {
            InitializeComponent();
        }
    
        private void Form1_Load(object sender, EventArgs e)
        {
            // Prevent form from showing up in taskbar which also prevents activation by Alt+Tab 
    
            this.ShowInTaskbar = false;
        }
    }
    

    【讨论】:

      【解决方案2】:

      在 WPF 中试试这个:

      ShowActivated="False"
      

      【讨论】:

        【解决方案3】:

        您正在寻找的可能是 WS_EX_NOACTIVATE 扩展窗口样式。单击时不会激活具有此样式的窗口。例如,虚拟键盘窗口就是这种风格。

        要将此样式应用于窗口,请覆盖 CreateParams 函数并更改 baseParams.ExStyle。

        【讨论】:

        • 谢谢,这非常适合防止它获得焦点,它现在甚至不会出现在 alt-tab 中,这太棒了。我仍然遇到的一个小问题是 TopMost = true 似乎覆盖了 ShowWithoutActivation,因此在调用 form.Show() 时它仍然获得焦点。有没有办法解决这个问题?
        • 请参阅 Ziketo 对此的回应。还记得为人们提出的有帮助的问题投票。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-04-09
        • 1970-01-01
        • 2011-09-04
        • 1970-01-01
        • 2011-03-10
        • 2019-01-29
        相关资源
        最近更新 更多