C# Force Form Focus
因此,在问这个问题之前,我确实搜索过Google和SO。基本上,我有一个DLL,其中已编译了一个窗体。该表格将用于在屏幕上显示信息。最终它将是异步的,并且在dll中公开了很多自定义项。现在,我只希望它正确显示。我遇到的问题是,我通过在Powershell会话中加载dll来使用它。因此,当我尝试显示表单并将其放到顶部并获得焦点时,在其他所有应用程序上显示都没有问题,但是我一生都无法将其显示在Powershell窗口中。这是我当前用来尝试显示的代码。我敢肯定,一旦我弄清了它的大部分,就不需要了,这只是我通过google找到的所有东西。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| 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,感谢您的信息。这是我根据您的建议尝试的:
1 2 3 4
| msgFrm.ShowDialog();
msgFrm.BringToFront();
msgFrm.Focus();
Application.DoEvents(); |
该表格仍在Powershell会话下出现。我将继续研究线程。我之前已经生成了线程,但从未在父线程与子线程进行通讯的地方生成过线程,因此我们将了解它的运行情况。
到目前为止,所有想法都得到了人们的认可。
好的,线程解决了这个问题。 @Quarrelsome,我确实尝试了这两种方法。没有一个(也没有一起)。我对使用线程有什么弊端感到好奇?我没有使用Application.Run,??但是我还没有遇到任何问题。我正在使用一个父线程和子线程都可以访问的介体类。在该对象中,我使用ReaderWriterLock锁定一个属性,该属性表示要在子线程创建的表单上显示的消息。父级锁定该属性,然后编写应显示的内容。子线程锁定该属性,并读取它应将表单上的标签更改为的内容。孩子必须在轮询间隔(我默认将其设置为500ms)上执行此操作,这并不是我真正满意的事情,但是我找不到一种事件驱动的方式来让孩子线程知道属性已更改,所以我我坚持投票。
这是我几年使用一种或另一种形式的一些代码。在另一个应用程序中弹出窗口有一些技巧。有了窗口句柄后,请执行以下操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| 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);
} |
我不会发布整个单元,因为它会执行其他不相关的事情
但这里是上述代码的常量和导入。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| //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; |
我也无法激活并将窗口置于前台。这是最终为我工作的代码。我不确定是否能解决您的问题。
基本上,先调用ShowWindow()然后再调用SetForegroundWindow()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| 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);
}
} |
TopMost = true;
。启用() ?
那些有什么好处吗?
将其拆分为自己的线程有点邪恶,因为如果不使用Application.Run调用它,它将无法正常工作,这将吞噬线程。在最坏的情况下,我想您可以将其分离到另一个进程中,并通过磁盘或WCF进行通信。
ShowDialog()的窗口行为是否与Show()不同?
如果您尝试了怎么办:
1 2 3
| msgFrm.Show();
msgFrm.BringToFront();
msgFrm.Focus(); |
以下解决方案应满足您的要求:
程序集可以加载到PowerShell中并实例化主类
在此实例上调用ShowMessage方法时,将显示并激活一个新窗口
如果您多次调用ShowMessage,则同一窗口将更新其标题文本并被激活
要停止使用该窗口,请调用Dispose方法
步骤1:让我们创建一个临时工作目录(您自然可以使用自己的目录)
1 2 3
| (powershell.exe)
mkdir C:\\TEMP\\PshWindow
cd C:\\TEMP\\PshWindow |
步骤2:现在,我们定义将在PowerShell中与之交互的类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62
| // 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();
}
} |
以及将显示的表格:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| // 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:让我们编译程序集...
1 2
| (powershell.exe)
csc /out:PshWindow.dll /target:library InfoProvider.cs MyForm.cs |
步骤4:...并在PowerShell中加载程序集以使其有趣:
1 2 3 4
| (powershell.exe)
[System.Reflection.Assembly]::LoadFile('C:\\TEMP\\PshWindow\\PshWindow.dll')
$a = New-Object PshWindow.InfoProvider
$a.ShowMessage('Hello, world') |
现在应该弹出一个绿色的窗口,标题为" Hello,world"。如果您重新激活PowerShell窗口并输入:
1
| $a.ShowMessage('Stack overflow') |
窗口的标题应更改为"堆栈溢出",并且窗口应再次处于活动状态。
要停止使用我们的窗口,请处置该对象:
此解决方案在Windows XP SP3 x86和Windows Vista SP1 x64中均可以正常工作。如果对该解决方案的工作方式有疑问,我可以通过详细讨论来更新此条目。目前,我希望代码能不言自明。
非常感谢。
我想我把它缩短了一些,这是我放在单独线程上的内容,而且似乎工作正常。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38
| 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);
}
}
} |
我不介意定义。
您不只是希望对话框成为调用表单的子级吗?
为此,您需要在调用窗口中输入通行证,
使用ShowDialog(IWin32Window owner)方法。
您不需要为此导入任何win32函数。如果.Focus()不够,则表单还应具有.BringToFront()方法供您使用。如果失败,则可以将其.TopMost属性设置为true。您不想永远将其保留为真,因此请调用Application.DoEvents,以便表单可以处理该消息并将其设置回false。
|