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

时间:2020-03-06 14:57:34  来源:igfitidea点击:

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

解决方案

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

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

编辑:请注意,这仅引发了一个已经创建的表单,而没有窃取焦点。

如果我们愿意使用Win32 P / Invoke,则可以使用ShowWindow方法(第一个代码示例正是我们想要的)。

当我们使用创建新表单时

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

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

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

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

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

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

在单独的线程中创建并启动通知表单,并在表单打开后将焦点重设回主表单。让通知表单提供一个从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())以编程方式将其关闭。

缺少一些细节,但希望这能使我们朝正确的方向前进。

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

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

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

这很好用:

[System.Runtime.InteropServices.DllImport("user32")]

public static extern long OpenIcon(long hwnd);

[System.Runtime.InteropServices.DllImport("user32")]

public static extern long SetForegroundWindow(long hwnd);

public static void ActivateInstance()
{

    long MyHndl = 0;

    long result = 0;

    Process objProcess = Process.GetCurrentProcess();

    MyHndl = objProcess.MainWindowHandle.ToInt32();

    result = OpenIcon(MyHndl); // Restore the program.

    result = SetForegroundWindow(MyHndl); // Activate the application.

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

}

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

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