2008-09-05 3 views
22

Fondamentalement, j'ai une DLL qui a un formulaire compilé en elle. Le formulaire sera utilisé pour afficher des informations à l'écran. Finalement, il sera asynchrone et exposera beaucoup de personnalisation dans la DLL. Pour l'instant, je veux juste qu'il s'affiche correctement. Le problème que j'ai est que j'utilise la DLL en le chargeant dans une session Powershell. Donc, quand j'essaie d'afficher le formulaire et de le mettre au point, il n'a aucun problème à afficher sur toutes les autres applications, mais je ne peux pas pour la vie de moi l'afficher sur la fenêtre Powershell . Voici le code que j'utilise actuellement pour essayer de l'afficher. Je suis sûr que la majorité ne sera pas nécessaire une fois que je l'aurai compris, cela représente tout ce que j'ai trouvé via 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); 
    } 
} 

Comme je le dis, je suis sûr que la plupart de ce qui est pas nécessaire ou même dans l'erreur, je voulais juste montrer les choses que j'avais essayé. De plus, comme je l'ai mentionné, j'ai l'intention de faire en sorte que cela soit affiché de façon asynchrone à un moment donné, ce qui, je le suppose, se terminera par un fil séparé. Est-ce que diviser le formulaire dans son propre thread le rend plus facile de le faire se concentrer sur la session Powershell?


@Joel, merci pour l'info. Voici ce que j'ai essayé en fonction de votre suggestion:

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

La forme se heurte encore sous la session Powershell. Je vais continuer à travailler sur le filetage. J'ai généré des threads auparavant mais jamais là où le thread parent avait besoin de parler au thread fils, donc nous verrons comment ça se passe.

Thnks pour toutes les idées jusqu'ici les gens.


Ok, l'enfilage s'est occupé du problème. @Quarrelsome, j'ai essayé les deux. Ni l'un ni l'autre (ni les deux ensemble) n'a fonctionné. Je suis curieux de savoir ce qui est mal à propos de l'utilisation du filetage? Je n'utilise pas Application.Run et je n'ai toujours pas de problème. J'utilise une classe de médiateur à laquelle le thread parent et le thread enfant ont accès. Dans cet objet, j'utilise un ReaderWriterLock pour verrouiller une propriété qui représente le message que je veux afficher sur le formulaire que le thread enfant crée. Le parent verrouille la propriété puis écrit ce qui doit être affiché. Le thread enfant verrouille la propriété et lit ce qu'il doit modifier l'étiquette du formulaire. L'enfant doit faire cela sur un intervalle d'interrogation (je le par défaut à 500ms) dont je ne suis pas très heureux, mais je n'ai pas pu trouver un moyen de faire savoir au fils fils que la propriété avait changé, alors Je suis coincé avec l'interrogation.

+0

Pouvez-vous appeler simplement mise au point() sur l'un des contrôles sous la forme? –

+0

Merci pour les réponses. @Chad, essayant juste de Focus() un contrôle sur le formulaire s'est terminé avec les mêmes résultats que je recevais déjà. @Dean, je pense que pour utiliser votre méthode, je devrais diviser le formulaire en son propre fil. Je prévoyais de faire ce chemin à un moment donné, donc je suppose que je vais commencer à ce sujet maintenant. – EBGreen

Répondre

16

J'ai également eu du mal à activer et à mettre une fenêtre au premier plan. Voici le code qui a finalement fonctionné pour moi. Je ne suis pas sûr si cela va résoudre votre problème.

Fondamentalement, appelez ShowWindow() puis 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

quel paramètre dois-je passer à ShowWindow(); méthode???? – Hassanation

+0

Où trouvez-vous les définitions originales pour toutes ces constantes SW_? – gonzobrains

+1

@gonzobrains http://pinvoke.net –

0

Vous ne devriez pas avoir besoin d'importer des fonctions win32 pour cela. Si .Focus() n'est pas suffisant, le formulaire doit également avoir une méthode .BringToFront() que vous pouvez utiliser. Si cela échoue, vous pouvez définir sa propriété .TopMost sur true. Vous ne voulez pas laisser il est vrai pour toujours, alors appelez Application.DoEvents afin que le formulaire puisse traiter ce message et le remettre à false.

3

Est-ce que ShowDialog() a un comportement différent de la fenêtre que juste Show()?

si vous avez essayé:

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

TopMost = true; .Activer()?

L'un ou l'autre est-il bon?

Le découper dans son propre thread est un peu mal car il ne fonctionnera pas correctement si vous ne l'appelez pas avec Application.Run et que le thread sera avalé. Dans le pire des cas, je suppose que vous pourriez le séparer dans un processus différent et communiquer via le disque ou WCF.

16

Voici un code que j'ai utilisé sous une forme ou une autre pendant quelques années. Il y a quelques pièges à faire apparaître une fenêtre dans une autre application. Une fois que vous avez la poignée de fenêtre faire:

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

Je ne vais pas poster toute l'unité depuis puisqu'il fait d'autres choses qui ne sont pas pertinentes mais ici sont les constantes et les importations pour le code ci-dessus.

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

C'est la seule réponse ici qui a fonctionné pour moi, merci Gevin pour le partage! –

+2

Btw, il semble comme une pratique dangereuse selon Raymond Chen cela peut provoquer le gel de l'interface utilisateur: Je vous ai averti: Les dangers de l'attachement des files d'attente d'entrée http://blogs.msdn.com/b/oldnewthing/archive/2008/ 08/01/8795860.aspx Avez-vous déjà rencontré ce problème Kevin? –

+0

Nous avons utilisé ce code pendant des années sur de nombreux sites clients dans un applicatoin qui fonctionne 24x7 sans problèmes signalés. Cela ne veut pas dire que Chen n'a pas raison, mais si c'est un problème, cela n'arrive pas très souvent. –

0

Vous ne voulez pas simplement que le dialogue soit un enfant du formulaire d'appel?

Pour ce faire, vous aurez besoin de la passe dans la fenêtre d'appel et d'utiliser la méthode ShowDialog (propriétaire IWin32Window).

2

La solution suivante devrait répondre à vos besoins:

  1. Assemblée peut être chargé dans la classe PowerShell et principale instancié
  2. Lorsque la méthode ShowMessage sur cette instance est appelée, une nouvelle fenêtre apparaît et activé
  3. Si vous appelez ShowMessage plusieurs fois, cette même fenêtre met à jour son texte de titre et est activée
  4. Pour arrêter d'utiliser la fenêtre, appelez la méthode Dispose

Étape 1: Créons un répertoire de travail temporaire (vous pouvez naturellement utiliser votre propre répertoire)

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

Étape 2: Maintenant, nous allons définir la classe que nous interagiront avec 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(); 
    } 
} 

en plus du formulaire qui sera affiché:

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

Étape 3: Compilons l'assemblée ...

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

Étape 4: ... et charger l'ensemble dans PowerShell pour avoir du plaisir avec elle:

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

Un vert Une fenêtre avec le titre 'Hello, world' devrait maintenant s'ouvrir et être active.Si vous réactivez la fenêtre PowerShell et entrez:

$a.ShowMessage('Stack overflow') 

le titre de la fenêtre devrait changer de « débordement de pile » et la fenêtre doit être à nouveau actif.

Pour arrêter de travailler avec notre fenêtre, jetez l'objet:

$a.Dispose() 

Cette solution fonctionne comme prévu dans Windows XP SP3, x86 et Windows Vista SP1, x64. S'il y a des questions sur la façon dont cette solution fonctionne, je peux mettre à jour cette entrée avec une discussion détaillée. Pour l'instant, j'espère que le code est explicite.

1

Merci beaucoup. Je pense que je l'ai fait un peu plus court, voici ce que je mets sur un fil séparé et semble fonctionner correctement.

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 pris la peine repasting les définitions ...