SplashScreen asynchrone

11 février 2009 07:44 par Franck Desbrosses

Lors du lancement d’une application, le temps de chargement de certains éléments peut paraître interminable à l’utilisateur. Il est donc assez courant de placer un écran de démarrage permettant de faire patienter l’utilisateur pendant le chargement de l’application.

Le framework 3.5 SP1 apporte une nouvelle classe "SplashScreen" permettant de réaliser ce genre de chose. Néanmoins, elle nous contraint à utiliser une image statique (png, bmp...). Or, il pourrait être intéressant de rendre cet écran un peu plus “dynamique”. On pourrait par exemple informer l’utilisateur de ce qui est en train d’être chargé, ou bien encore afficher une animation. Le problème est que si le thread principal est très demandé, l’animation risque de ne pas être fluide, voire de ne pas se lancer du tout. Il faut donc avoir recours à de l’asynchronisme.

   1: public partial class SplashWindow : Window
   2: {
   3:     public SplashWindow()
   4:     {
   5:         InitializeComponent();
   6:     }
   7:  
   8:     // Instance statique de la fenêtre
   9:     public static SplashWindow _monSplashScreen;
  10:  
  11:     /// <summary>
  12:     /// Affiche le SplashScreen
  13:     /// </summary>
  14:     public static void ShowMe()
  15:     {
  16:         Thread thread = new Thread(() =>
  17:         {
  18:             // Instancie la fenêtre
  19:             _monSplashScreen = new SplashWindow();
  20:             // Place la fenêtre par dessus les autres
  21:             _monSplashScreen.Topmost = true;
  22:             // Affiche la fenêtre
  23:             _monSplashScreen.Show();
  24:  
  25:             // Démarre une nouvelle "pompe de messages" associée au thread
  26:             System.Windows.Threading.Dispatcher.Run();
  27:             
  28:             // Lorsqu'InvokeShutdown() sera appelée, 
  29:             // on rend le focus à la fenêtre principale de l'application
  30:             App.Current.Dispatcher.Invoke(
  31:                     new Action(() => App.Current.MainWindow.Activate()));
  32:         });
  33:  
  34:         // Place le thread appelant en STA (cloisonnement des threads)
  35:         thread.SetApartmentState(ApartmentState.STA);
  36:  
  37:         // Démarre le nouveau thread
  38:         thread.Start();
  39:     }
  40:  
  41:     public static void CloseMe()
  42:     {
  43:         // Teste l'existence d'une instance de la fenêtre
  44:         if (_monSplashScreen != null)
  45:         {
  46:             // Demande à la fenêtre de se fermer 
  47:             // depuis le Dispatcher qui lui est associé
  48:             _monSplashScreen.Dispatcher.Invoke(
  49:                     new Action(() => _monSplashScreen.Close()));
  50:  
  51:             // Demande l'arrêt du dispatcher associé à la fenêtre
  52:             _monSplashScreen.Dispatcher.InvokeShutdown();
  53:         }
  54:     }
  55: }

Le principe est finalement assez simple, on crée et on affiche une fenêtre dans un nouveau thread auquel on associe un nouveau Dispatcher, si bien que l’animation se trouvant dans le xaml de cette fenêtre aura son propre Dispatcher et pourra s’exécuter sans aucune dégradation.

Il suffit ensuite d’afficher et de fermer la fenêtre au moment voulu :

   1: class Program
   2: {
   3:     [STAThread]
   4:     static void Main()
   5:     {
   6:         // Affiche le SplashScreen
   7:         SplashWindow.ShowMe();
   8:  
   9:         App myApp = new App();
  10:         
  11:         myApp.MainWindow = new Window1();
  12:         myApp.MainWindow.Show();
  13:         myApp.Run();
  14:     }
  15: }
  16:  
  17: public partial class Window1 : Window
  18: {
  19:     public Window1()
  20:     {
  21:         InitializeComponent();
  22:  
  23:         // Chargement des données
  24:         LoadData();
  25:  
  26:         // Ferme le SplashScreen
  27:         SplashWindow.CloseMe();
  28:     }
  29: }