c# - ventana - ¿Cuál es la forma correcta de crear una aplicación WPF de instancia única?
xamarin codigo (30)
Al usar C # y WPF en .NET (en lugar de Windows Forms o consola), ¿cuál es la forma correcta de crear una aplicación que solo se puede ejecutar como una sola instancia?
Sé que tiene algo que ver con una cosa mítica llamada mutex, rara vez puedo encontrar a alguien que se moleste en detenerse y explicar qué es uno de estos.
El código también debe informar a la instancia que ya se está ejecutando que el usuario intentó iniciar una segunda, y quizás también pase cualquier argumento de la línea de comandos si existiera alguno.
Aquí hay un ejemplo que le permite tener una sola instancia de una aplicación. Cuando se cargan nuevas instancias, pasan sus argumentos a la instancia principal que se está ejecutando.
public partial class App : Application
{
private static Mutex SingleMutex;
public static uint MessageId;
private void Application_Startup(object sender, StartupEventArgs e)
{
IntPtr Result;
IntPtr SendOk;
Win32.COPYDATASTRUCT CopyData;
string[] Args;
IntPtr CopyDataMem;
bool AllowMultipleInstances = false;
Args = Environment.GetCommandLineArgs();
// TODO: Replace {00000000-0000-0000-0000-000000000000} with your application''s GUID
MessageId = Win32.RegisterWindowMessage("{00000000-0000-0000-0000-000000000000}");
SingleMutex = new Mutex(false, "AppName");
if ((AllowMultipleInstances) || (!AllowMultipleInstances && SingleMutex.WaitOne(1, true)))
{
new Main();
}
else if (Args.Length > 1)
{
foreach (Process Proc in Process.GetProcesses())
{
SendOk = Win32.SendMessageTimeout(Proc.MainWindowHandle, MessageId, IntPtr.Zero, IntPtr.Zero,
Win32.SendMessageTimeoutFlags.SMTO_BLOCK | Win32.SendMessageTimeoutFlags.SMTO_ABORTIFHUNG,
2000, out Result);
if (SendOk == IntPtr.Zero)
continue;
if ((uint)Result != MessageId)
continue;
CopyDataMem = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(Win32.COPYDATASTRUCT)));
CopyData.dwData = IntPtr.Zero;
CopyData.cbData = Args[1].Length*2;
CopyData.lpData = Marshal.StringToHGlobalUni(Args[1]);
Marshal.StructureToPtr(CopyData, CopyDataMem, false);
Win32.SendMessageTimeout(Proc.MainWindowHandle, Win32.WM_COPYDATA, IntPtr.Zero, CopyDataMem,
Win32.SendMessageTimeoutFlags.SMTO_BLOCK | Win32.SendMessageTimeoutFlags.SMTO_ABORTIFHUNG,
5000, out Result);
Marshal.FreeHGlobal(CopyData.lpData);
Marshal.FreeHGlobal(CopyDataMem);
}
Shutdown(0);
}
}
}
public partial class Main : Window
{
private void Window_Loaded(object sender, RoutedEventArgs e)
{
HwndSource Source;
Source = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
Source.AddHook(new HwndSourceHook(Window_Proc));
}
private IntPtr Window_Proc(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam, ref bool Handled)
{
Win32.COPYDATASTRUCT CopyData;
string Path;
if (Msg == Win32.WM_COPYDATA)
{
CopyData = (Win32.COPYDATASTRUCT)Marshal.PtrToStructure(lParam, typeof(Win32.COPYDATASTRUCT));
Path = Marshal.PtrToStringUni(CopyData.lpData, CopyData.cbData / 2);
if (WindowState == WindowState.Minimized)
{
// Restore window from tray
}
// Do whatever we want with information
Activate();
Focus();
}
if (Msg == App.MessageId)
{
Handled = true;
return new IntPtr(App.MessageId);
}
return IntPtr.Zero;
}
}
public class Win32
{
public const uint WM_COPYDATA = 0x004A;
public struct COPYDATASTRUCT
{
public IntPtr dwData;
public int cbData;
public IntPtr lpData;
}
[Flags]
public enum SendMessageTimeoutFlags : uint
{
SMTO_NORMAL = 0x0000,
SMTO_BLOCK = 0x0001,
SMTO_ABORTIFHUNG = 0x0002,
SMTO_NOTIMEOUTIFNOTHUNG = 0x0008
}
[DllImport("user32.dll", SetLastError=true, CharSet=CharSet.Auto)]
public static extern uint RegisterWindowMessage(string lpString);
[DllImport("user32.dll")]
public static extern IntPtr SendMessageTimeout(
IntPtr hWnd, uint Msg, IntPtr wParam, IntPtr lParam,
SendMessageTimeoutFlags fuFlags, uint uTimeout, out IntPtr lpdwResult);
}
Aquí hay un muy buen article sobre la solución Mutex. El enfoque descrito por el artículo es ventajoso por dos razones.
Primero, no requiere una dependencia en el ensamblado Microsoft.VisualBasic. Si mi proyecto ya dependiera de esa asamblea, probablemente recomendaría el uso del enfoque que se muestra en la respuesta aceptada. Pero como es, no uso el ensamblado Microsoft.VisualBasic, y prefiero no agregar una dependencia innecesaria a mi proyecto.
En segundo lugar, el artículo muestra cómo llevar la instancia existente de la aplicación al primer plano cuando el usuario intenta iniciar otra instancia. Ese es un toque muy agradable que las otras soluciones de Mutex descritas aquí no abordan.
ACTUALIZAR
A partir del 1 de agosto de 2014, el artículo que vinculé anteriormente todavía está activo, pero el blog no se ha actualizado en mucho tiempo. Eso me preocupa de que eventualmente pueda desaparecer, y con ello, la solución recomendada. Estoy reproduciendo el contenido del artículo aquí para la posteridad. Las palabras pertenecen únicamente al propietario del blog en Sanity Free Coding .
Hoy quería refactorizar un código que prohibía que mi aplicación ejecutara múltiples instancias de sí misma.
Anteriormente había usado System.Diagnostics.Process para buscar una instancia de mi myapp.exe en la lista de procesos. Si bien esto funciona, conlleva muchos gastos generales y quería algo más limpio.
Sabiendo que podría usar un mutex para esto (pero nunca haberlo hecho antes) me propuse cortar mi código y simplificar mi vida.
En la clase de mi aplicación principal, creé una estática llamada Mutex :
static class Program
{
static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
[STAThread]
...
}
Tener un mutex con nombre nos permite apilar la sincronización a través de múltiples procesos y subprocesos, que es la magia que estoy buscando.
Mutex.WaitOne tiene una sobrecarga que especifica una cantidad de tiempo para que esperemos. Dado que en realidad no queremos sincronizar nuestro código (más simplemente verifique si está actualmente en uso) usamos la sobrecarga con dos parámetros: Mutex.WaitOne (Timepan timeout, bool exitContext) . Espera uno devuelve verdadero si es capaz de ingresar, y falso si no lo fue. En este caso, no queremos esperar nada; Si se está utilizando nuestro mutex, omítalo y continúe, así pasamos en TimeSpan.Zero (espere 0 milisegundos), y configuramos exitContext en true para que podamos salir del contexto de sincronización antes de intentar bloquearlo. Usando esto, envolvemos nuestro código Application.Run dentro de algo como esto:
static class Program
{
static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
[STAThread]
static void Main() {
if(mutex.WaitOne(TimeSpan.Zero, true)) {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
mutex.ReleaseMutex();
} else {
MessageBox.Show("only one instance at a time");
}
}
}
Por lo tanto, si nuestra aplicación se está ejecutando, WaitOne devolverá el valor falso y obtendremos un cuadro de mensaje.
En lugar de mostrar un cuadro de mensaje, opté por utilizar un poco de Win32 para notificar a mi instancia en ejecución que alguien olvidó que ya se estaba ejecutando (colocándose en la parte superior de todas las demás ventanas). Para lograr esto, utilicé PostMessage para transmitir un mensaje personalizado a cada ventana (el mensaje personalizado fue registrado con RegisterWindowMessage por mi aplicación en ejecución, lo que significa que solo mi aplicación sabe qué es) y luego mi segunda instancia se cierra. La instancia de la aplicación en ejecución recibiría esa notificación y la procesaría. Para hacer eso, WndProc en mi formulario principal y escuché mi notificación personalizada. Cuando recibí esa notificación, establezco la propiedad TopMost del formulario en true para que aparezca en la parte superior.
Esto es lo que terminé con:
- Programa.cs
static class Program
{
static Mutex mutex = new Mutex(true, "{8F6F0AC4-B9A1-45fd-A8CF-72F04E6BDE8F}");
[STAThread]
static void Main() {
if(mutex.WaitOne(TimeSpan.Zero, true)) {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
mutex.ReleaseMutex();
} else {
// send our Win32 message to make the currently running instance
// jump on top of all the other windows
NativeMethods.PostMessage(
(IntPtr)NativeMethods.HWND_BROADCAST,
NativeMethods.WM_SHOWME,
IntPtr.Zero,
IntPtr.Zero);
}
}
}
- NativeMethods.cs
// this class just wraps some Win32 stuff that we''re going to use
internal class NativeMethods
{
public const int HWND_BROADCAST = 0xffff;
public static readonly int WM_SHOWME = RegisterWindowMessage("WM_SHOWME");
[DllImport("user32")]
public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
[DllImport("user32")]
public static extern int RegisterWindowMessage(string message);
}
- Form1.cs (lado frontal parcial)
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected override void WndProc(ref Message m)
{
if(m.Msg == NativeMethods.WM_SHOWME) {
ShowMe();
}
base.WndProc(ref m);
}
private void ShowMe()
{
if(WindowState == FormWindowState.Minimized) {
WindowState = FormWindowState.Normal;
}
// get our current "TopMost" value (ours will always be false though)
bool top = TopMost;
// make our form jump to the top of everything
TopMost = true;
// set it back to whatever it was
TopMost = top;
}
}
Aquí hay una solución:
Protected Overrides Sub OnStartup(e As StartupEventArgs)
Const appName As String = "TestApp"
Dim createdNew As Boolean
_mutex = New Mutex(True, appName, createdNew)
If Not createdNew Then
''app is already running! Exiting the application
MessageBox.Show("Application is already running.")
Application.Current.Shutdown()
End If
MyBase.OnStartup(e)
End Sub
Bueno, tengo una clase desechable para esto que funciona fácilmente para la mayoría de los casos de uso:
Úsalo así:
static void Main()
{
using (SingleInstanceMutex sim = new SingleInstanceMutex())
{
if (sim.IsOtherInstanceRunning)
{
Application.Exit();
}
// Initialize program here.
}
}
Aquí está:
/// <summary>
/// Represents a <see cref="SingleInstanceMutex"/> class.
/// </summary>
public partial class SingleInstanceMutex : IDisposable
{
#region Fields
/// <summary>
/// Indicator whether another instance of this application is running or not.
/// </summary>
private bool isNoOtherInstanceRunning;
/// <summary>
/// The <see cref="Mutex"/> used to ask for other instances of this application.
/// </summary>
private Mutex singleInstanceMutex = null;
/// <summary>
/// An indicator whether this object is beeing actively disposed or not.
/// </summary>
private bool disposed;
#endregion
#region Constructor
/// <summary>
/// Initializes a new instance of the <see cref="SingleInstanceMutex"/> class.
/// </summary>
public SingleInstanceMutex()
{
this.singleInstanceMutex = new Mutex(true, Assembly.GetCallingAssembly().FullName, out this.isNoOtherInstanceRunning);
}
#endregion
#region Properties
/// <summary>
/// Gets an indicator whether another instance of the application is running or not.
/// </summary>
public bool IsOtherInstanceRunning
{
get
{
return !this.isNoOtherInstanceRunning;
}
}
#endregion
#region Methods
/// <summary>
/// Closes the <see cref="SingleInstanceMutex"/>.
/// </summary>
public void Close()
{
this.ThrowIfDisposed();
this.singleInstanceMutex.Close();
}
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
if (!this.disposed)
{
/* Release unmanaged ressources */
if (disposing)
{
/* Release managed ressources */
this.Close();
}
this.disposed = true;
}
}
/// <summary>
/// Throws an exception if something is tried to be done with an already disposed object.
/// </summary>
/// <remarks>
/// All public methods of the class must first call this.
/// </remarks>
public void ThrowIfDisposed()
{
if (this.disposed)
{
throw new ObjectDisposedException(this.GetType().Name);
}
}
#endregion
}
Desde here
Un uso común para un Mutex de proceso cruzado es asegurar que solo se pueda ejecutar una instancia de un programa a la vez. Así es como se hace:
class OneAtATimePlease {
// Use a name unique to the application (eg include your company URL)
static Mutex mutex = new Mutex (false, "oreilly.com OneAtATimeDemo");
static void Main()
{
// Wait 5 seconds if contended – in case another instance
// of the program is in the process of shutting down.
if (!mutex.WaitOne(TimeSpan.FromSeconds (5), false))
{
Console.WriteLine("Another instance of the app is running. Bye!");
return;
}
try
{
Console.WriteLine("Running - press Enter to exit");
Console.ReadLine();
}
finally
{
mutex.ReleaseMutex();
}
}
}
Una buena característica de Mutex es que si la aplicación finaliza sin que se llame primero a ReleaseMutex, el CLR lanzará el Mutex automáticamente.
El código article que es la referencia para la respuesta marcada es un gran comienzo.
Sin embargo, descubrí que no maneja muy bien los casos en que la instancia que ya existe tiene un diálogo modal abierto, ya sea que ese diálogo sea administrado (como otro formulario como un cuadro de información) o uno no administrado (como el OpenFileDialog incluso cuando se utiliza la clase estándar .NET). Con el código original, el formulario principal está activado, pero el modal permanece inactivo, lo que parece extraño, y el usuario debe hacer clic en él para seguir usando la aplicación.
Entonces, he creado una clase de utilidad SingleInstance para manejar todo esto de forma bastante automática para las aplicaciones Winforms y WPF.
Winforms :
1) modificar la clase del programa de esta manera:
static class Program
{
public static readonly SingleInstance Singleton = new SingleInstance(typeof(Program).FullName);
[STAThread]
static void Main(string[] args)
{
// NOTE: if this always return false, close & restart Visual Studio
// this is probably due to the vshost.exe thing
Singleton.RunFirstInstance(() =>
{
SingleInstanceMain(args);
});
}
public static void SingleInstanceMain(string[] args)
{
// standard code that was in Main now goes here
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
2) modificar la clase de la ventana principal de esta manera:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
protected override void WndProc(ref Message m)
{
// if needed, the singleton will restore this window
Program.Singleton.OnWndProc(this, m, true);
// TODO: handle specific messages here if needed
base.WndProc(ref m);
}
}
WPF:
1) modifique la página de la aplicación de esta manera (y asegúrese de configurar su acción de compilación en la página para poder redefinir el método principal):
public partial class App : Application
{
public static readonly SingleInstance Singleton = new SingleInstance(typeof(App).FullName);
[STAThread]
public static void Main(string[] args)
{
// NOTE: if this always return false, close & restart Visual Studio
// this is probably due to the vshost.exe thing
Singleton.RunFirstInstance(() =>
{
SingleInstanceMain(args);
});
}
public static void SingleInstanceMain(string[] args)
{
// standard code that was in Main now goes here
App app = new App();
app.InitializeComponent();
app.Run();
}
}
2) modificar la clase de la ventana principal de esta manera:
public partial class MainWindow : Window
{
private HwndSource _source;
public MainWindow()
{
InitializeComponent();
}
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
_source = (HwndSource)PresentationSource.FromVisual(this);
_source.AddHook(HwndSourceHook);
}
protected virtual IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
// if needed, the singleton will restore this window
App.Singleton.OnWndProc(hwnd, msg, wParam, lParam, true, true);
// TODO: handle other specific message
return IntPtr.Zero;
}
Y aquí está la clase de utilidad:
using System;
using System.ComponentModel;
using System.Runtime.InteropServices;
using System.Threading;
namespace SingleInstanceUtilities
{
public sealed class SingleInstance
{
private const int HWND_BROADCAST = 0xFFFF;
[DllImport("user32.dll")]
private static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
[DllImport("user32.dll", CharSet = CharSet.Unicode)]
private static extern int RegisterWindowMessage(string message);
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
public SingleInstance(string uniqueName)
{
if (uniqueName == null)
throw new ArgumentNullException("uniqueName");
Mutex = new Mutex(true, uniqueName);
Message = RegisterWindowMessage("WM_" + uniqueName);
}
public Mutex Mutex { get; private set; }
public int Message { get; private set; }
public void RunFirstInstance(Action action)
{
RunFirstInstance(action, IntPtr.Zero, IntPtr.Zero);
}
// NOTE: if this always return false, close & restart Visual Studio
// this is probably due to the vshost.exe thing
public void RunFirstInstance(Action action, IntPtr wParam, IntPtr lParam)
{
if (action == null)
throw new ArgumentNullException("action");
if (WaitForMutext(wParam, lParam))
{
try
{
action();
}
finally
{
ReleaseMutex();
}
}
}
public static void ActivateWindow(IntPtr hwnd)
{
if (hwnd == IntPtr.Zero)
return;
FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(hwnd));
}
public void OnWndProc(IntPtr hwnd, int m, IntPtr wParam, IntPtr lParam, bool restorePlacement, bool activate)
{
if (m == Message)
{
if (restorePlacement)
{
WindowPlacement placement = WindowPlacement.GetPlacement(hwnd, false);
if (placement.IsValid && placement.IsMinimized)
{
const int SW_SHOWNORMAL = 1;
placement.ShowCmd = SW_SHOWNORMAL;
placement.SetPlacement(hwnd);
}
}
if (activate)
{
SetForegroundWindow(hwnd);
FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(hwnd));
}
}
}
#if WINFORMS // define this for Winforms apps
public void OnWndProc(System.Windows.Forms.Form form, int m, IntPtr wParam, IntPtr lParam, bool activate)
{
if (form == null)
throw new ArgumentNullException("form");
if (m == Message)
{
if (activate)
{
if (form.WindowState == System.Windows.Forms.FormWindowState.Minimized)
{
form.WindowState = System.Windows.Forms.FormWindowState.Normal;
}
form.Activate();
FormUtilities.ActivateWindow(FormUtilities.GetModalWindow(form.Handle));
}
}
}
public void OnWndProc(System.Windows.Forms.Form form, System.Windows.Forms.Message m, bool activate)
{
if (form == null)
throw new ArgumentNullException("form");
OnWndProc(form, m.Msg, m.WParam, m.LParam, activate);
}
#endif
public void ReleaseMutex()
{
Mutex.ReleaseMutex();
}
public bool WaitForMutext(bool force, IntPtr wParam, IntPtr lParam)
{
bool b = PrivateWaitForMutext(force);
if (!b)
{
PostMessage((IntPtr)HWND_BROADCAST, Message, wParam, lParam);
}
return b;
}
public bool WaitForMutext(IntPtr wParam, IntPtr lParam)
{
return WaitForMutext(false, wParam, lParam);
}
private bool PrivateWaitForMutext(bool force)
{
if (force)
return true;
try
{
return Mutex.WaitOne(TimeSpan.Zero, true);
}
catch (AbandonedMutexException)
{
return true;
}
}
}
// NOTE: don''t add any field or public get/set property, as this must exactly map to Windows'' WINDOWPLACEMENT structure
[StructLayout(LayoutKind.Sequential)]
public struct WindowPlacement
{
public int Length { get; set; }
public int Flags { get; set; }
public int ShowCmd { get; set; }
public int MinPositionX { get; set; }
public int MinPositionY { get; set; }
public int MaxPositionX { get; set; }
public int MaxPositionY { get; set; }
public int NormalPositionLeft { get; set; }
public int NormalPositionTop { get; set; }
public int NormalPositionRight { get; set; }
public int NormalPositionBottom { get; set; }
[DllImport("user32.dll", SetLastError = true)]
private static extern bool SetWindowPlacement(IntPtr hWnd, ref WindowPlacement lpwndpl);
[DllImport("user32.dll", SetLastError = true)]
private static extern bool GetWindowPlacement(IntPtr hWnd, ref WindowPlacement lpwndpl);
private const int SW_SHOWMINIMIZED = 2;
public bool IsMinimized
{
get
{
return ShowCmd == SW_SHOWMINIMIZED;
}
}
public bool IsValid
{
get
{
return Length == Marshal.SizeOf(typeof(WindowPlacement));
}
}
public void SetPlacement(IntPtr windowHandle)
{
SetWindowPlacement(windowHandle, ref this);
}
public static WindowPlacement GetPlacement(IntPtr windowHandle, bool throwOnError)
{
WindowPlacement placement = new WindowPlacement();
if (windowHandle == IntPtr.Zero)
return placement;
placement.Length = Marshal.SizeOf(typeof(WindowPlacement));
if (!GetWindowPlacement(windowHandle, ref placement))
{
if (throwOnError)
throw new Win32Exception(Marshal.GetLastWin32Error());
return new WindowPlacement();
}
return placement;
}
}
public static class FormUtilities
{
[DllImport("user32.dll")]
private static extern IntPtr GetWindow(IntPtr hWnd, int uCmd);
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr SetActiveWindow(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool IsWindowVisible(IntPtr hWnd);
[DllImport("kernel32.dll")]
public static extern int GetCurrentThreadId();
private delegate bool EnumChildrenCallback(IntPtr hwnd, IntPtr lParam);
[DllImport("user32.dll")]
private static extern bool EnumThreadWindows(int dwThreadId, EnumChildrenCallback lpEnumFunc, IntPtr lParam);
private class ModalWindowUtil
{
private const int GW_OWNER = 4;
private int _maxOwnershipLevel;
private IntPtr _maxOwnershipHandle;
private bool EnumChildren(IntPtr hwnd, IntPtr lParam)
{
int level = 1;
if (IsWindowVisible(hwnd) && IsOwned(lParam, hwnd, ref level))
{
if (level > _maxOwnershipLevel)
{
_maxOwnershipHandle = hwnd;
_maxOwnershipLevel = level;
}
}
return true;
}
private static bool IsOwned(IntPtr owner, IntPtr hwnd, ref int level)
{
IntPtr o = GetWindow(hwnd, GW_OWNER);
if (o == IntPtr.Zero)
return false;
if (o == owner)
return true;
level++;
return IsOwned(owner, o, ref level);
}
public static void ActivateWindow(IntPtr hwnd)
{
if (hwnd != IntPtr.Zero)
{
SetActiveWindow(hwnd);
}
}
public static IntPtr GetModalWindow(IntPtr owner)
{
ModalWindowUtil util = new ModalWindowUtil();
EnumThreadWindows(GetCurrentThreadId(), util.EnumChildren, owner);
return util._maxOwnershipHandle; // may be IntPtr.Zero
}
}
public static void ActivateWindow(IntPtr hwnd)
{
ModalWindowUtil.ActivateWindow(hwnd);
}
public static IntPtr GetModalWindow(IntPtr owner)
{
return ModalWindowUtil.GetModalWindow(owner);
}
}
}
Este código debe ir al método principal. Mire here para obtener más información sobre el método principal en WPF.
[DllImport("user32.dll")]
private static extern Boolean ShowWindow(IntPtr hWnd, Int32 nCmdShow);
private const int SW_SHOWMAXIMIZED = 3;
static void Main()
{
Process currentProcess = Process.GetCurrentProcess();
var runningProcess = (from process in Process.GetProcesses()
where
process.Id != currentProcess.Id &&
process.ProcessName.Equals(
currentProcess.ProcessName,
StringComparison.Ordinal)
select process).FirstOrDefault();
if (runningProcess != null)
{
ShowWindow(runningProcess.MainWindowHandle, SW_SHOWMAXIMIZED);
return;
}
}
Método 2
static void Main()
{
string procName = Process.GetCurrentProcess().ProcessName;
// get the list of all processes by that name
Process[] processes=Process.GetProcessesByName(procName);
if (processes.Length > 1)
{
MessageBox.Show(procName + " already running");
return;
}
else
{
// Application.Run(...);
}
}
Nota: los métodos anteriores suponen que su proceso / aplicación tiene un nombre único. Debido a que utiliza el nombre del proceso para encontrar si hay procesadores existentes. Entonces, si su aplicación tiene un nombre muy común (es decir, Bloc de notas), el enfoque anterior no funcionará.
MSDN en realidad tiene una aplicación de ejemplo para C # y VB para hacer exactamente esto: http://msdn.microsoft.com/en-us/library/ms771662(v=VS.90).aspx
La técnica más común y confiable para desarrollar la detección de una sola instancia es usar la infraestructura remota de Microsoft .NET Framework (System.Remoting). Microsoft .NET Framework (versión 2.0) incluye un tipo, WindowsFormsApplicationBase, que encapsula la funcionalidad de comunicación remota requerida. Para incorporar este tipo en una aplicación WPF, un tipo debe derivarse de él, y debe utilizarse como un calzo entre el método de punto de entrada estático de la aplicación, Principal y el tipo de Aplicación de la aplicación WPF. El shim detecta cuándo se inicia una aplicación por primera vez, y cuando se intentan los lanzamientos posteriores, y los rendimientos controlan el tipo de aplicación WPF para determinar cómo procesar los lanzamientos.
- Para C #, la gente simplemente respira hondo y se olvida de todo "No quiero incluir VisualBasic DLL". Por this y por lo que dice Scott Hanselman y por el hecho de que esta es prácticamente la solución más limpia al problema y está diseñada por personas que saben mucho más sobre el marco que usted.
- Desde el punto de vista de la usabilidad, el hecho es si su usuario está cargando una aplicación y ya está abierta y le está dando un mensaje de error como
''Another instance of the app is running. Bye''
''Another instance of the app is running. Bye''
entonces no van a ser un usuario muy feliz. Simplemente DEBE (en una aplicación GUI) cambiar a esa aplicación y pasar los argumentos proporcionados, o si los parámetros de la línea de comando no tienen ningún significado, debe mostrar la aplicación que puede haber sido minimizada.
El marco de trabajo ya tiene soporte para esto, es solo que un idiota llamado DLL Microsoft.VisualBasic
y no se colocó en Microsoft.ApplicationUtils
o algo así. Supéralo, o abre Reflector.
Sugerencia: si utiliza este enfoque exactamente como es, y ya tiene un App.xaml con recursos, etc., querrá echarle un vistazo a esto también .
Podría usar la clase Mutex, pero pronto descubrirá que necesitará implementar el código para pasar los argumentos y otros. Bueno, aprendí un truco al programar en WinForms cuando leí el libro de Chris Sell . Este truco utiliza la lógica que ya está disponible para nosotros en el marco. No sé sobre ti, pero cuando me entero de cosas que puedo reutilizar en el marco, esa es la ruta que tomo en lugar de reinventar la rueda. A menos que por supuesto no haga todo lo que quiero.
Cuando entré en WPF, se me ocurrió una forma de usar ese mismo código, pero en una aplicación WPF. Esta solución debe satisfacer sus necesidades basadas en su pregunta.
Primero, necesitamos crear nuestra clase de aplicación. En esta clase vamos a anular el evento OnStartup y crear un método llamado Activate, que se usará más adelante.
public class SingleInstanceApplication : System.Windows.Application
{
protected override void OnStartup(System.Windows.StartupEventArgs e)
{
// Call the OnStartup event on our base class
base.OnStartup(e);
// Create our MainWindow and show it
MainWindow window = new MainWindow();
window.Show();
}
public void Activate()
{
// Reactivate the main window
MainWindow.Activate();
}
}
Segundo, necesitaremos crear una clase que pueda administrar nuestras instancias. Antes de pasar por eso, realmente vamos a reutilizar un código que se encuentra en el ensamblado Microsoft.VisualBasic. Ya que estoy usando C # en este ejemplo, tuve que hacer una referencia al ensamblaje. Si está utilizando VB.NET, no tiene que hacer nada. La clase que usaremos es WindowsFormsApplicationBase y heredará nuestro administrador de instancias y luego aprovechará las propiedades y los eventos para manejar la creación de instancias.
public class SingleInstanceManager : Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase
{
private SingleInstanceApplication _application;
private System.Collections.ObjectModel.ReadOnlyCollection<string> _commandLine;
public SingleInstanceManager()
{
IsSingleInstance = true;
}
protected override bool OnStartup(Microsoft.VisualBasic.ApplicationServices.StartupEventArgs eventArgs)
{
// First time _application is launched
_commandLine = eventArgs.CommandLine;
_application = new SingleInstanceApplication();
_application.Run();
return false;
}
protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs)
{
// Subsequent launches
base.OnStartupNextInstance(eventArgs);
_commandLine = eventArgs.CommandLine;
_application.Activate();
}
}
Básicamente, estamos usando los bits VB para detectar las instancias únicas y procesarlas en consecuencia. OnStartup se activará cuando se cargue la primera instancia. OnStartupNextInstance se activa cuando la aplicación se vuelve a ejecutar. Como puede ver, puedo llegar a lo que se pasó en la línea de comandos a través de los argumentos del evento. Establecí el valor en un campo de instancia. Puede analizar la línea de comandos aquí o puede pasarla a su aplicación a través del constructor y la llamada al método Activate.
Tercero, es hora de crear nuestro EntryPoint. En lugar de actualizar la aplicación como lo haría normalmente, vamos a aprovechar nuestro SingleInstanceManager.
public class EntryPoint
{
[STAThread]
public static void Main(string[] args)
{
SingleInstanceManager manager = new SingleInstanceManager();
manager.Run(args);
}
}
Bueno, espero que puedas seguir todo y puedas usar esta implementación y hacerla tuya.
Una nueva que usa material Mutex e IPC, y también pasa cualquier argumento de línea de comandos a la instancia en ejecución, es la blogs.microsoft.co.il/blogs/arik/archive/2010/05/28/… .
Actualización 2017-01-25. Después de probar algunas cosas, decidí usar VisualBasic.dll, es más fácil y funciona mejor (al menos para mí). Dejo mi respuesta anterior solo como referencia ...
Justo como referencia, así es como lo hice sin pasar argumentos (lo cual no puedo encontrar ninguna razón para hacerlo ... me refiero a una aplicación única con argumentos que pasan de una instancia a otra). Si se requiere la asociación de archivos, entonces se debe instanciar una aplicación (según la expectativa estándar de los usuarios) para cada documento. Si tiene que pasar args a la aplicación existente, creo que usaría vb dll.
No pasar args (solo aplicación de instancia única), prefiero no registrar un nuevo mensaje de Windows y no anular el bucle de mensajes como se define en la Solución de Matt Davis. Aunque no es un gran problema agregar un VisualBasic dll, pero prefiero no agregar una nueva referencia solo para hacer una aplicación de instancia única. Además, prefiero instanciar una nueva clase con Main en lugar de llamar a Shutdown from App.Startup override para asegurar que salga lo antes posible.
Con la esperanza de que a alguien le guste ... o le inspire un poco :-)
La clase de inicio del proyecto debe establecerse como ''SingleInstanceApp''.
public class SingleInstanceApp
{
[STAThread]
public static void Main(string[] args)
{
Mutex _mutexSingleInstance = new Mutex(true, "MonitorMeSingleInstance");
if (_mutexSingleInstance.WaitOne(TimeSpan.Zero, true))
{
try
{
var app = new App();
app.InitializeComponent();
app.Run();
}
finally
{
_mutexSingleInstance.ReleaseMutex();
_mutexSingleInstance.Close();
}
}
else
{
MessageBox.Show("One instance is already running.");
var processes = Process.GetProcessesByName(Assembly.GetEntryAssembly().GetName().Name);
{
if (processes.Length > 1)
{
foreach (var process in processes)
{
if (process.Id != Process.GetCurrentProcess().Id)
{
WindowHelper.SetForegroundWindow(process.MainWindowHandle);
}
}
}
}
}
}
}
WindowHelper:
using System;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.Windows.Threading;
namespace HQ.Util.Unmanaged
{
public class WindowHelper
{
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool SetForegroundWindow(IntPtr hWnd);
Agregué un método sendMessage a la clase NativeMethods.
Aparentemente, el método de envío de mensajes posterior al mensaje, si la aplicación no se muestra en la barra de tareas, sin embargo, al usar el método sendmessage se resuelve.
class NativeMethods
{
public const int HWND_BROADCAST = 0xffff;
public static readonly int WM_SHOWME = RegisterWindowMessage("WM_SHOWME");
[DllImport("user32")]
public static extern bool PostMessage(IntPtr hwnd, int msg, IntPtr wparam, IntPtr lparam);
[DllImport("user32.dll", CharSet = CharSet.Auto)]
public static extern IntPtr SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
[DllImport("user32")]
public static extern int RegisterWindowMessage(string message);
}
Aquí están mis 2 centavos.
static class Program
{
[STAThread]
static void Main()
{
bool createdNew;
using (new Mutex(true, "MyApp", out createdNew))
{
if (createdNew) {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var mainClass = new SynGesturesLogic();
Application.ApplicationExit += mainClass.tray_exit;
Application.Run();
}
else
{
var current = Process.GetCurrentProcess();
foreach (var process in Process.GetProcessesByName(current.ProcessName).Where(process => process.Id != current.Id))
{
NativeMethods.SetForegroundWindow(process.MainWindowHandle);
break;
}
}
}
}
}
Esta es una solución liviana que utilizo que le permite a la aplicación traer una ventana ya existente a primer plano sin tener que recurrir a mensajes personalizados de Windows o buscar nombres de procesos a ciegas.
[DllImport("user32.dll")]
static extern bool SetForegroundWindow(IntPtr hWnd);
static readonly string guid = "<Application Guid>";
static void Main()
{
Mutex mutex = null;
if (!CreateMutex(out mutex))
return;
// Application startup code.
Environment.SetEnvironmentVariable(guid, null, EnvironmentVariableTarget.User);
}
static bool CreateMutex(out Mutex mutex)
{
bool createdNew = false;
mutex = new Mutex(false, guid, out createdNew);
if (createdNew)
{
Process process = Process.GetCurrentProcess();
string value = process.Id.ToString();
Environment.SetEnvironmentVariable(guid, value, EnvironmentVariableTarget.User);
}
else
{
string value = Environment.GetEnvironmentVariable(guid, EnvironmentVariableTarget.User);
Process process = null;
int processId = -1;
if (int.TryParse(value, out processId))
process = Process.GetProcessById(processId);
if (process == null || !SetForegroundWindow(process.MainWindowHandle))
MessageBox.Show("Unable to start application. An instance of this application is already running.");
}
return createdNew;
}
Editar: También puede almacenar e inicializar la exclusión mutua y crear Nueva estaticamente, pero tendrá que disponer / liberar explícitamente la exclusión mutua una vez que haya terminado con ella. Personalmente, prefiero mantener el mutex local, ya que se eliminará automáticamente incluso si la aplicación se cierra sin llegar al final de Main.
Esto es lo mismo implementado a través del evento.
public enum ApplicationSingleInstanceMode
{
CurrentUserSession,
AllSessionsOfCurrentUser,
Pc
}
public class ApplicationSingleInstancePerUser: IDisposable
{
private readonly EventWaitHandle _event;
/// <summary>
/// Shows if the current instance of ghost is the first
/// </summary>
public bool FirstInstance { get; private set; }
/// <summary>
/// Initializes
/// </summary>
/// <param name="applicationName">The application name</param>
/// <param name="mode">The single mode</param>
public ApplicationSingleInstancePerUser(string applicationName, ApplicationSingleInstanceMode mode = ApplicationSingleInstanceMode.CurrentUserSession)
{
string name;
if (mode == ApplicationSingleInstanceMode.CurrentUserSession)
name = $"Local//{applicationName}";
else if (mode == ApplicationSingleInstanceMode.AllSessionsOfCurrentUser)
name = $"Global//{applicationName}{Environment.UserDomainName}";
else
name = $"Global//{applicationName}";
try
{
bool created;
_event = new EventWaitHandle(false, EventResetMode.ManualReset, name, out created);
FirstInstance = created;
}
catch
{
}
}
public void Dispose()
{
_event.Dispose();
}
}
Parece que hay una muy buena manera de manejar esto:
blogs.microsoft.co.il/blogs/arik/archive/2010/05/28/…
Esto proporciona una clase que puede agregar que administra todo el problema de exclusión y mensajería para simplificar su implementación hasta el punto en que es simplemente trivial.
Sin usar Mutex, respuesta simple:
System.Diagnostics;
...
string thisprocessname = Process.GetCurrentProcess().ProcessName;
if (Process.GetProcesses().Count(p => p.ProcessName == thisprocessname) > 1)
return;
Ponlo dentro del Program.Main()
.
Ejemplo :
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Diagnostics;
namespace Sample
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
//simple add Diagnostics namespace, and these 3 lines below
string thisprocessname = Process.GetCurrentProcess().ProcessName;
if (Process.GetProcesses().Count(p => p.ProcessName == thisprocessname) > 1)
return;
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Sample());
}
}
}
Puede agregar MessageBox.Show
a la if
declaración y poner "Aplicación que ya se está ejecutando".
Esto podría ser útil para alguien.
Utilice la solución de mutex:
using System;
using System.Windows.Forms;
using System.Threading;
namespace OneAndOnlyOne
{
static class Program
{
static String _mutexID = " // generate guid"
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Boolean _isNotRunning;
using (Mutex _mutex = new Mutex(true, _mutexID, out _isNotRunning))
{
if (_isNotRunning)
{
Application.Run(new Form1());
}
else
{
MessageBox.Show("An instance is already running.");
return;
}
}
}
}
}
Así es como terminé ocupándome de este problema. Tenga en cuenta que el código de depuración todavía está allí para probar. Este código está dentro de OnStartup en el archivo App.xaml.cs. (WPF)
// Process already running ?
if (Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName).Length > 1)
{
// Show your error message
MessageBox.Show("xxx is already running. /r/n/r/nIf the original process is hung up you may need to restart your computer, or kill the current xxx process using the task manager.", "xxx is already running!", MessageBoxButton.OK, MessageBoxImage.Exclamation);
// This process
Process currentProcess = Process.GetCurrentProcess();
// Get all processes running on the local computer.
Process[] localAll = Process.GetProcessesByName(Process.GetCurrentProcess().ProcessName);
// ID of this process...
int temp = currentProcess.Id;
MessageBox.Show("This Process ID: " + temp.ToString());
for (int i = 0; i < localAll.Length; i++)
{
// Find the other process
if (localAll[i].Id != currentProcess.Id)
{
MessageBox.Show("Original Process ID (Switching to): " + localAll[i].Id.ToString());
// Switch to it...
SetForegroundWindow(localAll[i].MainWindowHandle);
}
}
Application.Current.Shutdown();
}
Esto puede tener problemas que no he detectado todavía. Si me encuentro con alguno actualizaré mi respuesta.
El siguiente código es mi solución de tuberías con nombre WCF para registrar una aplicación de instancia única. Está bien porque también provoca un evento cuando otra instancia intenta iniciarse y recibe la línea de comandos de la otra instancia.
Está orientado hacia WPF porque usa la System.Windows.StartupEventHandler
clase, pero esto podría modificarse fácilmente.
Este código requiere una referencia a PresentationFramework
, y System.ServiceModel
.
Uso:
class Program
{
static void Main()
{
var applicationId = new Guid("b54f7b0d-87f9-4df9-9686-4d8fd76066dc");
if (SingleInstanceManager.VerifySingleInstance(applicationId))
{
SingleInstanceManager.OtherInstanceStarted += OnOtherInstanceStarted;
// Start the application
}
}
static void OnOtherInstanceStarted(object sender, StartupEventArgs e)
{
// Do something in response to another instance starting up.
}
}
Código fuente:
/// <summary>
/// A class to use for single-instance applications.
/// </summary>
public static class SingleInstanceManager
{
/// <summary>
/// Raised when another instance attempts to start up.
/// </summary>
public static event StartupEventHandler OtherInstanceStarted;
/// <summary>
/// Checks to see if this instance is the first instance running on this machine. If it is not, this method will
/// send the main instance this instance''s startup information.
/// </summary>
/// <param name="guid">The application''s unique identifier.</param>
/// <returns>True if this instance is the main instance.</returns>
public static bool VerifySingleInstace(Guid guid)
{
if (!AttemptPublishService(guid))
{
NotifyMainInstance(guid);
return false;
}
return true;
}
/// <summary>
/// Attempts to publish the service.
/// </summary>
/// <param name="guid">The application''s unique identifier.</param>
/// <returns>True if the service was published successfully.</returns>
private static bool AttemptPublishService(Guid guid)
{
try
{
ServiceHost serviceHost = new ServiceHost(typeof(SingleInstance));
NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
serviceHost.AddServiceEndpoint(typeof(ISingleInstance), binding, CreateAddress(guid));
serviceHost.Open();
return true;
}
catch
{
return false;
}
}
/// <summary>
/// Notifies the main instance that this instance is attempting to start up.
/// </summary>
/// <param name="guid">The application''s unique identifier.</param>
private static void NotifyMainInstance(Guid guid)
{
NetNamedPipeBinding binding = new NetNamedPipeBinding(NetNamedPipeSecurityMode.None);
EndpointAddress remoteAddress = new EndpointAddress(CreateAddress(guid));
using (ChannelFactory<ISingleInstance> factory = new ChannelFactory<ISingleInstance>(binding, remoteAddress))
{
ISingleInstance singleInstance = factory.CreateChannel();
singleInstance.NotifyMainInstance(Environment.GetCommandLineArgs());
}
}
/// <summary>
/// Creates an address to publish/contact the service at based on a globally unique identifier.
/// </summary>
/// <param name="guid">The identifier for the application.</param>
/// <returns>The address to publish/contact the service.</returns>
private static string CreateAddress(Guid guid)
{
return string.Format(CultureInfo.CurrentCulture, "net.pipe://localhost/{0}", guid);
}
/// <summary>
/// The interface that describes the single instance service.
/// </summary>
[ServiceContract]
private interface ISingleInstance
{
/// <summary>
/// Notifies the main instance that another instance of the application attempted to start.
/// </summary>
/// <param name="args">The other instance''s command-line arguments.</param>
[OperationContract]
void NotifyMainInstance(string[] args);
}
/// <summary>
/// The implementation of the single instance service interface.
/// </summary>
private class SingleInstance : ISingleInstance
{
/// <summary>
/// Notifies the main instance that another instance of the application attempted to start.
/// </summary>
/// <param name="args">The other instance''s command-line arguments.</param>
public void NotifyMainInstance(string[] args)
{
if (OtherInstanceStarted != null)
{
Type type = typeof(StartupEventArgs);
ConstructorInfo constructor = type.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, Type.EmptyTypes, null);
StartupEventArgs e = (StartupEventArgs)constructor.Invoke(null);
FieldInfo argsField = type.GetField("_args", BindingFlags.Instance | BindingFlags.NonPublic);
Debug.Assert(argsField != null);
argsField.SetValue(e, args);
OtherInstanceStarted(null, e);
}
}
}
}
Encontré la solución más simple, similar a la de Dale Ragan, pero ligeramente modificada. Hace prácticamente todo lo que necesita y se basa en la clase estándar de Microsoft WindowsFormsApplicationBase.
En primer lugar, crea la clase SingleInstanceController, que puede usar en todas las demás aplicaciones de instancia única, que usan formularios de Windows:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using Microsoft.VisualBasic.ApplicationServices;
namespace SingleInstanceController_NET
{
public class SingleInstanceController
: WindowsFormsApplicationBase
{
public delegate Form CreateMainForm();
public delegate void StartNextInstanceDelegate(Form mainWindow);
CreateMainForm formCreation;
StartNextInstanceDelegate onStartNextInstance;
public SingleInstanceController(CreateMainForm formCreation, StartNextInstanceDelegate onStartNextInstance)
{
// Set whether the application is single instance
this.formCreation = formCreation;
this.onStartNextInstance = onStartNextInstance;
this.IsSingleInstance = true;
this.StartupNextInstance += new StartupNextInstanceEventHandler(this_StartupNextInstance);
}
void this_StartupNextInstance(object sender, StartupNextInstanceEventArgs e)
{
if (onStartNextInstance != null)
{
onStartNextInstance(this.MainForm); // This code will be executed when the user tries to start the running program again,
// for example, by clicking on the exe file.
} // This code can determine how to re-activate the existing main window of the running application.
}
protected override void OnCreateMainForm()
{
// Instantiate your main application form
this.MainForm = formCreation();
}
public void Run()
{
string[] commandLine = new string[0];
base.Run(commandLine);
}
}
}
Luego puedes usarlo en tu programa de la siguiente manera:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using SingleInstanceController_NET;
namespace SingleInstance
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
static Form CreateForm()
{
return new Form1(); // Form1 is used for the main window.
}
static void OnStartNextInstance(Form mainWindow) // When the user tries to restart the application again,
// the main window is activated again.
{
mainWindow.WindowState = FormWindowState.Maximized;
}
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
SingleInstanceController controller = new SingleInstanceController(CreateForm, OnStartNextInstance);
controller.Run();
}
}
}
Tanto el programa como la solución SingleInstanceController_NET deben hacer referencia a Microsoft.VisualBasic. Si solo desea reactivar la aplicación en ejecución como una ventana normal cuando el usuario intenta reiniciar el programa en ejecución, el segundo parámetro en SingleInstanceController puede ser nulo. En el ejemplo dado, la ventana está maximizada.
Esto es lo que yo uso. Combinó la enumeración de procesos para realizar la conmutación y la exclusión mutua para salvaguardar de los "clics activos":
public partial class App
{
[DllImport("user32")]
private static extern int OpenIcon(IntPtr hWnd);
[DllImport("user32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
var p = Process
.GetProcessesByName(Process.GetCurrentProcess().ProcessName);
foreach (var t in p.Where(t => t.MainWindowHandle != IntPtr.Zero))
{
OpenIcon(t.MainWindowHandle);
SetForegroundWindow(t.MainWindowHandle);
Current.Shutdown();
return;
}
// there is a chance the user tries to click on the icon repeatedly
// and the process cannot be discovered yet
bool createdNew;
var mutex = new Mutex(true, "MyAwesomeApp",
out createdNew); // must be a variable, though it is unused -
// we just need a bit of time until the process shows up
if (!createdNew)
{
Current.Shutdown();
return;
}
new Bootstrapper().Run();
}
}
Mira el siguiente código. Es una solución excelente y simple para evitar múltiples instancias de una aplicación WPF.
private void Application_Startup(object sender, StartupEventArgs e)
{
Process thisProc = Process.GetCurrentProcess();
if (Process.GetProcessesByName(thisProc.ProcessName).Length > 1)
{
MessageBox.Show("Application running");
Application.Current.Shutdown();
return;
}
var wLogin = new LoginWindow();
if (wLogin.ShowDialog() == true)
{
var wMain = new Main();
wMain.WindowState = WindowState.Maximized;
wMain.Show();
}
else
{
Application.Current.Shutdown();
}
}
No puedo encontrar una solución corta aquí, así que espero que a alguien le guste esto:
ACTUALIZADO 2018-09-20
(Por cierto. Ponga el código en su "Program.cs" )
using System.Diagnostics;
static void Main()
{
Process ThisProcess = Process.GetCurrentProcess();
Process[] AllProcesses = Process.GetProcessesByName(ThisProcess.ProcessName);
if (AllProcesses.Length > 1)
{
//Don''t put a MessageBox in here because the user could spam this MessageBox.
return;
}
// Código opcional. Si no quieres que alguien ejecute tu ".exe" con un nombre diferente:
string exeName = AppDomain.CurrentDomain.FriendlyName;
if (exeName != "the name of you''re executable.exe") // If u try that in debug mode, don''t forget that u don''t use ur normal .exe. Debug uses the .vshost.exe.
{// You can add here a MassageBox if you want. To point users that the name got changed and maybe what the name should be or something like that^^
MessageBox.Show("The executable name should be /"the name of you''re executable.exe/"",
"Wrong executable name", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
//Following Code is default code:
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainForm());
}
Normalmente, este es el código que uso para las aplicaciones de Windows Forms de instancia única :
[STAThread]
public static void Main()
{
String assemblyName = Assembly.GetExecutingAssembly().GetName().Name;
using (Mutex mutex = new Mutex(false, assemblyName))
{
if (!mutex.WaitOne(0, false))
{
Boolean shownProcess = false;
Process currentProcess = Process.GetCurrentProcess();
foreach (Process process in Process.GetProcessesByName(currentProcess.ProcessName))
{
if (!process.Id.Equals(currentProcess.Id) && process.MainModule.FileName.Equals(currentProcess.MainModule.FileName) && !process.MainWindowHandle.Equals(IntPtr.Zero))
{
IntPtr windowHandle = process.MainWindowHandle;
if (NativeMethods.IsIconic(windowHandle))
NativeMethods.ShowWindow(windowHandle, ShowWindowCommand.Restore);
NativeMethods.SetForegroundWindow(windowHandle);
shownProcess = true;
}
}
if (!shownProcess)
MessageBox.Show(String.Format(CultureInfo.CurrentCulture, "An instance of {0} is already running!", assemblyName), assemblyName, MessageBoxButtons.OK, MessageBoxIcon.Asterisk, MessageBoxDefaultButton.Button1, (MessageBoxOptions)0);
}
else
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form());
}
}
}
Donde los componentes nativos son:
[DllImport("User32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern Boolean IsIconic([In] IntPtr windowHandle);
[DllImport("User32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern Boolean SetForegroundWindow([In] IntPtr windowHandle);
[DllImport("User32.dll", CharSet = CharSet.Unicode, ExactSpelling = true, SetLastError = true)]
[return: MarshalAs(UnmanagedType.Bool)]
internal static extern Boolean ShowWindow([In] IntPtr windowHandle, [In] ShowWindowCommand command);
public enum ShowWindowCommand : int
{
Hide = 0x0,
ShowNormal = 0x1,
ShowMinimized = 0x2,
ShowMaximized = 0x3,
ShowNormalNotActive = 0x4,
Minimize = 0x6,
ShowMinimizedNotActive = 0x7,
ShowCurrentNotActive = 0x8,
Restore = 0x9,
ShowDefault = 0xA,
ForceMinimize = 0xB
}
Nunca debe usar un mutex con nombre para implementar una aplicación de instancia única (o al menos no para el código de producción). El código malicioso puede fácilmente DoS ( denegación de servicio ) su culo ...
Por lo general, cada vez que ejecutamos un .exe, cada vez que crea un proceso de Windows separado con su propio espacio de direcciones, recursos, etc. Pero no queremos este criterio ya que esto nos impediría crear un proceso único. Las aplicaciones de instancia única se pueden crear utilizando el Mutex en C # que se describe en este artículo
Además, si queremos llevar la aplicación a la cima, podemos hacerlo usando
[DllImport("user32")]
static extern IntPtr SetForegroundWindow(IntPtr hWnd);
Solo algunos pensamientos: hay casos en los que se requiere que solo una instancia de una aplicación no sea "aburrida", como algunos le harían creer. Las aplicaciones de la base de datos, etc. son un orden de magnitud más difícil si se permite que múltiples instancias de la aplicación para que un solo usuario acceda a una base de datos (ya sabes, todo eso actualiza todos los registros que están abiertos en varias instancias de la aplicación para los usuarios). máquina, etc.). En primer lugar, por lo que se refiere a la "colisión de nombres", no use un nombre legible por humanos, use un GUID en su lugar o, mejor aún, un GUID + el nombre legible por humanos. Como alguien señaló, un ataque de DOS apestaría, pero si la persona maliciosa se tomó la molestia de obtener el nombre de exclusión mutua e incorporarlo en su aplicación,De todos modos, es prácticamente un objetivo y tendrá que hacer MUCHO más para protegerse que solo un nombre de mutex. Además, si uno usa la variante de: nuevo Mutex (verdadero, "algún GUID más Nombre", fuera de AIsFirstInstance), ya tiene su indicador de si el Mutex es la primera instancia.
También puede utilizar el Tiempo de ejecución de CodeFluent, que es un conjunto gratuito de herramientas. Proporciona una clase SingleInstance para implementar una aplicación de instancia única.
Tantas respuestas a una pregunta tan aparentemente simple. Solo para agitar un poco las cosas aquí está mi solución a este problema.
Crear un Mutex puede ser molesto porque el JIT-er solo lo ve usándolo por una pequeña parte de su código y quiere marcarlo como listo para la recolección de basura. Básicamente quiere superarte, pensando que no vas a utilizar ese Mutex durante tanto tiempo. En realidad, desea conservar este Mutex mientras su aplicación se esté ejecutando. La mejor manera de decirle al recolector de basura que lo deje solo en Mutex es decirle que lo mantenga vivo a través de las diferentes generaciones de la colección de garajes. Ejemplo:
var m = new Mutex(...);
...
GC.KeepAlive(m);
Levanté la idea de esta página: http://www.ai.uga.edu/~mc/SingleInstance.html