Voici le code que j'ai implémenté jusqu'à présent pour créer une application WPF à une seule instance:
#region Using Directives
using System;
using System.Globalization;
using System.Reflection;
using System.Threading;
using System.Windows;
using System.Windows.Interop;
#endregion
namespace MyWPF
{
public partial class MainApplication : Application, IDisposable
{
#region Members
private Int32 m_Message;
private Mutex m_Mutex;
#endregion
#region Methods: Functions
private IntPtr HandleMessages(IntPtr handle, Int32 message, IntPtr wParameter, IntPtr lParameter, ref Boolean handled)
{
if (message == m_Message)
{
if (MainWindow.WindowState == WindowState.Minimized)
MainWindow.WindowState = WindowState.Normal;
Boolean topmost = MainWindow.Topmost;
MainWindow.Topmost = true;
MainWindow.Topmost = topmost;
}
return IntPtr.Zero;
}
private void Dispose(Boolean disposing)
{
if (disposing && (m_Mutex != null))
{
m_Mutex.ReleaseMutex();
m_Mutex.Close();
m_Mutex = null;
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
#endregion
#region Methods: Overrides
protected override void OnStartup(StartupEventArgs e)
{
Assembly assembly = Assembly.GetExecutingAssembly();
Boolean mutexCreated;
String mutexName = String.Format(CultureInfo.InvariantCulture, "Local\\{{{0}}}{{{1}}}", Assembly.GetType().GUID, Assembly.GetName().Name);
m_Mutex = new Mutex(true, mutexName, out mutexCreated);
m_Message = NativeMethods.RegisterWindowMessage(mutexName);
if (!mutexCreated)
{
m_Mutex = null;
NativeMethods.PostMessage(NativeMethods.HWND_BROADCAST, m_Message, IntPtr.Zero, IntPtr.Zero);
Current.Shutdown();
return;
}
base.OnStartup(e);
MainWindow window = new MainWindow();
MainWindow = window;
window.Show();
HwndSource.FromHwnd((new WindowInteropHelper(window)).Handle).AddHook(new HwndSourceHook(HandleMessages));
}
protected override void OnExit(ExitEventArgs e)
{
Dispose();
base.OnExit(e);
}
#endregion
}
}
Tout fonctionne parfaitement ... mais j'ai quelques doutes à ce sujet et j'aimerais recevoir vos suggestions sur la façon dont mon approche pourrait être améliorée.
1) Code Analysis m'a demandé d'implémenter l'interface IDisposable
car j'utilisais les membres IDisposable
(les Mutex
). Mon implémentation de Dispose()
est-elle assez bonne? Dois-je l'éviter car il ne sera jamais appelé?
2) Il vaut mieux utiliser m_Mutex = new Mutex(true, mutexName, out mutexCreated);
et vérifier le résultat ou utiliser m_Mutex = new Mutex(false, mutexName);
puis vérifier m_Mutex.WaitOne(TimeSpan.Zero, false);
? En cas de multithreading, je veux dire ...
3) RegisterWindowMessage
L'appel d'API doit renvoyer UInt32
... mais HwndSourceHook
n'accepte que Int32
Comme valeur de message ... si je m'inquiète des comportements inattendus (comme un résultat supérieur à Int32.MaxValue
)?
4) Dans OnStartup
override ... dois-je exécuter base.OnStartup(e);
même si une autre instance est déjà en cours d'exécution et que je vais fermer l'application?
5) Existe-t-il un meilleur moyen d'amener l'instance existante au sommet qui n'a pas besoin de définir la valeur de Topmost
? Peut-être Activate()
?
6) Pouvez-vous voir un défaut dans mon approche? Quelque chose concernant le multithreading, la mauvaise gestion des exceptions et quelque chose comme ça? Par exemple ... que se passe-t-il si mon application plante entre OnStartup
et OnExit
?
1) Cela ressemble à une implémentation Dispose standard pour moi. Ce n'est pas vraiment nécessaire (voir point 6) mais cela ne fait pas de mal. (Nettoyage à la fermeture, c'est un peu comme nettoyer la maison avant de la brûler, à mon humble avis, mais les opinions sur la question diffèrent ..)
Quoi qu'il en soit, pourquoi ne pas utiliser "Dispose" comme nom de la méthode de nettoyage, même si elle n'est pas appelée directement? Vous auriez pu l'appeler "Cleanup", mais n'oubliez pas que vous écrivez également du code pour les humains, et Dispose semble familier et que quiconque sur .NET comprend à quoi cela sert. Alors, optez pour "Dispose".
2) J'ai toujours vu m_Mutex = new Mutex(false, mutexName);
Je pense que c'est plus une convention qu'un avantage technique.
3) À partir de MSDN:
Si le message est enregistré avec succès, la valeur de retour est un identifiant de message compris entre 0xC000 et 0xFFFF.
Je ne m'inquiéterais donc pas. Habituellement, pour cette classe de fonctions, UInt n'est pas utilisé pour "il ne rentre pas dans Int, utilisons UInt pour avoir quelque chose de plus" mais pour clarifier un contrat "la fonction ne retourne jamais de valeur négative".
4) J'éviterais de l'appeler si vous arrêtez, même raison que # 1
5) Il y a plusieurs façons de procéder. La manière la plus simple dans Win32 est simplement de demander à la deuxième instance d'appeler SetForegroundWindow (Regardez ici: http://blogs.msdn.com/b/oldnewthing/archive/2009/02/20/9435239.aspx ); cependant, je ne sais pas s'il existe une fonctionnalité WPF équivalente ou si vous devez l'inclure.
6)
Par exemple ... que se passe-t-il si mon application se bloque entre OnStartup et OnExit?
C'est OK: quand un processus se termine, tous les descripteurs appartenant au processus sont libérés; le mutex est également libéré.
Bref, mes recommandations:
Par exemple, vous pouvez utiliser votre technique (essayer d'envoyer/publier un message dans la fenêtre - si elle ne répond pas, elle est bloquée), ainsi que la technique MSK, pour rechercher et terminer l'ancien processus. Commencez ensuite normalement.
Il y a plusieurs choix,
Mutex
Mutex myMutex ;
private void Application_Startup(object sender, StartupEventArgs e)
{
bool aIsNewInstance = false;
myMutex = new Mutex(true, "MyWPFApplication", out aIsNewInstance);
if (!aIsNewInstance)
{
MessageBox.Show("Already an instance is running...");
App.Current.Shutdown();
}
}
Gestionnaire de processus
private void Application_Startup(object sender, StartupEventArgs e)
{
Process proc = Process.GetCurrentProcess();
int count = Process.GetProcesses().Where(p=>
p.ProcessName == proc.ProcessName).Count();
if (count > 1)
{
MessageBox.Show("Already an instance is running...");
App.Current.Shutdown();
}
}
Utilisez une prise d'écoute
Une façon de signaler une autre application consiste à lui ouvrir une connexion Tcp. Créez un socket, liez-le à un port et écoutez sur un thread d'arrière-plan les connexions. Si cela réussit, exécutez normalement. Sinon, établissez une connexion à ce port, ce qui signale à l'autre instance qu'une deuxième tentative de lancement d'application a été effectuée. L'instance d'origine peut ensuite mettre sa fenêtre principale au premier plan, le cas échéant.
Les logiciels/pare-feu de "sécurité" peuvent poser problème.
Je voulais avoir une expérience utilisateur un peu meilleure - si une autre instance est déjà en cours d'exécution, activons-la plutôt que d'afficher une erreur sur la deuxième instance. Voici ma mise en œuvre.
J'utilise nommé Mutex pour m'assurer qu'une seule instance est en cours d'exécution et nommé EventWaitHandle pour passer la notification d'une instance à une autre.
App.xaml.cs:
/// <summary>Interaction logic for App.xaml</summary>
public partial class App
{
#region Constants and Fields
/// <summary>The event mutex name.</summary>
private const string UniqueEventName = "{GUID}";
/// <summary>The unique mutex name.</summary>
private const string UniqueMutexName = "{GUID}";
/// <summary>The event wait handle.</summary>
private EventWaitHandle eventWaitHandle;
/// <summary>The mutex.</summary>
private Mutex mutex;
#endregion
#region Methods
/// <summary>The app on startup.</summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The e.</param>
private void AppOnStartup(object sender, StartupEventArgs e)
{
bool isOwned;
this.mutex = new Mutex(true, UniqueMutexName, out isOwned);
this.eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName);
// So, R# would not give a warning that this variable is not used.
GC.KeepAlive(this.mutex);
if (isOwned)
{
// Spawn a thread which will be waiting for our event
var thread = new Thread(
() =>
{
while (this.eventWaitHandle.WaitOne())
{
Current.Dispatcher.BeginInvoke(
(Action)(() => ((MainWindow)Current.MainWindow).BringToForeground()));
}
});
// It is important mark it as background otherwise it will prevent app from exiting.
thread.IsBackground = true;
thread.Start();
return;
}
// Notify other instance so it could bring itself to foreground.
this.eventWaitHandle.Set();
// Terminate this instance.
this.Shutdown();
}
#endregion
}
Et BringToForeground dans MainWindow.cs:
/// <summary>Brings main window to foreground.</summary>
public void BringToForeground()
{
if (this.WindowState == WindowState.Minimized || this.Visibility == Visibility.Hidden)
{
this.Show();
this.WindowState = WindowState.Normal;
}
// According to some sources these steps gurantee that an app will be brought to foreground.
this.Activate();
this.Topmost = true;
this.Topmost = false;
this.Focus();
}
Et ajoutez Startup = "AppOnStartup" (merci vhanla!):
<Application x:Class="MyClass.App"
xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
Startup="AppOnStartup">
<Application.Resources>
</Application.Resources>
</Application>
Travaille pour moi :)
Pour WPF, utilisez simplement:
public partial class App : Application
{
private static Mutex _mutex = null;
protected override void OnStartup(StartupEventArgs e)
{
const string appName = "MyAppName";
bool createdNew;
_mutex = new Mutex(true, appName, out createdNew);
if (!createdNew)
{
//app is already running! Exiting the application
Application.Current.Shutdown();
}
base.OnStartup(e);
}
}
La façon la plus simple de gérer cela serait d'utiliser un sémaphore nommé. Essayez quelque chose comme ça ...
public partial class App : Application
{
Semaphore sema;
bool shouldRelease = false;
protected override void OnStartup(StartupEventArgs e)
{
bool result = Semaphore.TryOpenExisting("SingleInstanceWPFApp", out sema);
if (result) // we have another instance running
{
App.Current.Shutdown();
}
else
{
try
{
sema = new Semaphore(1, 1, "SingleInstanceWPFApp");
}
catch
{
App.Current.Shutdown(); //
}
}
if (!sema.WaitOne(0))
{
App.Current.Shutdown();
}
else
{
shouldRelease = true;
}
base.OnStartup(e);
}
protected override void OnExit(ExitEventArgs e)
{
if (sema != null && shouldRelease)
{
sema.Release();
}
}
}
J'ai utilisé un simple TCP socket pour cela (en Java, il y a 10 ans).
pour empêcher une deuxième instance,
cela pourrait être fait comme ceci (ceci pour une application WPF (voir ref à App ()), mais fonctionne aussi sur WinForms):
public partial class App : Application
{
public App()
{
// initiate it. Call it first.
preventSecond();
}
private const string UniqueEventName = "{GENERATE-YOUR-OWN-GUID}";
private void preventSecond()
{
try
{
EventWaitHandle.OpenExisting(UniqueEventName); // check if it exists
this.Shutdown();
}
catch (WaitHandleCannotBeOpenedException)
{
new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName); // register
}
}
}
Deuxième version: ci-dessus plus signalisation de l'autre instance pour afficher la fenêtre (changez la partie MainWindow pour WinForms):
public partial class App : Application
{
public App()
{
// initiate it. Call it first.
//preventSecond();
SingleInstanceWatcher();
}
private const string UniqueEventName = "{GENERATE-YOUR-OWN-GUID}";
private EventWaitHandle eventWaitHandle;
/// <summary>prevent a second instance and signal it to bring its mainwindow to foregorund</summary>
/// <seealso cref="https://stackoverflow.com/a/23730146/1644202"/>
private void SingleInstanceWatcher()
{
// check if it is allready open.
try
{
// try to open it - if another instance is running, it will exist
this.eventWaitHandle = EventWaitHandle.OpenExisting(UniqueEventName);
// Notify other instance so it could bring itself to foreground.
this.eventWaitHandle.Set();
// Terminate this instance.
this.Shutdown();
}
catch (WaitHandleCannotBeOpenedException)
{
// listen to a new event
this.eventWaitHandle = new EventWaitHandle(false, EventResetMode.AutoReset, UniqueEventName);
}
// if this instance gets the signal to show the main window
new Task(() =>
{
while (this.eventWaitHandle.WaitOne())
{
Current.Dispatcher.BeginInvoke((Action)(() =>
{
// could be set or removed anytime
if (!Current.MainWindow.Equals(null))
{
var mw = Current.MainWindow;
if (mw.WindowState == WindowState.Minimized || mw.Visibility != Visibility.Visible)
{
mw.Show();
mw.WindowState = WindowState.Normal;
}
// According to some sources these steps gurantee that an app will be brought to foreground.
mw.Activate();
mw.Topmost = true;
mw.Topmost = false;
mw.Focus();
}
}));
}
})
.Start();
}
}
Ce code en tant que drop in class, sera @ Selfcontained-C-Sharp-WPF-compatible-utility-classes-utilitaires/tils.SingleInstance.cs
Il s'agit d'une solution simple, ouvrez votre fichier de démarrage (vue à partir de l'endroit où votre application démarre) dans ce cas son MainWindow.xaml. Ouvrez votre fichier MainWindow.xaml.cs. Allez dans le constructeur et après intializecomponent () ajoutez ce code:
Process Currentproc = Process.GetCurrentProcess();
Process[] procByName=Process.GetProcessesByName("notepad"); //Write the name of your exe file in inverted commas
if(procByName.Length>1)
{
MessageBox.Show("Application is already running");
App.Current.Shutdown();
}
N'oubliez pas d'ajouter System.Diagnostics
Voici un exemple qui amène l'ancienne instance au premier plan également:
public partial class App : Application
{
[DllImport("user32", CharSet = CharSet.Unicode)]
static extern IntPtr FindWindow(string cls, string win);
[DllImport("user32")]
static extern IntPtr SetForegroundWindow(IntPtr hWnd);
[DllImport("user32")]
static extern bool IsIconic(IntPtr hWnd);
[DllImport("user32")]
static extern bool OpenIcon(IntPtr hWnd);
private static Mutex _mutex = null;
protected override void OnStartup(StartupEventArgs e)
{
const string appName = "LinkManager";
bool createdNew;
_mutex = new Mutex(true, appName, out createdNew);
if (!createdNew)
{
ActivateOtherWindow();
//app is already running! Exiting the application
Application.Current.Shutdown();
}
base.OnStartup(e);
}
private static void ActivateOtherWindow()
{
var other = FindWindow(null, "!YOUR MAIN WINDOW TITLE HERE!");
if (other != IntPtr.Zero)
{
SetForegroundWindow(other);
if (IsIconic(other))
OpenIcon(other);
}
}
}
Mais cela ne fonctionnera que si le titre de votre fenêtre principale ne change pas pendant l'exécution.
Modifier:
Vous pouvez également utiliser l'événement Startup
dans App.xaml
Au lieu de remplacer OnStartup
.
// App.xaml.cs
private void Application_Startup(object sender, StartupEventArgs e)
{
const string appName = "LinkManager";
bool createdNew;
_mutex = new Mutex(true, appName, out createdNew);
if (!createdNew)
{
ActivateOtherWindow();
//app is already running! Exiting the application
Application.Current.Shutdown();
}
}
// App.xaml
<Application x:Class="MyApp.App"
xmlns="http://schemas.Microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.Microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:MyApp"
StartupUri="MainWindow.xaml" Startup="Application_Startup"> //<- startup event
N'oubliez pas de ne pas appeler base.OnStartup(e)
dans ce cas!