C#强制表格焦点

时间:2008-09-05 15:25:11

标签: c# winforms

所以,在询问这个问题之前,我确实搜索过google和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毫秒)这样做,我不是很高兴,但我找不到一个事件驱动的方式让孩子线程知道这个项目已经改变,所以我'我坚持投票。

8 个答案:

答案 0 :(得分:16)

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

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

答案 1 :(得分:16)

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

      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;

答案 2 :(得分:3)

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

如果你尝试了怎么办?

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

答案 3 :(得分:3)

TopMost = true; .Activate()?

哪一个都不错?

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

答案 4 :(得分:2)

以下解决方案应符合您的要求:

  1. 程序集可以加载到PowerShell和主类实例化
  2. 调用此实例上的ShowMessage方法时,会显示并激活一个新窗口
  3. 如果多次调用ShowMessage,则同一窗口会更新其标题文本并激活
  4. 要停止使用该窗口,请调用Dispose方法
  5. 第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中均可正常运行。如果对此解决方案的工作方式存在疑问,我可以通过详细讨论更新此条目。现在我希望代码不言自明。

答案 5 :(得分:1)

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

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

我不打扰重新定义......

答案 6 :(得分:0)

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

答案 7 :(得分:0)

难道您不希望对话框成为调用表单的子级吗?

要做到这一点,你需要在调用窗口中传递和 使用ShowDialog(IWin32Window所有者)方法。