Cuando creamos una nueva aplicación, nos puede interesar que solo se ejecute una instancia de nuestra aplicación, por ejemplo, si vamos a crear un reproductor de música, puede que deseemos que solo se pueda usar un único reproductor, pues no tiene mucho sentido escuchar dos canciones a la vez, perecería ruido lo que estuviera sonando!!
Para evitar esta situación haremos que solo exista una instancia en ejecución de nuestra aplicación. La forma mas sencilla de hacer esto es mediante un proyecto de Visual Basic, en el solo tendremos que acceder a las propiedades del proyecto principal y activar la casilla Convertir aplicación de instancia única, si generamos la solución e intentamos ejecutar dos veces el *.exe veremos como se vuelve activa la ventana que apareció por primera vez.
Ahora veremos como podemos hacer esto mismo pero escribiendo nuestro código. Podríamos pensar como una posible solución usar el patrón de diseño Singleton:
public partial class Form2 : Form
{
private Form2()
{
InitializeComponent();
}
private static Form2 form2 = null;
public static Form2 formInstance
{
get
{
if (form2 == null)
form2 = new Form2();
return form2;
}
}
}
y en main llamaríamos al form del siguiente modo:
Application.Run(Form2.formInstance);
pero esto no funcionaria para nuestro propósito porque dos procesos diferentes no podrían acceder al mismo form, esto sería util, tal vez, en una aplicación tipo MDI para evitar que se llegue a trabajar con varias instancias del form principal.
Una de las formas que podemos utilizar para que se cree una única instancia de nuestra aplicación es utilizar un mutex, ya que cada mutex es único en el sistema, podemos utilizar este objeto para determina si ya existe una instancia de nuestra aplicación:
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//...
bool onlyInstance;
Mutex mtx = new Mutex(true, "Mi_Aplicacion", out onlyInstance);
if (onlyInstance)
{
//este bloque de codigo se ejecuta
//cuando el proceso posee el objeto
//mutex
Application.Run(new Form1());
}
else
{
//codigo para cuando ya existe una
//instancia de nuestra aplicacion
//ejecutandose en el sistema
}
}
la llamada al mutex tienen el siguiente significado, el primer valor, true, indica que el proceso que crea el objeto mutex obtendrá (o intentará obtener) la exclusión mutua, "Mi_Aplicacion" es el nombre que se le asigna al mutex, y el último parámetro, onlyInstance es una variable de tipo booleano en la que se almacenara un valor de retorno, que será true cuando se obtenga la exclusión mutua. En la primera llamada a nuestra aplicación, la variable onlyInstance contendrá el valor true y en sucesivas llamadas será false, así es como determinamos el código a ejecutar. Para usar un objeto Mutex debemos usar el namespace System.Threading.
Por ultimo veremos otra implementación diferente en la que se "trae al frente" nuestra aplicación que hace uso de la API de Windows. Estas llamadas a la API Win32 de Windows, veremos como se declaran funciones exportadas desde una dll, nos permiten cierta funcionalidad a la hora de trabajar con una ventana:
static class Program1
{
[DllImport("User32.dll")]
private static extern bool ShowWindowAsync(IntPtr hWnd, int cmdShow);
[DllImport("User32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
private const int WS_SHOWNORMAL = 1;
[STAThread]
public static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Process instance = RunningInstance();
if (instance == null)
{
//Si aún no hay una instancia corriendo, muestro el formulario.
Application.Run(new Form1());
}
else
{
//Si hay otra instancia de este proceso.
HandleRunningInstance(instance);
}
}
public static Process RunningInstance()
{
Process current = Process.GetCurrentProcess();
Process[] processes = Process.GetProcessesByName(current.ProcessName);
//Recorro los procesos corriendo con el mismo nombre
foreach (Process process in processes)
{
//Ignoro el proceso actual
if (process.Id != current.Id)
{
//Me aseguro que el proceso está corriendo del archivo exe.
if (Assembly.GetExecutingAssembly().Location.Replace("/", "\\") ==
current.MainModule.FileName)
{
//Devuelvo la instancia corriendo.
return process;
}
}
}
//Si no encuentra ninguna instancia corriendo, devuelve null.
return null;
}
public static void HandleRunningInstance(Process instance)
{
//Me aseguro que la ventana no esté ni minimizada ni maximizada.
ShowWindowAsync(instance.MainWindowHandle, WS_SHOWNORMAL);
//Llevo la instancia al primer plano.
SetForegroundWindow(instance.MainWindowHandle);
}
}
Hagamos especial hincapié en el atributo DllImport, este atributo nos permite describir una función externa pasando el nombre de la dll donde se encuentra la función que queremos usar. Hay que tener cuidado de incluir los modificadores static extern en la función que estamos definiendo.
[DllImport("User32.dll")]
private static extern bool ShowWindowAsync(IntPtr hWnd, int cmdShow);
Esta función establece el estado de una ventana creado por un proceso diferente al nuestro y la utilizaremos para que cada vez que se intente llamar de nuevo a la aplicación esta se normalice sin importar si se encuentra minimizada o maximizada
[DllImport("User32.dll")]
private static extern bool SetForegroundWindow(IntPtr hWnd);
La llamada a esta función hace que se active el proceso que creó la ventana y la trae al primer plano
La funcionalidad del método RunningInstance es la de buscar una instancia en el sistema de la aplicación, la primera línea de código de la función devuelve el proceso activo, el cual utilizaremos en la siguiente línea para buscar todos los procesos que compartan el mismo nombre. Para luego, con el foreach recorrer el array de objetos process, e ignorando el proceso desde el que trabajamos, encontrar el proceso inicial y asegurarnos que es del mismo *.exe. El objeto devuelto será el proceso que se creo la primera vez que se ejecutó la aplicación o null si es la primera vez que se ejecuta la aplicación y con este objeto de tipo Process determinamos el código a ejecutar, si crear el formulario principal o buscar el que ya existe. Por ultimo, para la clase que implemente este código, deberemos hacer uso de los namespace System.Diagnostics, System.Runtime.InteropServices y System.Reflection.