C#强制表单焦点
因此,在询问此问题之前,我确实搜索过Google和SO。基本上,我有一个DLL,其中已编译了一个窗体。该表格将用于在屏幕上显示信息。最终它将是异步的,并且在dll中公开了很多自定义项。现在,我只希望它能正确显示。我遇到的问题是,我通过在Powershell会话中加载dll来使用它。因此,当我尝试显示表单并将其放到顶部并获得焦点时,在所有其他应用程序上显示都没有问题,但是我一生都无法将其显示在Powershell窗口中。这是我当前正在尝试使其显示的代码。我敢肯定,一旦我弄清了它的大部分,就不需要了,这只是我在google上找到的所有内容。
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锁定一个属性,该属性表示要在子线程创建的表单上显示的消息。父级锁定该属性,然后写出应显示的内容。子线程将锁定该属性,并读取它应将表单上的标签更改为的内容。孩子必须在一个轮询间隔(我默认为500ms)上执行此操作,这并不是我真正满意的事情,但是我找不到事件驱动的方式来让孩子线程知道属性已更改,所以我我坚持投票。
解决方案
回答
我也无法激活并将窗口置于前台。这是最终为我工作的代码。我不确定是否能解决问题。
基本上,先调用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); } }
回答
我们不需要为此导入任何win32函数。如果.Focus()还不够,则表单还应具有.BringToFront()方法供我们使用。如果失败,则可以将其.TopMost属性设置为true。我们不想永远将其保留为真,因此请调用Application.DoEvents,以便表单可以处理该消息并将其设置回false。
回答
ShowDialog()是否具有与Show()不同的窗口行为?
如果我们尝试了怎么办:
msgFrm.Show(); msgFrm.BringToFront(); msgFrm.Focus();
回答
TopMost = true;
。启用() ?
那些有什么好处吗?
将其拆分为自己的线程有点邪恶,因为如果不使用Application.Run调用它,它将无法正常工作,这将吞噬线程。在最坏的情况下,我想我们可以将其分离到另一个进程中,并通过磁盘或者WCF进行通信。
回答
这是几年来我在一种或者另一种形式上使用过的一些代码。在弹出另一个应用程序中的窗口时有一些技巧。有了窗口句柄后,请执行以下操作:
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(IWin32Window owner)方法。
回答
以下解决方案应满足要求:
- 程序集可以加载到PowerShell中并实例化主类
- 在此实例上调用ShowMessage方法时,将显示并激活一个新窗口
- 如果我们多次调用ShowMessage,则同一窗口将更新其标题文本并被激活
- 要停止使用该窗口,请调用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')
窗口的标题应更改为"堆栈溢出",并且窗口应再次处于活动状态。
要停止使用我们的窗口,请处置该对象:
$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); } } }
我不介意定义...