【问题标题】:C# Force Form FocusC# 强制表单焦点
【发布时间】:2008-09-05 15:25:11
【问题描述】:

所以,在问这个问题之前,我确实搜索了谷歌和 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 毫秒)上执行此操作,我对此并不满意,但我找不到事件驱动的方式让子线程知道属性已更改,所以我我坚持轮询。

【问题讨论】:

  • 你能在表单中的一个控件上调用 Focus() 吗?
  • 感谢您的回复。 @Chad,只是尝试 Focus() 表单上的控件,结果与我已经得到的结果相同。 @Dean,我认为要使用您的方法,我需要将表单拆分为自己的线程。我计划在某个时候这样做,所以我想我现在就开始吧。

标签: c# winforms


【解决方案1】:

我也无法激活窗口并将其置于前台。这是最终对我有用的代码。我不确定它是否能解决您的问题。

基本上,调用 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);
    }
}

【讨论】:

  • 在哪里可以找到所有这些 SW_ 常量的原始定义?
【解决方案2】:

这是我几年来在一种或另一种形式上使用的一些代码。在另一个应用程序中弹出一个窗口有一些问题。获得窗口句柄后,请执行以下操作:

      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;

【讨论】:

  • 这是唯一对我有用的答案,感谢 Gevin 的分享!
  • 顺便说一句,根据 Raymond Chen 的说法,这似乎是一种危险的做法,这可能会引发 UI 冻结:我警告过你:附加输入队列的危险blogs.msdn.com/b/oldnewthing/archive/2008/08/01/8795860.aspxKevin 你遇到过这个问题吗?
  • 多年来一直在许多客户站点中使用此代码,该应用程序 24x7 运行,没有报告任何问题。这并不意味着陈是不对的,但如果这是一个问题,它不会经常发生。
  • 这通常很好用,但是如果 CMD 窗口有焦点,它就不起作用,我相信这是因为 CMD 窗口没有消息循环,所以你不能 AttachThreadInput。跨度>
  • 对 CMD 窗口感兴趣。从来没有碰到过那个。似乎没有什么能完全奏效。这令人沮丧。作为用户,我讨厌突然弹出并窃取焦点的窗口。但是我们软件的用户一直要求这样做。我们不断收到诸如“每小时弹出一个窗口并强制用户输入一些数字”之类的要求。
【解决方案3】:

ShowDialog() 的窗口行为是否与 Show() 不同?

如果你尝试过:

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

【讨论】:

    【解决方案4】:

    TopMost = true; .Activate() ?

    这两个有什么好处?

    将其拆分到自己的线程中有点邪恶,因为如果您不使用 Application.Run 调用它,它将无法正常工作,并且会吞噬线程。在最坏的情况下,我猜您可以将其分离到不同的进程中并通过磁盘或 WCF 进行通信。

    【讨论】:

      【解决方案5】:

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

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

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

      (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 中都能正常工作。如果对此解决方案的工作原理有疑问,我可以通过详细讨论更新此条目。现在我希望代码能不言自明。

      【讨论】:

        【解决方案6】:

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

        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);
                }
            }
        }
        

        我懒得重复定义...

        【讨论】:

          【解决方案7】:

          您不需要为此导入任何 win32 函数。如果 .Focus() 还不够,表单还应该有一个可以使用的 .BringToFront() 方法。如果失败,您可以将其 .TopMost 属性设置为 true。您不想它永远为真,然后调用 Application.DoEvents 以便表单可以处理该消息并将其设置回假。

          【讨论】:

            【解决方案8】:

            您不希望对话框成为调用表单的子窗体吗?

            为此,您需要调用窗口中的通行证,并且 使用 ShowDialog(IWin32Window owner) 方法。

            【讨论】:

              猜你喜欢
              • 2012-01-17
              • 2014-10-29
              • 2017-06-25
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-10-08
              相关资源
              最近更新 更多