2008-09-05 35 views
22

Entonces, busqué en google y SO antes de hacer esta pregunta. Básicamente tengo una DLL que tiene un formulario compilado en ella. El formulario se usará para mostrar información en la pantalla. Eventualmente será asincrónico y expondrá mucha personalización en el dll. Por ahora solo quiero que se muestre correctamente. El problema que estoy teniendo es que uso el archivo dll al cargarlo en una sesión de Powershell. Así que cuando trato de mostrar el formulario y lograr que llegue a la cima y tener el foco, no tiene ningún problema con mostrarlo en todas las demás aplicaciones, pero no puedo hacerlo verlo en la ventana de Powershell. . Aquí está el código que estoy usando para tratar de que se muestre. Estoy seguro de que la mayoría de ellos no serán necesarios una vez que lo encuentre, esto solo representa todas las cosas que encontré a través de google.C# Force Form Focus

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

como digo Estoy seguro de que la mayoría de lo que se necesita o no o incluso plano erróneas, sólo quería mostrar las cosas que había intentado. Además, como mencioné, planeo que esto se muestre de forma asíncrona en algún momento que sospecho que terminará requiriendo un hilo separado. ¿Se dividiría el formulario en su propio hilo para facilitar el enfoque en la sesión de Powershell?


@Joel, gracias por la información. Esto es lo que he intentado en base a su sugerencia:

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

La forma aún aparece bajo la sesión de PowerShell. Procederé a resolver el enhebrado. He generado hilos antes, pero nunca donde el hilo padre necesitaba hablar con el hilo hijo, así que ya veremos cómo funciona.

Thnks para todas las ideas hasta ahora gente.


Bien, el enhebrado resolvió el problema. @Quarrelsome, probé ambos. Ninguno (ni ambos juntos) funcionó. Tengo curiosidad sobre lo que es malo sobre el uso de enhebrar? No estoy utilizando Application.Run y ​​aún no he tenido un problema. Estoy usando una clase de mediador a la que tienen acceso el subproceso padre y el subproceso secundario. En ese objeto, estoy usando ReaderWriterLock para bloquear una propiedad que representa el mensaje que quiero que se muestre en el formulario que crea el subproceso secundario. El padre bloquea la propiedad y luego escribe lo que se debe mostrar. El subproceso secundario bloquea la propiedad y lee a qué debería cambiar la etiqueta en el formulario. El niño tiene que hacer esto en un intervalo de sondeo (lo prefiero a 500ms) de lo que no estoy muy contento, pero no pude encontrar un modo impulsado por eventos para que el hilo hijo supiera que la libertad había cambiado, así que ' Estoy atascado con las encuestas.

+0

¿Puedes llamar Focus() en uno de los controles en el formulario? –

+0

Gracias por las respuestas. @Chad, simplemente tratando de enfocar() un control en el formulario terminó con los mismos resultados que ya estaba obteniendo. @Dean, creo que para utilizar su método tendré que dividir el formulario en su propio hilo. Planeé hacerlo de todos modos en algún momento, así que supongo que comenzaré con eso ahora. – EBGreen

Respuesta

16

También tuve problemas para activar y traer una ventana al primer plano. Aquí está el código que finalmente funcionó para mí. No estoy seguro si esto resolverá tu problema.

Básicamente, llame a ShowWindow() y luego a 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); 
    } 
} 
+0

qué parámetro debo pasar a ShowWindow(); ¿¿¿¿método???? – Hassanation

+0

¿Dónde se encuentran las definiciones originales para todas estas constantes SW_? – gonzobrains

+1

@gonzobrains http://pinvoke.net –

0

No necesita importar ninguna función win32 para esto. Si .Focus() no es suficiente, el formulario también debe tener un método .BringToFront() que pueda usar. Si eso falla, puede configurar su propiedad .TopMost en verdadero. No desea dejar verdadero para siempre, entonces llame a Application.DoEvents para que el formulario pueda procesar ese mensaje y establecerlo de nuevo en falso.

3

No ShowDialog() tienen un comportamiento de ventana diferente a solamente Mostrar()?

Lo que si se trató de:

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

TopMost = true; .Activar()?

Cualquiera de los cuales sirve?

Dividirlo en su propio hilo es un poco malo, ya que no funcionará correctamente si no lo llamas con Application.Run y ​​eso tragará el hilo. En el peor de los casos, supongo que podría separarlo en un proceso diferente y comunicarse a través del disco o WCF.

16

Aquí hay un código que he usado de una forma u otra durante algunos años. Hay algunos inconvenientes para hacer una ventana en otra aplicación emergente. Una vez que tenga el identificador de ventana hacer esto:

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

que no va a publicar toda la unidad, ya que desde hace otras cosas que no son relevantes pero aquí son las constantes e importaciones para el código de seguridad.

//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; 
+1

Esta es la única respuesta que funcionó para mí, ¡gracias a Gevin por compartir! –

+2

Por cierto, parece una práctica peligrosa de acuerdo con Raymond Chen esto puede provocar la congelación de la IU: Te advertí: Los peligros de adjuntar colas de entrada http://blogs.msdn.com/b/oldnewthing/archive/2008/ 08/01/8795860.aspx ¿Alguna vez conociste este problema, Kevin? –

+0

He estado utilizando este código durante años en muchos sitios de clientes en una aplicación que funciona 24x7 sin problemas informados. Eso no significa que Chen no tenga razón, pero si es un problema, no sucede muy a menudo. –

0

¿No solo desea que el diálogo sea un elemento secundario del formulario de llamada?

Para hacer eso necesitará el pase en la ventana de llamada y utilice el método ShowDialog (propietario de IWin32Window).

2

la siguiente solución debe satisfacer sus requisitos:

  1. Asamblea se puede cargar en PowerShell y clase principal instancia
  2. Cuando se llama al método ShowMessage en este caso, se muestra una nueva ventana y activa
  3. Si llama a ShowMessage varias veces, esta misma ventana actualiza su texto de título y se activa
  4. Para detener el uso de la ventana, llame al método de eliminación

Paso 1: Vamos a crear un directorio de trabajo temporal (se puede utilizar de forma natural su propio dir)

(powershell.exe) 
mkdir C:\TEMP\PshWindow 
cd C:\TEMP\PshWindow 

Paso 2: Ahora vamos a definir la clase que estén en contacto con en 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í como el formulario que se muestra a continuación:

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

Paso 3: Vamos a compilar el ensamblado ...

(powershell.exe) 
csc /out:PshWindow.dll /target:library InfoProvider.cs MyForm.cs 

Paso 4: ... y cargar el ensamblado en PowerShell para divertirse con él:

(powershell.exe) 
[System.Reflection.Assembly]::LoadFile('C:\TEMP\PshWindow\PshWindow.dll') 
$a = New-Object PshWindow.InfoProvider 
$a.ShowMessage('Hello, world') 

Un verde -ish ventana con el título 'Hola, mundo' debería ahora emergente y estar activo.Si reactiva la ventana de PowerShell y escriba: título

$a.ShowMessage('Stack overflow') 

del Ventana debería cambiar a 'desbordamiento de pila' y la ventana debe estar activo de nuevo.

que dejar de trabajar con la ventana, disponer del objeto:

$a.Dispose() 

Esta solución funciona como se esperaba en Windows XP SP3, x86 y Windows Vista SP1, x64. Si hay dudas sobre cómo funciona esta solución, puedo actualizar esta entrada con una discusión detallada. Por ahora espero que el código se explique por sí mismo.

1

Muchísimas gracias a la gente. Creo que lo he hecho un poco más corto, esto es lo que puse en un hilo separado y parece estar funcionando bien.

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 molestan repasting las definiciones ...