Durante lo sviluppo di applicazioni WPF di una certa “pesantezza”, può essere utile ricorrere a Splash Screen aggiornabili in tempo reale che notifichino all’utente lo stato di avanzamento del caricamento dei vari moduli, ad esempio tramite elementi di testo piuttosto che barre di progresso. A volte, infatti, può non essere sufficiente una semplice immagine impostabile tramite la Build Action "SplashScreen" di Visual Studio.
In questo post vorrei quindi mostrare una possibile implementazione di uno Splash Screen aggiornabile in tempo reale usando il pattern MVVM.
Per semplificare, supponiamo di posizionare nel Main la pesantissima sequenza di caricamento della nostra applicazione:
public static class Program
{
[STAThread]
public static void Main(string[] args)
{
App app = new App();
app.InitializeComponent(); // Load resources...
MainWindow mainWindow = null;
using (SplashScreenViewModel splashScreenViewModel = SplashScreen.ShowSplashScreen())
{
splashScreenViewModel.StatusText = "Initializing...";
// Loading application modules...
Thread.Sleep(3000); // Simulate delay...
mainWindow = new MainWindow();
splashScreenViewModel.StatusText = "Initialized.";
Thread.Sleep(1000);
}
app.Run(mainWindow);
}
}
Al fine di evitare fastidiosi effetti di freezing della GUI mentre il thread principale sta caricando i vari pezzi della nostra applicazione, la window dello Splash Screen va fatta girare necessariamente su un thread separato. Al termine della sequenza di caricamento, il thread principale deve poter chiudere lo Splash Screen utilizzando il Dispatcher corretto.
Vediamo quindi il codice della view:
public partial class SplashScreen : Window
{
private static object splashScreenlockObj = new object();
public SplashScreen()
{
InitializeComponent();
DataContext = new SplashScreenViewModel();
}
public static SplashScreenViewModel ShowSplashScreen()
{
lock (splashScreenlockObj)
{
SplashScreenViewModel splashScreenViewModel = null;
ManualResetEvent resetEvent = new ManualResetEvent(false);
Thread splashScreenThread = new Thread(() =>
{
Dispatcher.CurrentDispatcher.BeginInvoke((Action)delegate()
{
SplashScreen splashScreenWindow = new SplashScreen();
splashScreenViewModel = (SplashScreenViewModel)splashScreenWindow.DataContext;
splashScreenViewModel.Dispatcher = Dispatcher.CurrentDispatcher;
resetEvent.Set();
splashScreenWindow.Show();
});
Dispatcher.Run();
});
splashScreenThread.SetApartmentState(ApartmentState.STA);
splashScreenThread.IsBackground = true;
splashScreenThread.Start();
resetEvent.WaitOne(); // Wait for viewmodel initialization...
return splashScreenViewModel;
}
}
}
Il metodo statico ShowSplashScreen() visualizza lo splash screen e comunica al ViewModel quale Dispatcher utilizzare per la chiusura (InvokeShutdown()).
public class SplashScreenViewModel : ViewModelBase, IDisposable
{
private string _statusText = null;
public string StatusText
{
get { return _statusText; }
set
{
if (_statusText == value) return;
_statusText = value;
OnPropertyChanged("StatusText");
}
}
public Dispatcher Dispatcher { get; set; }
public void Dispose()
{
if (Dispatcher != null)
{
Dispatcher.InvokeShutdown();
Dispatcher = null;
}
}
}