通知窗口-防止窗口获得焦点

本文关键字:窗口 焦点 通知 | 更新日期: 2023-09-27 18:11:19

我在c#中获得通知框的正确行为有一些问题。基本上,我在屏幕的右下方展示了一个无边框的表单,它会显示一条消息几秒钟,然后消失。问题是,我需要它出现在其他窗口的顶部,而不能窃取焦点。理想情况下,我希望它是纯托管代码,尽管通过类似的例子,我怀疑这将是可能的。

目前,我正在防止它窃取焦点时,调用表单。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);
            }
        }
    }

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

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

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

通知窗口-防止窗口获得焦点

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

要将此样式应用于窗口,重写CreateParams函数并更改baseParams.ExStyle.

在WPF中试试:

ShowActivated="False"

我不是在这里寻找点,因为原来的海报已经发布了一个解决方案,为他们工作,但我想分享我的经验与这个问题。使用上面的解决方案(这是在问题的底部,而不是在回答形式)给我一个Win32Exception: Error creating window handle error.时使用WndProc代码,因为它是张贴在那里。ShowWithoutActivationCreateParams部分的工作很好,以防止激活的形式,仍然保持它的顶部。

我想出了另一个解决方案来防止表单被点击,使用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;
    }
}