显示表格而不会失去焦点?


140

我正在使用一个表单来显示通知(它显示在屏幕的右下角),但是当我显示此表单时,它会从主表单中夺走焦点。有没有一种方法可以显示这种“通知”形式而又不会失去重点?

Answers:


165

嗯,仅仅覆盖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;
  }
}

3
ShowWithoutActivation,不敢相信我没有找到它,浪费了整整一个下午!
deerchao 2010年

2
我还需要一套form1.Enabled = false防止偷焦点内对照
的Jader Dias的

23
并关闭TopMost。
mklein 2011年

4
如果您确实想要TopMost,请参阅其他答案
罗曼·斯塔科夫

2
的值WS_EX_NOACTIVATEWS_EX_TOOLWINDOW0x080000000x00000080分别。
胡安

69

PInvoke.netShowWindow方法窃取

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)回答了这个问题,我只是通过直接粘贴代码来扩展它。拥有编辑权限的人可以在那儿复制它,并删除所有我关心的东西;))


我想知道,他是否真的需要,如果他在屏幕左下方显示的表格在另一个线程中?
Patrick Desjardins,

50
我觉得难以置信,我们仍然需要链接到外部DLL文件以与表单进行交互。我们使用的是.NET Framework版本4!是时候打包它了。
Maltrap

9
接受的答案不正确。寻找ShowWithoutActivation
mklein 2011年

只需添加frm.Hide(); 如果要直接使窗体不突出重点,请在ShowInactiveTopmost函数的开头。不要忘记:使用System.Runtime.InteropServices; 使此代码运行
Zitun,2012年

1
@Talha此代码与Load事件无关。加载事件在加载表单时触发,而不是在显示表单时触发。
TheSoftwareJedi 2014年


12

这对我有用。它提供了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。

这是从这里偷来的,错误的,借来的(单击替代方法):

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


1
最热门+没有重点的作品,看起来像是所有答案中最干净的。
feos

自Windows 8开始不推荐使用Topmost,Microsoft在使用Windows 8时会惩罚您。效果是,在打开最顶层的窗口然后将其关闭后,Windows会将应用程序的其他窗口移到后台。这肯定不是您的应用程序所需的行为。微软之所以实现这一点,是因为过去许多程序员都滥用了非常侵入性的最高层。最重要的是非常进取。我从不使用它。
Elmue

9

这样做似乎很简单,但似乎可行:

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

编辑:请注意,这只是引发了一个已经创建的表单而没有失去焦点。


似乎在这里不起作用...可能是因为此“通知表单”是在另一个线程中打开的?
马蒂亚斯

1
在这种情况下,可能需要执行this.Invoke()调用以再次作为正确的线程来调用该方法。通常,使用错误线程处理表单会引发异常。
马修·沙利

尽管此方法确实有效,但它是一种令人讨厌的方法,在某些情况下对我造成了BSOD,因此请当心。
拉斐尔·史密斯

自Windows 8开始不推荐使用Topmost,Microsoft在使用Windows 8时会惩罚您。效果是,在打开最顶层的窗口然后将其关闭后,Windows会将应用程序的其他窗口移到后台。这肯定不是您的应用程序所需的行为。微软之所以实现这一点,是因为过去许多程序员都滥用了非常侵入性的最高层。最重要的是非常进取。我从不使用它。
Elmue

9

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


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

在单独的线程中创建并启动通知表单,并在表单打开后将焦点重设回您的主表单。让通知表单提供一个从该Form.Shown事件触发的OnFormOpened 事件。像这样:

private void StartNotfication()
{
  Thread th = new Thread(new ThreadStart(delegate
  {
    NotificationForm frm = new NotificationForm();
    frm.OnFormOpen += NotificationOpened;
    frm.ShowDialog();
  }));
  th.Name = "NotificationForm";
  th.Start();
} 

private void NotificationOpened()
{
   this.Focus(); // Put focus back on the original calling Form
}

您还可以保留NotifcationForm对象的句柄,以便可以通过主Form(frm.Close())以编程方式将其关闭。

缺少一些细节,但是希望这可以使您朝正确的方向前进。


仅当您的表单是原始活动表单时,这才起作用。这种违背这种通知的主要目的。
亚历克斯·莱曼

1
??这就是通知的目的-发出通知并将焦点重新恢复到最初处于活动状态的形式。
鲍勃·纳德勒

2
这仅将焦点放在您的应用程序中的表单上-如果当时有其他程序处于活动状态怎么办?仅当用户不看您的应用程序时,显示通知窗口(通常是向用户提供您应用程序状态的更新)才真正有用。
亚历克斯·莱曼

3

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

如果让用户知道某个事件绝对重要,则建议使用Messagebox.Show,因为它的性质是阻止其他事件进入主窗口,直到用户确认为止。但是要注意弹出窗口的盲目性。

如果不是很关键,则可能需要使用其他方式来显示通知,例如窗口底部的工具栏。您写道,在屏幕的右下角显示通知-执行此操作的标准方法是将气球提示系统任务栏图标结合使用。


2
-气球提示是不是一种选择,因为可以禁用-如果你有计划无论如何最小感谢您的recomendations状态栏可以被隐藏
马蒂亚斯

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);    
}

1

也可以单独通过逻辑处理它,尽管我必须承认,上面的建议在您最终使用BringToFront方法而没有真正失去焦点的情况下才是最优雅的建议。

无论如何,我遇到了这个问题,并通过使用DateTime属性解决了该问题,如果最近才进行了调用,则不允许进一步的BringToFront调用。

假定一个核心类'Core',它处理例如三种形式'Form1、2和3'。每个表单都需要一个DateTime属性和一个Activate事件,该事件调用Core将窗口置于最前面:

internal static DateTime LastBringToFrontTime { get; set; }

private void Form1_Activated(object sender, EventArgs e)
{
    var eventTime = DateTime.Now;
    if ((eventTime - LastBringToFrontTime).TotalMilliseconds > 500)
        Core.BringAllToFront(this);
    LastBringToFrontTime = eventTime;
}

然后在核心类中创建工作:

internal static void BringAllToFront(Form inForm)
{
    Form1.BringToFront();
    Form2.BringToFront();
    Form3.BringToFront();
    inForm.Focus();
}

附带说明一下,如果您要将最小化的窗口还原到其原始状态(未最大化),请使用:

inForm.WindowState = FormWindowState.Normal;

同样,我知道这只是缺少BringToFrontWithoutFocus的补丁解决方案。如果您要避免使用DLL文件,则这是一个建议。


1

我不知道这是否视为坏死发布,但这是我所做的,因为我无法使用user32的“ ShowWindow”和“ SetWindowPos”方法来使用它。不,在这种情况下,覆盖“ ShowWithoutActivation”不起作用,因为新窗口应始终位于顶部。无论如何,我创建了一个以表格为参数的辅助方法。调用时,它将显示表单,将其放到最前面并使其成为TopMost,而不会窃取当前窗口的焦点(显然,它可以,但是用户不会注意到)。

    [DllImport("user32.dll")]
    static extern IntPtr GetForegroundWindow();

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

    public static void ShowTopmostNoFocus(Form f)
    {
        IntPtr activeWin = GetForegroundWindow();

        f.Show();
        f.BringToFront();
        f.TopMost = true;

        if (activeWin.ToInt32() > 0)
        {
            SetForegroundWindow(activeWin);
        }
    }

0

我知道这听起来可能很愚蠢,但这可行:

this.TopMost = true;
this.TopMost = false;
this.TopMost = true;
this.SendToBack();

如果将前窗发送到后窗,则如果背景窗与新的前景窗重叠,则可能不再显示。
TamusJRoyce

0

我需要在窗口TopMost中执行此操作。我实现了上面的PInvoke方法,但是发现我的Load事件没有像上面的Talha那样被调用。我终于成功了。也许这会帮助某人。这是我的解决方案:

        form.Visible = false;
        form.TopMost = false;
        ShowWindow(form.Handle, ShowNoActivate);
        SetWindowPos(form.Handle, HWND_TOPMOST,
            form.Left, form.Top, form.Width, form.Height,
            NoActivate);
        form.Visible = true;    //So that Load event happens

-4

当您使用创建新表单时

Form f = new Form();
f.ShowDialog();

它会失去焦点,因为在关闭此表单之前,您的代码无法在主表单上继续执行。

例外是使用线程创建新表单,然后使用Form.Show()。但是请确保该线程是全局可见的,因为如果您在函数中声明该线程,则一旦函数退出,线程将结束并且表单将消失。


By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.