Single Instance Application con WPF

Scritto da  Massimo Bonanni il mercoledì 29 dicembre 2010
Linguaggio: VB   •  Framework: 3.5,4.0   •  Livello: 100

Download sorgenti


In questo articolo cercheremo di mostrare due possibili metodi per far si che la nostra applicazione WPF sia di tipo "single instance application" ovvero che possa esistere, al massimo, una sola istanza della stessa attiva.
Coloro che sviluppano in Windows Forms hanno la possibilità di definire, nelle proprietà dell'applicazione, se l'utente può eseguire più istanze della stessa come mostrato dalla seguente figura:

figura1

Nelle applicazioni WPF non esiste questa proprietà e l'equivalente pagina mostrata in figura1 è la seguente:

figura2

Per poter controllare che sia in esecuzione, al massimo, una sola istanza della nostra applicazione WPF dobbiamo implementare, quindi, del codice.

La prima soluzione proposta si basa sull'utilizzo di un Mutex di sistema, cioè un oggetto di sincronizzazione globale, a livello di sistema, che può essere controllato da processi diversi.

Quando un'applicazione WPF viene eseguita, si verifica la presenza di un Mutex di sistema con un ben determinato nome (ad esempio quello dell'applicazione che si sta eseguendo) secondo il seguente algoritmo:

  • se il mutex non esiste ne istanziamo uno e procederemo all'esecuzione dell'applicazione;
  • se il mutex esiste (cioè un'altra applicazione è già in esecuzione) allora chiudiamo l'istanza dell'applicazione.

Cominciamo con il definire una function che si occupa di controllare l'esistenza del mutex:

Protected Function MutexExists(ByVal mutexName As String) As Boolean
Dim _mutexExists As Boolean = False
Try
Dim _mutex = Mutex.OpenExisting(mutexName)
If _mutex IsNot Nothing Then
_mutexExists = True
End If
Catch ex As WaitHandleCannotBeOpenedException
_mutexExists = False
Catch ex As UnauthorizedAccessException
_mutexExists = True
End Try
Return _mutexExists
End Function

 

Utilizziamo il metodo statico OpenExisting() della classe Mutex per cercare di aprire l'oggetto di sincronizzazione. Nel caso in cui il mutex esista, il metodo non provocherà eccezioni mentre nel caso in cui il mutex non esiste (cioè non è possibile aprirlo) verrà sollevata l'eccezione WaitHandleCannotOpenedException.

Definita questa function possiamo utilizzarla all'interno dell'evento Statup della classe Application:

Private Sub Application_Startup(ByVal sender As Object,
ByVal e As System.Windows.StartupEventArgs) Handles Me.Startup
Dim mutexName = Me.Info.AssemblyName
If Not MutexExists(mutexName) Then
_instanceMutex = New Mutex(True, mutexName)
Else
MessageBox.Show("Esiste già un'applicazione attiva")
Me.Shutdown(1)
End If
End Sub

 

L'attributo _instanceMutex è di tipo Mutex (namespace System.Threading):

Private _instanceMutex As Mutex = Nothing

 

Se il mutex esiste, chiudiamo anticipatamente l'applicazione dopo aver visualizzato un messaggio (il messaggio potrà essere sostituito da un qualsiasi pezzo di codice a nostro piacimento) mentre se l'istanza dell'applicazione è la prima istanziamo l'attributo privato con un nuovo oggetto Mutex.

Per pulizia gestiamo anche l'evento di chiusura dell'applicazione rilasciando il mutex (altrimenti non potranno più essere eseguite istanze dell'applicazione stessa):

Private Sub Application_Exit(ByVal sender As Object,
ByVal e As System.Windows.ExitEventArgs) Handles Me.Exit
If _instanceMutex IsNot Nothing Then
_instanceMutex.ReleaseMutex()
End If
End Sub

 

Un'altra soluzione al problema si basa sull'utilizzo della classe WindowsFormsApplicationBase  contenuta nel namespace Microsoft.VisualBasic.ApplicationServices, la quale espone dei metodi per la gestione di un'applicazione Windows Form.
Il concetto di fondo è quello di sostituire, per la gestione del ciclo di vita dell'applicazione WPF, la classe Application con la classe WindowsFormsApplicationBase, o per meglio dire con una sua derivata.
Per implementare tutto ciò, definiamo la classe:

Public Class SingleInstanceApplication
Inherits Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase

Friend Sub New()
Me.IsSingleInstance = True
End Sub

Private _app As WPFApplication.Application
Private _counter As Integer

Protected Overrides Function OnStartup(ByVal eventArgs As Microsoft.VisualBasic.ApplicationServices.StartupEventArgs) As Boolean
_app = New WPFApplication.Application()
_app.StartupUri = New Uri("MainWindow.xaml", UriKind.Relative)
_app.Run()
Return False
End Function

Protected Overrides Sub OnStartupNextInstance(ByVal eventArgs As Microsoft.VisualBasic.ApplicationServices.StartupNextInstanceEventArgs)
_counter += 1
UpdateWindow()
Me.OnShutdown()
End Sub

Protected Sub UpdateWindow()
Dim window = TryCast(_app.MainWindow, MainWindow)
If window IsNot Nothing Then
window.txtInstanceNumber.Text = _counter.ToString()
End If
End Sub

End Class

 

Nella precedente classe, WPFApplication.Application è la nostra classe applicazione WPF (il code behind del file Application.xaml) e MainWindow.xaml è la finestra principale dell'applicazione.
Se riuscissimo a far partire la classe SingleInstanceApplication all'avvio dell'applicazione WPF, allora saremmo in grado di gestire lo Startup e lo StartupNextInstance e gestire, in questo modo, l'avvio di più istanze della nostra applicazione.
Per risolvere questo ultimo problema costruiamo una classe di Startup:

Public Class Startup
Public Shared Sub Main(ByVal args As String())
Dim singleInstance = New SingleInstanceApplication()
singleInstance.Run(args)
End Sub
End Class

 

e modifichiamo le proprietà dell'applicazione WPF per far eseguire la classe Startup all'avvio.

figura3

Per ottenere la possibilità di impostare lo startup object è necessario:

  1. Aprire la pagina delle proprietà dell'applicazione WPF cliccando con il tasto destro del mouse sul progetto e scegliendo il menù Proprietà;
  2. Disattivare il checkbox per l'attivazione dell'application framework;
  3. Selezionare la classe Startup come oggetto di avvio.

In questo modo, nel momento in cui l'applicazione viene avviata la prima volta, viene istanziata la classe SingleInstanceApplication, eseguito il metodo Run() e, di conseguenza, richiamato il metodo OnStartup, mentre  tutte le volte che viene eseguita un'ulteriore istanza dell'applicazione (senza aver chiuso la prima), viene richiamato il metodo OnStartupNextInstance.

Questi due metodi, in particolare il secondo, ci consentono di decidere cosa fare dalla seconda istanza in poi. Inoltre, la classe SingleInstanceApplication è, di fatto, una classe singleton (cioè unica nella nostra applicazione) e ci permette di  gestire gli scenari in cui il lancio di una successiva istanza dell'applicazione deve far aprire una finestra figlia dell'applicazione principale (ad esempio un nuovo documento di word).

Tramite l'attributo _app della classe SingleInstanceApplication possiamo, infatti, interagire con l'applicazione in esecuzione.

Nell'esempio che trovate allegato, è stata realizzata un'applicazione che conta il numero di istanze che l'utente tenta di avviare successivamente alla prima.


Tags: WPF,windows presentation,presentation framework

 
x