11
votes

Envoyer un message à un processus Windows (pas sa fenêtre principale)

J'ai une application que sur un démarrage ultérieur détecte s'il y a un processus avec le même nom en cours d'exécution et, dans l'affirmative, active la fenêtre de l'application exécutée, puis les sorties.

Le problème est que la fenêtre principale pourrait être cachée. (Seule une seule icône de zone de notification visible), me laissant donc sans poignée de fenêtre. P>

au démarrage, l'instance précédente ''s MAINWINDOWHANDLEL CODE> La propriété est 0, donc je ne peux pas envoyer Showwindow code> ou postmessage code>. P>

est-ce que je peux envoyer un message pouvant être intercepté par l'application exécutant, ce qui lui permet d'afficher sa fenêtre principale?

L'application est écrite en C #, le code que j'utilise pour y parvenir ci-dessous. P>

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


0 commentaires

3 Réponses :


10
votes

Voici comment j'ai fait ceci:

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


10 commentaires

Vous aurez besoin d'une référence sur 'System.Runtime.Interopservices'


@Matt beau travail, Matt! FYI: construit, fonctionne bien dans VS 2010 Beta 2 compilée contre le cadre 4.0. Ce que j'aime vraiment à propos de ceci, c'est que vous pouvez mettre un appel à 'MessageBox.Show ("..."); Dans le cas, vous réactivez l'instance unique et unique pour laisser l'utilisateur final savoir ce qui se passe. Ma seule question serait autour de la question de la mise en place de l'activation dans un bloc de try-catch afin que vous puissiez relâcher le mutex: cela a-t-il des implications pour le comportement de l'application si l'instance de formulaire principale se produit pour créer d'autres formes ou quoi que ce soit?


@Matt j'ai voté votre réponse, puis frappé accidentellement le haut-vote Hickey, et maintenant, alors ne me laisserez pas voter à nouveau, mettre un message que mon vote est "trop ​​vieux" pour être modifié à moins que la réponse Est mis à jour ! J'écrirai à ce sujet à ce sujet: Si mon poste de vote était "trop ​​vieux" en premier lieu, pourquoi l'a fait, apparemment, inverse le coup de contrôle?


@BILLW: la seule implication défavorable à laquelle je peux penser, c'est si un fil de premier plan est laissé en cours d'exécution après la situation principale. Dans ce cas, l'application disparaît de la vue mais reste vivante jusqu'à la fin du fil de premier plan. Cela pourrait potentiellement être mauvais Mojo si l'utilisateur a essayé de "redémarrer" l'application car le même fil de premier plan pourrait être créé une seconde fois. Dans mon cas, ce n'est pas un problème car je n'utilise pas que des threads d'arrière-plan. L'application meurt donc immédiatement lorsque le formal principal se décolore.


@Matt FYI: J'ai expérimenté le remplacement du bloc Essayer / Catch avec A "à l'aide de la déclaration: Utilisation (_single = nouveau mutex (true," {4EABF23-A35E-F0AB-3189-C81203BCAFF1} ") {// Code de démarrage de l'application. .. "Communiqués" le mutex ou ouvre des problèmes possibles avec le filetage, etc.


@BILLW: Il n'y a rien de tecourci techniquement avec le remplacement du bloc Essayer / Catch avec une déclaration en utilisant. L'utilisation de l'utilisation de l'utilisation du mutex est disposée correctement même si une exception est lancée . Mais c'est précisément pourquoi j'utilise le bloc Essayer / Catch. Si une exception non gérée survient, mon relevé de capture enregistre l'erreur de la visionneuse de l'événement, de sorte que j'ai au moins un aperçu de ce qui s'est passé. Vous pouvez également afficher les détails d'exception dans une boîte à une émission. L'utilisation de l'utilisation ne vous permet pas de le faire.


Merci Matt Davis, je viens de réaliser que j'ai oublié de le dire :) À ce moment-là, j'étais très impatient de mettre en œuvre / tester votre solution.


@chitza: votre accueil. Avoir un grand Thanksgiving. Se préparer à me diriger moi-même ...


diffusion? Ne sonne pas ça génial / efficace


@Mattdavis, mais monsieur comment puis-je passer les arguments de commande de commande à l'application Instances?



1
votes

Les tuyaux nommés peuvent être utilisés pour cela. C'est peut-être la méthode plus acceptable avec .NET.Vous pouvez définir un service dans l'application principale qui accepte un message à partir de l'application d'appel. Voici un échantillon du service, en VB. Il appelle l'application principale et transmet une chaîne à ce cas, dans ce cas, un nom de fichier. Il renvoie également une chaîne, mais tous les paramètres peuvent être utilisés ici.

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


3 commentaires

Pour des raisons de contexte, cet exemple utilise WCF pour échanger des données via le tuyau nommé


J'ai enquêté sur d'autres moyens de communication inter-processus et, en effet, c'était l'une des alternatives, mais je crois que l'utilisation de messages simples veuves est supérieure et plus simple.


@chitza ouais, avec la diffusion postmessage, pas vraiment si léger, n'est-ce pas?



4
votes

Pour d'autres personnes souhaitant y parvenir, je pose ci-dessous mon implémentation, à l'aide de la solution de Matt Davis.

dans Program.cs P>

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


2 commentaires

@chitza, testé votre solution à VS2010B2 sur le cadre 3.5, 4.0: Je ne trouve aucun cas qui déclenche le WNDProc. Si je lance une instance d'application, réduisez-le, le lancement, le lancement d'une autre instance d'application laisse l'instance actuelle minimisée. Si la fenêtre n'est pas minimisée, cela fonctionne comme vous l'attendez, apportant l'instance unique et unique à l'avant. J'ai constaté que si je déplaisais l'appel à la WNDProc à l'appel à l'appel à «Serfforegroundwindow (éliminer essentiellement l'affaire), qui m'a permis, dans le code d'interception WNDPROCI dans le formal de laforme principale, à tester pour le tourne-fenêté = minimisé et faites le droit chose.


Je n'ai pas pensé au formulaire minimisé. Le code ne traite que des fenêtres cachées et des fenêtres de fond non minimisées. Toutefois, le code doit être réécrit et ne poster que le message dans Program.CS, tout le code d'activation / restauration de la fenêtre doit être dans WNDPROC (MainForm.cs). Je vais le réécrire pendant le week-end et le republierai.