2009-11-22 24 views
9

Tengo una aplicación que en un inicio posterior detecta si hay un proceso con el mismo nombre en ejecución y, de ser así, activa la ventana de la aplicación en ejecución y luego sale.Enviar mensaje a un proceso de Windows (no su ventana principal)

El problema es que la ventana principal podría estar oculta (solo un ícono de área de notificación visible), dejándome así sin el identificador de ventana.

En el inicio, la propiedad MainWindowHandle de la instancia anterior es 0, por lo que no puedo enviar ShowWindow o PostMessage.

¿Hay alguna manera en que pueda enviar un mensaje que pueda ser interceptado por la aplicación en ejecución, permitiéndole mostrar su ventana principal?

La aplicación está escrita en C#, el código que estoy usando para lograr esto a continuación.

[STAThread] 
static void Main() 
{ 
    bool createdNew = true; 
    using (Mutex mutex = new Mutex(true, "MyMutexName", out createdNew)) 
    { 
     if (createdNew) 
     { 
      Application.EnableVisualStyles(); 
      Application.SetCompatibleTextRenderingDefault(false); 
      Application.Run(new MainForm()); 
     } 
     else 
     { 
      Process current = Process.GetCurrentProcess(); 
      foreach (Process process in Process.GetProcessesByName(current.ProcessName)) 
      { 
       if (process.Id != current.Id) 
       { 
        Interop.WINDOWINFO pwi = new Interop.WINDOWINFO(); 
        IntPtr handle = process.MainWindowHandle; 
        var isVisible = Interop.GetWindowInfo(handle, ref pwi); 
        if (!isVisible) 
        { 
         MessageBox.Show(Constants.APP_NAME + " is already running, check the notification area (near the clock).", 
             Constants.APP_NAME, MessageBoxButtons.OK, MessageBoxIcon.Information);//temporary message, until I find the solution 
         //Interop.ShowWindow(handle, Interop.WindowShowStyle.ShowNormal); 
         //Interop.PostMessage(handle, Interop.WM_CUSTOM_ACTIVATEAPP, IntPtr.Zero, IntPtr.Zero); 
        } 
        else 
         Interop.SetForegroundWindow(handle);//this works when the window is visible 
         break; 
        } 
       } 
      } 
     } 
    } 
} 

Respuesta

9

Así es como he hecho esto:

using System; 
using System.Runtime.InteropServices; 
using System.Threading; 
using System.Windows.Forms; 
public partial class MainForm : Form 
{ 
    #region Dll Imports 
    private const int HWND_BROADCAST = 0xFFFF; 

    private static readonly int WM_MY_MSG = RegisterWindowMessage("WM_MY_MSG"); 

    [DllImport("user32")] 
    private static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam); 

    [DllImport("user32")] 
    private static extern int RegisterWindowMessage(string message); 
    #endregion Dll Imports 
    static Mutex _single = new Mutex(true, "{4EABFF23-A35E-F0AB-3189-C81203BCAFF1}"); 
    [STAThread] 
    static void Main() 
    { 
     // See if an instance is already running... 
     if (_single.WaitOne(TimeSpan.Zero, true)) { 
      // No...start up normally. 
      Application.EnableVisualStyles(); 
      Application.SetCompatibleTextRenderingDefault(false); 
      try { 
       Application.Run(new MainForm()); 
      } catch (Exception ex) { 
       // handle exception accordingly 
      } finally { 
       _single.ReleaseMutex(); 
      } 
     } else { 
      // Yes...Bring existing instance to top and activate it. 
      PostMessage(
       (IntPtr) HWND_BROADCAST, 
       WM_MY_MSG, 
       new IntPtr(0xCDCD), 
       new IntPtr(0xEFEF)); 
     } 
    } 

    protected override void WndProc(ref Message m) 
    { 
     if (m.Msg == WM_MY_MSG) { 
      if ((m.WParam.ToInt32() == 0xCDCD) && (m.LParam.ToInt32() == 0xEFEF)) { 
       if (WindowState == FormWindowState.Minimized) { 
        WindowState = FormWindowState.Normal; 
       } 
       // Bring window to front. 
       bool temp = TopMost; 
       TopMost = true; 
       TopMost = temp; 
       // Set focus to the window. 
       Activate(); 
      } 
     } else { 
      base.WndProc(ref m); 
     } 
    } 
} 

espero haber transcrito esto correctamente. Tuve que dejar de lado muchas otras cosas, pero creo que obtuve lo que es necesario. Lo que tengo funciona para mí sin falta. Si tienes un problema, avísame y veré lo que me he perdido.

+0

Tendrá una referencia a '' System.Runtime.InteropServices – BillW

+1

@BillW: Gracias. La publicación se actualiza en consecuencia. –

+0

@Matt ¡Buen trabajo, Matt! fyi: compila, funciona bien en VS 2010 beta 2 compilado contra FrameWork 4.0. Lo que realmente me gusta de esto es que puedes poner una llamada a 'MessageBox.Show (' ... '); en el caso de que esté reactivando la instancia única para que el usuario final sepa qué está sucediendo. Mi única pregunta sería sobre el tema de poner la activación en un bloque try-catch para que pueda lanzar el Mutex: ¿eso tiene alguna implicación para el comportamiento de la aplicación si la instancia del formulario principal continúa creando otros formularios o lo que sea? – BillW

1

Canalizaciones con nombre se puede utilizar para esto. Podría ser el método más aceptable con .net. Puede definir un servicio en la aplicación principal que acepte un mensaje de la aplicación de llamada. Aquí hay una muestra del servicio, en vb. Llama a la aplicación principal y le pasa una cadena, en este caso, un nombre de archivo. También devuelve una cadena, pero cualquier parámetro puede usarse aquí.

Public Class PicLoadService : Implements IMainAppPicLoad 

Public Function LoadPic(ByVal fName As String) As String Implements IMainAppPicLoad.LoadPic 
' do some stuff here. 
LoadPic = "return string" 
End Function 

End Class 

La configuración en la aplicación de llamada es un poco más complicada. La llamada y la aplicación principal pueden ser la misma aplicación.

Imports System.Diagnostics 
Imports System.ServiceModel 
Imports System.IO 
Imports vb = Microsoft.VisualBasic 

Module MainAppLoader 

Sub Main() 

Dim epAddress As EndpointAddress 
Dim Client As picClient 
Dim s As String 
Dim loadFile As String 
Dim procs() As Process 
Dim processName As String = "MainApp" 

loadFile = "" ' filename to load 

procs = Process.GetProcessesByName(processName) 

If UBound(procs) >= 0 Then 
    epAddress = New EndpointAddress("net.pipe://localhost/MainAppPicLoad") 
    Client = New picClient(New NetNamedPipeBinding, epAddress) 
    s = Client.LoadPic(loadFile) 
End If 

End Sub 

<System.Diagnostics.DebuggerStepThroughAttribute(), _ 
System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")> _ 
Partial Public Class picClient 
    Inherits System.ServiceModel.ClientBase(Of IMainAppPicLoad) 
    Implements IMainAppPicLoad 

    Public Sub New(ByVal binding As System.ServiceModel.Channels.Binding, ByVal remoteAddress As System.ServiceModel.EndpointAddress) 
     MyBase.New(binding, remoteAddress) 
    End Sub 

    Public Function LoadPic(ByVal fName As String) As String Implements IMainAppPicLoad.LoadPic 
     Return MyBase.Channel.LoadPic(fName) 
    End Function 

End Class 

' from here down was auto generated by svcutil. 
' svcutil.exe /language:vb /out:generatedProxy.vb /config:app.config http://localhost:8000/MainAppPicLoad 
' Some has been simplified after auto code generation. 
<System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0"), _ 
System.ServiceModel.ServiceContractAttribute(ConfigurationName:="IMainAppPicLoad")> _ 
Public Interface IMainAppPicLoad 
    <System.ServiceModel.OperationContractAttribute(Action:="http://tempuri.org/IMainAppPicLoad/LoadPic", ReplyAction:="http://tempuri.org/IMainAppPicLoad/LoadPicResponse")> _ 
    Function LoadPic(ByVal fName As String) As String 
End Interface 

<System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")> _ 
Public Interface IMainAppPicLoadChannel 
    Inherits IMainAppPicLoad, System.ServiceModel.IClientChannel 
End Interface 

<System.Diagnostics.DebuggerStepThroughAttribute(), _ 
System.CodeDom.Compiler.GeneratedCodeAttribute("System.ServiceModel", "3.0.0.0")> _ 
Partial Public Class IMainAppPicLoadClient 
    Inherits System.ServiceModel.ClientBase(Of IMainAppPicLoad) 
    Implements IMainAppPicLoad 

    Public Sub New(ByVal binding As System.ServiceModel.Channels.Binding, ByVal remoteAddress As System.ServiceModel.EndpointAddress) 
    MyBase.New(binding, remoteAddress) 
    End Sub 

    Public Function LoadPic(ByVal fName As String) As String Implements IMainAppPicLoad.LoadPic 
    Return MyBase.Channel.LoadPic(fName) 
    End Function 
End Class 

End Module 

<ServiceContract()> Public Interface IMainAppPicLoad 
<OperationContract()> Function LoadPic(ByVal fName As String) As String 
End Interface 
+1

En aras de contexto, este ejemplo utiliza WCF para el intercambio de datos a través de la canalización con nombre –

+0

investigué otro medio de comunicación entre procesos y, de hecho, esta fue una de las alternativas, pero creo que el uso de simples Los mensajes de las viudas son más simples y ligeros. – chitza

+0

@chitza sí, con la difusión de PostMessage, no tan ligero, ¿verdad? – peenut

4

Para otras personas que quieren lograr esto, les dejo a mi aplicación, usando una solución de Matt Davis.

En Program.cs

static class Program 
{ 
    #region Dll Imports 
    public const int HWND_BROADCAST = 0xFFFF; 

    [DllImport("user32.dll")] 
    public static extern bool SetForegroundWindow(IntPtr hWnd); 

    [DllImport("user32")] 
    public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam); 

    [DllImport("user32")] 
    public static extern int RegisterWindowMessage(string message); 
    #endregion Dll Imports 

    public static readonly int WM_ACTIVATEAPP = RegisterWindowMessage("WM_ACTIVATEAPP"); 

    [STAThread] 
    static void Main() 
    { 
     bool createdNew = true; 
     //by creating a mutex, the next application instance will detect it 
     //and the code will flow through the "else" branch 
     using (Mutex mutex = new Mutex(true, "MyMutexName", out createdNew))//make sure it's an unique identifier (a GUID would be better) 
     { 
      if (createdNew) 
      { 
       Application.EnableVisualStyles(); 
       Application.SetCompatibleTextRenderingDefault(false); 
       Application.Run(new MainForm()); 
      } 
      else 
      { 
       //we tried to create a mutex, but there's already one (createdNew = false - another app created it before) 
       //so there's another instance of this application running 
       Process currentProcess = Process.GetCurrentProcess(); 

       //get the process that has the same name as the current one but a different ID 
       foreach (Process process in Process.GetProcessesByName(currentProcess.ProcessName)) 
       { 
        if (process.Id != currentProcess.Id) 
        { 
         IntPtr handle = process.MainWindowHandle; 

         //if the handle is non-zero then the main window is visible (but maybe somewhere in the background, that's the reason the user started a new instance) 
         //so just bring the window to front 
         if (handle != IntPtr.Zero) 
          SetForegroundWindow(handle); 
         else 
          //tough luck, can't activate the window, it's not visible and we can't get its handle 
          //so instead notify the process that it has to show it's window 
          PostMessage((IntPtr)HWND_BROADCAST, WM_ACTIVATEAPP, IntPtr.Zero, IntPtr.Zero);//this message will be sent to MainForm 

         break; 
        } 
       } 
      } 
     } 
    } 
} 

En MainForm.cs

protected override void WndProc(ref Message m) 
{ 
    base.WndProc(ref m); 
      //someone (another process) said that we should show the window (WM_ACTIVATEAPP) 
    if (m.Msg == Program.WM_ACTIVATEAPP) 
     this.Show(); 
} 
+0

@chitza, probó su solución en VS2010b2 en FrameWork 3.5, 4.0: No encuentro ningún caso que active el WndProc. Si abro una instancia de la aplicación, entonces minimizo, lanzando otra instancia de aplicación deja la instancia actual minimizada. Si la ventana no está minimizada, funciona como cabría esperar, lo que hace que la instancia única y exclusiva se adelante. Encontré que si movía la llamada al WndProc después de la llamada a 'SetForeGroundWindow (esencialmente eliminando el caso else), eso me permitió, en el código de intercepción de WndProc en MainForm, probar WindowState = Minimized y hacer el correcto cosa. – BillW

+1

No pensé en la forma de minimizar. El código solo maneja ventanas ocultas y ventanas de fondo no minimizadas. Sin embargo, el código debe ser reescrito y solo debe aparecer el mensaje en Program.cs, todos los códigos de activación/restauración de ventana deben estar en WndProc (MainForm.cs). Lo reescribiré durante el fin de semana y lo volveré a publicar. – chitza

Cuestiones relacionadas