如何在不抢占焦点的情况下显示一个表单?

156

我正在使用一个表单来显示通知(它出现在屏幕的右下角),但是当我展示这个表单时,它会夺取主表单的焦点。有没有一种方法可以在不夺取焦点的情况下显示这个“通知”表单?

20个回答

181

嗯,仅仅覆盖Form.ShowWithoutActivation不足够吗?

protected override bool ShowWithoutActivation
{
  get { return true; }
}

如果您不希望用户单击此通知窗口,则可以覆盖CreateParams:

protected override CreateParams CreateParams
{
  get
  {
    CreateParams baseParams = base.CreateParams;

    const int WS_EX_NOACTIVATE = 0x08000000;
    const int WS_EX_TOOLWINDOW = 0x00000080;
    baseParams.ExStyle |= ( int )( WS_EX_NOACTIVATE | WS_EX_TOOLWINDOW );

    return baseParams;
  }
}

4
展示未激活,真不敢相信我没找到它,浪费了一个下午! - deerchao
5
为了防止内部控件夺取焦点,我还需要设置 form1.Enabled = false - Jader Dias
8
如果您确实需要顶部显示功能,请参阅其他回答 - Roman Starkov
2
WS_EX_NOACTIVATEWS_EX_TOOLWINDOW 的值分别为 0x080000000x00000080 - Juan
1
我有这两种解决方案,但当你将Visible更改为true时,它们都不起作用。 处理Windows API就像玩响尾蛇一样,无论你多么小心,总有一天会被咬到。 - John

77

PInvoke.net ShowWindow 方法中获取:

private const int SW_SHOWNOACTIVATE = 4;
private const int HWND_TOPMOST = -1;
private const uint SWP_NOACTIVATE = 0x0010;

[DllImport("user32.dll", EntryPoint = "SetWindowPos")]
static extern bool SetWindowPos(
     int hWnd,             // Window handle
     int hWndInsertAfter,  // Placement-order handle
     int X,                // Horizontal position
     int Y,                // Vertical position
     int cx,               // Width
     int cy,               // Height
     uint uFlags);         // Window positioning flags

[DllImport("user32.dll")]
static extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

static void ShowInactiveTopmost(Form frm)
{
     ShowWindow(frm.Handle, SW_SHOWNOACTIVATE);
     SetWindowPos(frm.Handle.ToInt32(), HWND_TOPMOST,
     frm.Left, frm.Top, frm.Width, frm.Height,
     SWP_NOACTIVATE);
}

(Alex Lyman已经回答了这个问题,我只是通过直接粘贴代码扩展它。有编辑权限的人可以复制它并在那里删除它,至于我,我不介意;))


54
我觉得令人难以置信的是我们仍然需要链接到外部 DLL 文件来与窗体进行交互。我们已经用的是 .NET Framework 4 版本了!!是时候让微软解决这个问题了。 - Maltrap
9
接受的答案是错误的。请查找"ShowWithoutActivation"。 - mklein
@TheSoftwareJedi 当我使用这段代码时,窗体的Load事件没有触发...你能帮我吗? - Talha
1
@Talha 这段代码与Load事件无关。Load事件在窗体加载时触发,而不是在显示时触发。 - TheSoftwareJedi
@Maltrap - 这不是真的,可以通过纯.NET实现。只需覆盖ShowWithoutActivation属性即可。 - miroxlav
显示剩余8条评论

17

这是对我有效的方法。它提供了TopMost但不会夺取焦点。

    protected override bool ShowWithoutActivation
    {
       get { return true; }
    }

    private const int WS_EX_TOPMOST = 0x00000008;
    protected override CreateParams CreateParams
    {
       get
       {
          CreateParams createParams = base.CreateParams;
          createParams.ExStyle |= WS_EX_TOPMOST;
          return createParams;
       }
    }

记得在 Visual Studio 设计器或其他地方省略设置 TopMost。

这里借鉴了(抱歉,抄袭了)这里的内容(点击 Workarounds):

https://connect.microsoft.com/VisualStudio/feedback/details/401311/showwithoutactivation-is-not-supported-with-topmost


2
最上层 + 未聚焦的工作,看起来是所有答案中最干净的。 - feos
自Windows 8以来,Topmost已被弃用,当您使用它时,Microsoft会惩罚您。其效果是,在打开一个最顶层的窗口然后关闭它之后,Windows会将您的应用程序中的其他窗口移动到后台。这肯定不是您的应用程序所期望的行为。Microsoft实施此举是因为过去许多程序员滥用了非常侵入性的Topmost。Topmost非常具有攻击性。我从不使用它。 - Elmue

14

如果你愿意使用Win32 P/Invoke,那么你可以使用ShowWindow方法(第一个代码示例完全符合你的需求)。


9

来自pinvoke.net的Alex Lyman/TheSoftwareJedi答案中的示例代码将使窗口成为“最顶层”窗口,这意味着在弹出后无法将其放在普通窗口后面。考虑到Matias对他想要使用它的描述,那可能就是他想要的。但是如果您希望用户在弹出窗口后能够将其放置在其他窗口后面,请在示例中使用HWND_TOP(0)而不是HWND_TOPMOST(-1)。


9

这样做看起来像是一个hack,但似乎是有效的:

this.TopMost = true;  // as a result the form gets thrown to the front
this.TopMost = false; // but we don't actually want our form to always be on top

编辑:请注意,这仅仅是在不窃取焦点的情况下提高了已经创建的表单。

这里似乎不起作用...可能是因为这个“通知表单”在另一个线程中打开了? - Tute
1
在这种情况下,您可能需要使用this.Invoke()调用来再次调用方法以正确处理线程。通常,从错误的线程操作表单会导致异常抛出。 - Matthew Scharley
虽然这种方法确实可行,但正如所提到的那样,它是一种hacky方法,在某些情况下会导致BSOD,因此请注意。 - Raphael Smit
自Windows 8以来,Topmost已被弃用,当您使用它时,Microsoft会惩罚您。其效果是,在打开一个最顶层的窗口然后关闭它之后,Windows会将您的应用程序中的其他窗口移动到后台。这肯定不是您的应用程序所期望的行为。Microsoft实施此举是因为过去许多程序员滥用了非常侵入性的Topmost。Topmost非常具有攻击性。我从不使用它。 - Elmue
这确实会窃取焦点。 - Jimmy

6
在WPF中,您可以这样解决它:
在窗口中添加以下属性:
<Window
    x:Class="myApplication.winNotification"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
  Title="Notification Popup" Width="300" SizeToContent="Height"
  WindowStyle="None" AllowsTransparency="True" Background="Transparent" ShowInTaskbar="False" Topmost="True" Focusable="False" ShowActivated="False" >
</Window>

最后一个属性就是您需要的ShowActivated="False"。

4
我有类似的东西,我只需显示通知表单,然后执行。
this.Focus();

将焦点重新聚焦到主要表单上。


简单而有效! - Tom

3

您可能需要考虑要显示哪种类型的通知。

如果绝对有必要让用户知道某个事件,推荐使用Messagebox.Show,因为它会阻塞主窗口的任何其他事件,直到用户确认之后才会消失。但要注意避免弹窗盲区(pop-up blindness)。

如果不是非常重要,您可以使用其他方式来显示通知,例如在窗口底部使用工具栏。您写道,您将通知显示在屏幕右下角-标准方法是使用气球提示(balloon tip)和系统托盘图标的组合。(参考链接)(参考链接)


2
气球提示不是一个选项,因为它可以被禁用。 如果你将程序最小化,状态栏也可以隐藏。不管怎样,谢谢你的建议。 - Tute

3

这个很有效。

请参阅:OpenIcon - MSDNSetForegroundWindow - MSDN

using System.Runtime.InteropServices;

[DllImport("user32.dll")]
static extern bool OpenIcon(IntPtr hWnd);

[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);

public static void ActivateInstance()
{
    IntPtr hWnd = IntPtr hWnd = Process.GetCurrentProcess().MainWindowHandle;

    // Restore the program.
    bool result = OpenIcon(hWnd); 
    // Activate the application.
    result = SetForegroundWindow(hWnd);

    // End the current instance of the application.
    //System.Environment.Exit(0);    
}

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