C# 强制表单焦点
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/46030/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
C# Force Form Focus
提问by EBGreen
So, I did search google and SO prior to asking this question. Basically I have a DLL that has a form compiled into it. The form will be used to display information to the screen. Eventually it will be asynchronous and expose a lot of customization in the dll. For now I just want it to display properly. The problem that I am having is that I use the dll by loading it in a Powershell session. So when I try to display the form and get it to come to the top and have focus, It has no problem with displaying over all the other apps, but I can't for the life of me get it to display over the Powershell window. Here is the code that I am currently using to try and get it to display. I am sure that the majority of it won't be required once I figure it out, this just represents all the things that I found via google.
所以,在问这个问题之前,我确实搜索了 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);
}
}
As I say I'm sure that most of that is either not needed or even flat out wrong, I just wanted to show the things that I had tried. Also, as I mentioned, I plan to have this be asynchronously displayed at some point which I suspect will wind up requiring a separate thread. Would splitting the form out into it's own thread make it easier to cause it to get focus over the Powershell session?
正如我所说,我确信其中的大部分内容要么是不需要的,要么是完全错误的,我只是想展示我尝试过的东西。此外,正如我所提到的,我计划在某个时间点异步显示它,我怀疑这最终会需要一个单独的线程。将表单拆分为它自己的线程是否会使其更容易将注意力集中在 Powershell 会话上?
@Joel, thanks for the info. Here is what I tried based on your suggestion:
@Joel,感谢您提供的信息。以下是我根据您的建议尝试的方法:
msgFrm.ShowDialog();
msgFrm.BringToFront();
msgFrm.Focus();
Application.DoEvents();
The form still comes up underthe Powershell session. I'll proceed with working out the threading. I've spawned threads before but never where the parent thread needed to talk to the child thread, so we'll see how it goes.
该表单仍然出现在Powershell 会话下。我将继续解决线程问题。我之前已经生成了线程,但从来没有在父线程需要与子线程通信的地方生成线程,所以我们将看看它是如何进行的。
Thnks for all the ideas so far folks.
到目前为止,感谢所有的想法。
Ok, threading it took care of the problem. @Quarrelsome, I did try both of those. Neither (nor both together) worked. I am curious as to what is evil about using threading? I am not using Application.Run and I have yet to have a problem. I am using a mediator class that both the parent thread and the child thread have access to. In that object I am using a ReaderWriterLock to lock one property that represents the message that I want displayed on the form that the child thread creates. The parent locks the property then writes what should be displayed. The child thread locks the property and reads what it should change the label on the form to. The child has to do this on a polling interval (I default it to 500ms) which I'm not real happy about, but I could not find an event driven way to let the child thread know that the proerty had changed, so I'm stuck with polling.
好的,线程处理解决了这个问题。@Quarrelsome,我确实尝试了这两个。两者(或两者一起)都不起作用。我很好奇使用线程有什么坏处?我没有使用 Application.Run 并且我还没有遇到问题。我正在使用父线程和子线程都可以访问的中介类。在该对象中,我使用 ReaderWriterLock 来锁定一个属性,该属性表示我希望在子线程创建的表单上显示的消息。父级锁定该属性,然后写入应显示的内容。子线程锁定属性并读取它应该将表单上的标签更改为什么。孩子必须在轮询间隔(我默认为 500 毫秒)上执行此操作,我对此并不满意,但我找不到事件驱动的方式让子线程知道属性已更改,
采纳答案by Dean Hill
I also had trouble activating and bringing a window to the foreground. Here is the code that eventually worked for me. I'm not sure if it will solve your problem.
我也无法激活并将窗口置于前台。这是最终对我有用的代码。我不确定它是否会解决您的问题。
Basically, call ShowWindow() then SetForegroundWindow().
基本上,先调用 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);
}
}
回答by Joel Coehoorn
You shouldn't need to import any win32 functions for this. If .Focus() isn't enough the form should also have a .BringToFront() method you can use. If that fails, you can set it's .TopMost property to true. You don't want to leaveit true forever, so then call Application.DoEvents so the form can process that message and set it back to false.
您不需要为此导入任何 win32 函数。如果 .Focus() 不够,表单还应该有一个 .BringToFront() 方法可以使用。如果失败,您可以将其 .TopMost 属性设置为 true。你不想离开它真的永远,所以再调用Application.DoEvents这样的形式可以处理的消息,并将其设置为false。
回答by sieben
Doesn't ShowDialog()have different window behavior than just Show()?
没有的ShowDialog()已经不仅仅是不同的窗口行为展() ?
What if you tried:
如果您尝试过会怎样:
msgFrm.Show();
msgFrm.BringToFront();
msgFrm.Focus();
回答by Quibblesome
TopMost = true; .Activate() ?
TopMost = 真;。启用() ?
Either of those any good?
这两个有什么好处吗?
Splitting it out into its own thread is a bit evil as it wont work properly if you don't call it with Application.Run and that will swallow up the thread. In the worst case scenario I guess you could separate it out into a different process and communicate via the disk or WCF.
将其拆分为自己的线程有点邪恶,因为如果您不使用 Application.Run 调用它,它将无法正常工作,并且会吞噬线程。在最坏的情况下,我想您可以将其分离到不同的进程中并通过磁盘或 WCF 进行通信。
回答by Kevin Gale
Here is some code that I've used on one form or another for a few years. There are a few gotchas to making a window in another app pop up. Once you have the window handle do this:
这是我在一种或另一种形式上使用了几年的一些代码。在另一个应用程序中弹出一个窗口有一些问题。获得窗口句柄后,请执行以下操作:
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);
}
I won't post the whole unit since since it does other things that aren't relevant but here are the constants and imports for the above code.
我不会发布整个单元,因为它做了其他不相关的事情,但这里是上述代码的常量和导入。
//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;
回答by jyoung
Don't you just want the dialog to be a child of the calling form?
难道您不只是希望对话框成为调用表单的子项吗?
To do that you'll need the pass in the calling window and use the ShowDialog( IWin32Window owner ) method.
为此,您需要调用窗口中的传递并使用 ShowDialog( IWin32Window owner ) 方法。
回答by Milan Gardian
The following solution should meet your requirements:
以下解决方案应满足您的要求:
- Assembly can be loaded into PowerShell and main class instantiated
- When ShowMessage method on this instance is called, a new window is shown and activated
- If you call ShowMessage multiple times, this same window updates its title text and is activated
- To stop using the window, call Dispose method
- 程序集可以加载到 PowerShell 中并实例化主类
- 当调用此实例上的 ShowMessage 方法时,将显示并激活一个新窗口
- 如果您多次调用 ShowMessage,则同一窗口会更新其标题文本并被激活
- 要停止使用该窗口,请调用 Dispose 方法
Step 1: Let's create a temporary working directory (you can naturally use your own dir)
第一步:我们创建一个临时工作目录(你自然可以使用自己的目录)
(powershell.exe)
mkdir C:\TEMP\PshWindow
cd C:\TEMP\PshWindow
Step 2: Now let's define class that we will be interacting with in PowerShell:
第 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();
}
}
As well as the Form that will be shown:
以及将显示的表单:
// 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;
}
}
Step 3: Let's compile the assembly...
第 3 步:让我们编译程序集...
(powershell.exe)
csc /out:PshWindow.dll /target:library InfoProvider.cs MyForm.cs
Step 4: ... and load the assembly in PowerShell to have fun with it:
第 4 步:...并在 PowerShell 中加载程序集以体验它的乐趣:
(powershell.exe)
[System.Reflection.Assembly]::LoadFile('C:\TEMP\PshWindow\PshWindow.dll')
$a = New-Object PshWindow.InfoProvider
$a.ShowMessage('Hello, world')
A green-ish window with title 'Hello, world' should now pop-up and be active. If you reactivate the PowerShell window and enter:
现在应该弹出一个标题为“Hello, world”的绿色窗口并处于活动状态。如果您重新激活 PowerShell 窗口并输入:
$a.ShowMessage('Stack overflow')
The Window's title should change to 'Stack overflow' and the window should be active again.
窗口的标题应更改为“堆栈溢出”,并且窗口应再次处于活动状态。
To stop working with our window, dispose the object:
要停止使用我们的窗口,请处置该对象:
$a.Dispose()
This solution works as expected in both Windows XP SP3, x86 and Windows Vista SP1, x64. If there are question about how this solution works I can update this entry with detailed discussion. For now I'm hoping the code if self-explanatory.
此解决方案可在 Windows XP SP3、x86 和 Windows Vista SP1、x64 中按预期工作。如果对此解决方案的工作方式有疑问,我可以通过详细讨论更新此条目。现在我希望代码不言自明。
回答by Milan Gardian
Huge thanks people.
I think I've made it a bit shorter, here's what I put on a seperate thread and seems to be working ok.
非常感谢人们。
我想我已经把它缩短了一点,这是我放在一个单独的线程上,似乎工作正常。
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);
}
}
}
I don`t bother repasting the definitions...
我懒得重新定义定义...