C # Force Form Focus - PullRequest
       47

C # Force Form Focus

22 голосов
/ 05 сентября 2008

Итак, я выполнил поиск в Google и SO, прежде чем задавать этот вопрос. В основном у меня есть DLL, в которой есть скомпилированная форма. Форма будет использоваться для отображения информации на экране. В конце концов это будет асинхронным и выставить много настроек в DLL. Сейчас я просто хочу, чтобы он отображался правильно. Проблема, которая у меня возникла, заключается в том, что я использую DLL, загружая ее в сеансе Powershell. Поэтому, когда я пытаюсь отобразить форму и заставить ее подняться на вершину и сфокусироваться, у нее нет проблем с отображением поверх всех других приложений, но я не могу на всю жизнь заставить ее отображать поверх окна 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?


@ Джоэл, спасибо за информацию. Вот что я попробовал, основываясь на вашем предложении:

msgFrm.ShowDialog();
msgFrm.BringToFront();
msgFrm.Focus();
Application.DoEvents();

Форма все еще появляется в сеансе Powershell. Я приступлю к разработке потоков. Я порождал потоки раньше, но никогда там, где родительскому потоку нужно было общаться с дочерним потоком, так что посмотрим, как он пойдет.

Спасибо за все идеи до сих пор люди.


Хорошо, поточность позаботилась о проблеме. @ Quarrelsome, я попробовал оба из них. Ни (ни то и другое вместе) не работало. Мне интересно, что плохого в использовании потоков? Я не использую Application.Run, и у меня еще есть проблема. Я использую класс-посредник, к которому родительский поток и дочерний поток имеют доступ. В этом объекте я использую ReaderWriterLock для блокировки одного свойства, которое представляет сообщение, которое я хочу отобразить в форме, которую создает дочерний поток. Родитель блокирует свойство, затем записывает, что должно отображаться. Дочерний поток блокирует свойство и считывает, на что он должен изменить метку в форме. Ребенок должен делать это в интервале опроса (по умолчанию это 500 мс), что меня не очень радует, но я не смог найти управляемый событиями способ сообщить дочернему потоку, что свойство изменилось, поэтому я ' м застрял с опросом.

Ответы [ 8 ]

16 голосов
/ 18 сентября 2008

Вот код, который я использовал в той или иной форме в течение нескольких лет. Есть несколько ошибок для создания окна в другом приложении. Когда у вас есть дескриптор окна, сделайте это:

      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;
16 голосов
/ 05 сентября 2008

У меня также были проблемы с активацией и выводом окна на передний план. Вот код, который в итоге сработал для меня. Я не уверен, решит ли это вашу проблему.

По сути, вызовите 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);
    }
}
3 голосов
/ 05 сентября 2008

TopMost = true; .Activate ()?

Что-нибудь из этого хорошо?

Разделение его на собственный поток - это немного зло, поскольку он не будет работать должным образом, если вы не вызовете его с помощью Application.Run, и это поглотит поток. В худшем случае, я думаю, вы могли бы разделить его на другой процесс и обмениваться данными через диск или WCF.

3 голосов
/ 05 сентября 2008

Разве ShowDialog () не имеет поведения окна, отличного от Show () ?

Что если вы попробовали:

msgFrm.Show();
msgFrm.BringToFront();
msgFrm.Focus();
2 голосов
/ 13 января 2009

Следующее решение должно соответствовать вашим требованиям:

  1. Сборка может быть загружена в PowerShell и создание экземпляра основного класса
  2. Когда вызывается метод ShowMessage для этого экземпляра, отображается и активируется новое окно
  3. Если вы вызываете ShowMessage несколько раз, это же окно обновляет текст заголовка и активируется
  4. Чтобы прекратить использование окна, вызовите метод 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')

Теперь должно появиться зеленое окно с заголовком «Привет, мир». Если вы снова активируете окно PowerShell и введите:

$a.ShowMessage('Stack overflow')

Название окна должно измениться на «Переполнение стека», и окно должно снова стать активным.

Чтобы прекратить работу с нашим окном, утилизируйте объект:

$a.Dispose()

Это решение работает, как и ожидалось, в Windows XP SP3, x86 и Windows Vista SP1, x64. Если есть вопрос о том, как работает это решение, я могу обновить эту запись с подробным обсуждением. Пока я надеюсь, что код, если не требует пояснений.

1 голос
/ 04 апреля 2009

Огромное спасибо людям.
Я думаю, что я сделал это немного короче, вот что я положил на отдельную нить и, кажется, работает нормально.

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

Я не потрудился переписать определения ...

0 голосов
/ 01 октября 2008

Разве вы не хотите, чтобы диалог был потомком формы вызова?

Для этого вам понадобится пропуск в окне вызова и используйте метод ShowDialog (владелец IWin32Window).

0 голосов
/ 05 сентября 2008

Для этого вам не нужно импортировать какие-либо функции win32. Если .Focus () недостаточно, форма также должна иметь метод .BringToFront (), который вы можете использовать. Если это не удастся, вы можете установить для его свойства .TopMost значение true. Вы не хотите оставлять это значение true навсегда, поэтому затем вызовите Application.DoEvents, чтобы форма могла обработать это сообщение и установить обратно значение false.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...