C#Force Form Focus

所以,在提出这个问题之前,我确实搜索了谷歌和SO。 基本上我有一个DLL,它有一个编译成它的表单。 表单将用于向屏幕显示信息。 最终它将是异步的,并在dll中暴露了大量的自定义。 现在我只想让它正确显示。 我遇到的问题是我通过在Powershell会话中加载它来使用dll。 因此,当我尝试显示表单并让它到达顶部并具有焦点时,显示在所有其他应用程序上没有任何问题,但我不能在我的生活中让它显示在Powershell窗口上。 这是我目前用来尝试显示的代码。 我确信,一旦我弄明白,大多数都不会被要求,这只是代表我通过谷歌找到的所有东西。

CLass Blah { [DllImport("user32.dll", EntryPoint = "SystemParametersInfo")] public static extern bool SystemParametersInfo(uint uiAction, uint uiParam, uint pvParam, uint fWinIni); [DllImport("user32.dll", EntryPoint = "SetForegroundWindow")] public static extern bool SetForegroundWindow(IntPtr hWnd); [DllImport("User32.dll", EntryPoint = "ShowWindowAsync")] private static extern bool ShowWindowAsync(IntPtr hWnd, int cmdShow); private const int WS_SHOWNORMAL = 1; public void ShowMessage(string msg) { MessageForm msgFrm = new MessageForm(); msgFrm.lblMessage.Text = "FOO"; msgFrm.ShowDialog(); msgFrm.BringToFront(); msgFrm.TopMost = true; msgFrm.Activate(); SystemParametersInfo((uint)0x2001, 0, 0, 0x0002 | 0x0001); ShowWindowAsync(msgFrm.Handle, WS_SHOWNORMAL); SetForegroundWindow(msgFrm.Handle); SystemParametersInfo((uint)0x2001, 200000, 200000, 0x0002 | 0x0001); } } 

正如我所说,我确信其中大部分要么不需要,要么甚至是错误的,我只是想展示我曾尝试过的东西。 另外,正如我所提到的,我计划在某些时候异步显示它,我怀疑它最终会需要一个单独的线程。 将表单拆分为自己的线程会使它更容易引起对Powershell会话的关注吗?


@Joel,谢谢你的信息。 以下是我根据您的建议尝试的内容:

 msgFrm.ShowDialog(); msgFrm.BringToFront(); msgFrm.Focus(); Application.DoEvents(); 

该表格仍然在Powershell会议下提出。 我将继续研究线程。 我之前已经生成了线程,但从来没有父线程需要与子线程进行通信,因此我们将看到它是如何进行的。

到目前为止所有想法都有所帮助。


好吧,线程处理问题。 @Quarrelsome,我确实试过了这两个。 两者(或两者都没有)都起作用。 我很好奇使用线程的坏处是什么? 我没有使用Application.Run,​​我还没有遇到问题。 我正在使用父线程和子线程都可以访问的中介类。 在该对象中,我使用ReaderWriterLock来锁定一个属性,该属性表示我想要在子线程创建的表单上显示的消息。 父锁定属性然后写入应显示的内容。 子线程锁定属性并读取应将表单上的标签更改为的内容。 孩子必须在轮询间隔(我默认为500毫秒)这样做,我不是很高兴,但我找不到一个事件驱动的方式让孩子线程知道这个节日已经改变,所以我’我坚持投票。

我也无法激活并将窗口置于前台。 这是最终为我工作的代码。 我不确定它是否能解决你的问题。

基本上,调用ShowWindow()然后调用SetForegroundWindow()。

 using System.Diagnostics; using System.Runtime.InteropServices; // Sets the window to be foreground [DllImport("User32")] private static extern int SetForegroundWindow(IntPtr hwnd); // Activate or minimize a window [DllImportAttribute("User32.DLL")] private static extern bool ShowWindow(IntPtr hWnd, int nCmdShow); private const int SW_SHOW = 5; private const int SW_MINIMIZE = 6; private const int SW_RESTORE = 9; private void ActivateApplication(string briefAppName) { Process[] procList = Process.GetProcessesByName(briefAppName); if (procList.Length > 0) { ShowWindow(procList[0].MainWindowHandle, SW_RESTORE); SetForegroundWindow(procList[0].MainWindowHandle); } } 

以下是我在一种或另一种forms上使用了几年的一些代码。 弹出另一个应用程序窗口有一些问题。 一旦你有窗口句柄,执行以下操作:

  if (IsIconic(hWnd)) ShowWindowAsync(hWnd, SW_RESTORE); ShowWindowAsync(hWnd, SW_SHOW); SetForegroundWindow(hWnd); // Code from Karl E. Peterson, www.mvps.org/vb/sample.htm // Converted to Delphi by Ray Lischner // Published in The Delphi Magazine 55, page 16 // Converted to C# by Kevin Gale IntPtr foregroundWindow = GetForegroundWindow(); IntPtr Dummy = IntPtr.Zero; uint foregroundThreadId = GetWindowThreadProcessId(foregroundWindow, Dummy); uint thisThreadId = GetWindowThreadProcessId(hWnd, Dummy); if (AttachThreadInput(thisThreadId, foregroundThreadId, true)) { BringWindowToTop(hWnd); // IE 5.5 related hack SetForegroundWindow(hWnd); AttachThreadInput(thisThreadId, foregroundThreadId, false); } if (GetForegroundWindow() != hWnd) { // Code by Daniel P. Stasinski // Converted to C# by Kevin Gale IntPtr Timeout = IntPtr.Zero; SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, Timeout, 0); SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, Dummy, SPIF_SENDCHANGE); BringWindowToTop(hWnd); // IE 5.5 related hack SetForegroundWindow(hWnd); SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, Timeout, SPIF_SENDCHANGE); } 

我不会发布整个单元,因为它做了其他不相关的事情,但这里是上面代码的常量和导入。

 //Win32 API calls necesary to raise an unowned processs main window [DllImport("user32.dll")] private static extern bool SetForegroundWindow(IntPtr hWnd); [DllImport("user32.dll")] private static extern bool ShowWindowAsync(IntPtr hWnd, int nCmdShow); [DllImport("user32.dll")] private static extern bool IsIconic(IntPtr hWnd); [DllImport("user32.dll", SetLastError = true)] private static extern bool SystemParametersInfo(uint uiAction, uint uiParam, IntPtr pvParam, uint fWinIni); [DllImport("user32.dll", SetLastError = true)] private static extern uint GetWindowThreadProcessId(IntPtr hWnd, IntPtr lpdwProcessId); [DllImport("user32.dll")] private static extern IntPtr GetForegroundWindow(); [DllImport("user32.dll")] private static extern bool AttachThreadInput(uint idAttach, uint idAttachTo, bool fAttach); [DllImport("user32.dll")] static extern bool BringWindowToTop(IntPtr hWnd); [DllImport("user32.dll")] private static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, Int32 nMaxCount); [DllImport("user32.dll")] private static extern int GetWindowThreadProcessId(IntPtr hWnd, ref Int32 lpdwProcessId); [DllImport("User32.dll")] public static extern IntPtr GetParent(IntPtr hWnd); private const int SW_HIDE = 0; private const int SW_SHOWNORMAL = 1; private const int SW_NORMAL = 1; private const int SW_SHOWMINIMIZED = 2; private const int SW_SHOWMAXIMIZED = 3; private const int SW_MAXIMIZE = 3; private const int SW_SHOWNOACTIVATE = 4; private const int SW_SHOW = 5; private const int SW_MINIMIZE = 6; private const int SW_SHOWMINNOACTIVE = 7; private const int SW_SHOWNA = 8; private const int SW_RESTORE = 9; private const int SW_SHOWDEFAULT = 10; private const int SW_MAX = 10; private const uint SPI_GETFOREGROUNDLOCKTIMEOUT = 0x2000; private const uint SPI_SETFOREGROUNDLOCKTIMEOUT = 0x2001; private const int SPIF_SENDCHANGE = 0x2; 

ShowDialog()有不同的窗口行为而不仅仅是Show()吗?

如果你尝试了怎么办:

 msgFrm.Show(); msgFrm.BringToFront(); msgFrm.Focus(); 

TopMost = true; 。启用() ?

哪一个都好?

将它拆分成自己的线程有点邪恶,因为它不能正常工作,如果你不用Application.Run调用它,这将吞噬线程。 在最糟糕的情况下,我猜你可以将它分成不同的进程并通过磁盘或WCF进行通信。

以下解决方案应满足您的要求:

  1. 可以将程序集加载到PowerShell中并实例化主类
  2. 调用此实例上的ShowMessage方法时,将显示并激活一个新窗口
  3. 如果多次调用ShowMessage,则同一窗口将更新其标题文本并激活
  4. 要停止使用该窗口,请调用Dispose方法

第1步 :让我们创建一个临时工作目录(您可以自然地使用自己的目录)

 (powershell.exe) mkdir C:\TEMP\PshWindow cd C:\TEMP\PshWindow 

第2步 :现在让我们定义我们将在PowerShell中与之交互的类:

 // file 'InfoProvider.cs' in C:\TEMP\PshWindow using System; using System.Threading; using System.Windows.Forms; namespace PshWindow { public sealed class InfoProvider : IDisposable { public void Dispose() { GC.SuppressFinalize(this); lock (this._sync) { if (!this._disposed) { this._disposed = true; if (null != this._worker) { if (null != this._form) { this._form.Invoke(new Action(() => this._form.Close())); } this._worker.Join(); this._form = null; this._worker = null; } } } } public void ShowMessage(string msg) { lock (this._sync) { // make sure worker is up and running if (this._disposed) { throw new ObjectDisposedException("InfoProvider"); } if (null == this._worker) { this._worker = new Thread(() => (this._form = new MyForm(this._sync)).ShowDialog()) { IsBackground = true }; this._worker.Start(); while (this._form == null || !this._form.Created) { Monitor.Wait(this._sync); } } // update the text this._form.Invoke(new Action(delegate { this._form.Text = msg; this._form.Activate(); })); } } private bool _disposed; private Form _form; private Thread _worker; private readonly object _sync = new object(); } } 

以及将显示的表格:

 // file 'MyForm.cs' in C:\TEMP\PshWindow using System; using System.Drawing; using System.Threading; using System.Windows.Forms; namespace PshWindow { internal sealed class MyForm : Form { public MyForm(object sync) { this._sync = sync; this.BackColor = Color.LightGreen; this.Width = 200; this.Height = 80; this.FormBorderStyle = FormBorderStyle.SizableToolWindow; } protected override void OnShown(EventArgs e) { base.OnShown(e); this.TopMost = true; lock (this._sync) { Monitor.PulseAll(this._sync); } } private readonly object _sync; } } 

第3步 :让我们编译程序集……

 (powershell.exe) csc /out:PshWindow.dll /target:library InfoProvider.cs MyForm.cs 

第4步 :…并在PowerShell中加载程序集以获得乐趣:

 (powershell.exe) [System.Reflection.Assembly]::LoadFile('C:\TEMP\PshWindow\PshWindow.dll') $a = New-Object PshWindow.InfoProvider $a.ShowMessage('Hello, world') 

现在应该弹出一个标题为“Hello,world”的绿色窗口并激活。 如果重新激活PowerShell窗口并输入:

 $a.ShowMessage('Stack overflow') 

Window的标题应更改为“Stack overflow”,窗口应再次激活。

要停止使用我们的窗口,请处置对象:

 $a.Dispose() 

此解决方案在Windows XP SP3,x86和Windows Vista SP1,x64中均可正常运行。 如果对此解决方案的工作方式存在疑问,我可以通过详细讨论更新此条目。 现在我希望代码不言自明。

非常感谢大家。 我想我已经把它缩短了一点,这就是我在一个单独的线程上放的东西,似乎工作正常。

  private static void StatusChecking() { IntPtr iActiveForm = IntPtr.Zero, iCurrentACtiveApp = IntPtr.Zero; Int32 iMyProcID = Process.GetCurrentProcess().Id, iCurrentProcID = 0; IntPtr iTmp = (IntPtr)1; while (bIsRunning) { try { Thread.Sleep(45); if (Form.ActiveForm != null) { iActiveForm = Form.ActiveForm.Handle; } iTmp = GetForegroundWindow(); if (iTmp == IntPtr.Zero) continue; GetWindowThreadProcessId(iTmp, ref iCurrentProcID); if (iCurrentProcID == 0) { iCurrentProcID = 1; continue; } if (iCurrentProcID != iMyProcID) { SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, IntPtr.Zero, 0); SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, IntPtr.Zero, SPIF_SENDCHANGE); BringWindowToTop(iActiveForm); SetForegroundWindow(iActiveForm); } else iActiveForm = iTmp; } catch (Exception ex) { Definitions.UnhandledExceptionHandler(ex, 103106); } } } 

我不打扰重新定义……

您不需要为此导入任何win32函数。 如果.Focus()不够,表单也应该有一个.BringToFront()方法可以使用。 如果失败,您可以将其.TopMost属性设置为true。 您不希望永远保持真实,因此请调用Application.DoEvents,以便表单可以处理该消息并将其设置为false。

你不是只想让对话框成为调用表单的子代吗?

为此,您需要在调用窗口中传递并使用ShowDialog(IWin32Window所有者)方法。