Écran de démarrage multi-thread en C #?

Je veux un écran de démarrage à afficher pendant le chargement de l’application. J’ai un formulaire avec un contrôle de la barre d’état système qui lui est lié. Je souhaite que l’écran de démarrage s’affiche pendant le chargement de ce formulaire, ce qui prend un peu de temps puisqu’il accède à une API de service Web pour remplir certaines listes déroulantes. Je souhaite également effectuer des tests de base sur les dépendances avant de les charger (en d’autres termes, le service Web est disponible, le fichier de configuration est lisible). Au fur et à mesure de chaque phase du démarrage, je souhaite mettre à jour l’écran de démarrage avec les progrès réalisés.

J’ai beaucoup lu sur le threading, mais je me perds là où cela doit être contrôlé (la méthode main ()?). Je manque également comment fonctionne Application.Run() , est-ce là que les threads pour cela devraient être créés à partir de? Maintenant, si la forme avec le contrôle de la barre d’état système est la forme “vivante”, le splash doit-il venir de là? Ne serait-ce pas charger jusqu’à ce que le formulaire est terminé de toute façon?

Je ne suis pas à la recherche d’un document de code, mais plutôt d’un algorithme / d’une approche pour que je puisse le comprendre une fois pour toutes 🙂

Eh bien, pour une application ClickOnce que j’ai déployée par le passé, nous avons utilisé l’espace de noms Microsoft.VisualBasic pour gérer les threads de l’écran de démarrage. Vous pouvez référencer et utiliser l’assembly Microsoft.VisualBasic de C # dans .NET 2.0 et fournir de nombreux services sympas.

  1. Avoir le formulaire principal hérité de Microsoft.VisualBasic.WindowsFormsApplicationBase
  2. Remplacez la méthode “OnCreateSplashScreen” comme suit:

     protected override void OnCreateSplashScreen() { this.SplashScreen = new SplashForm(); this.SplashScreen.TopMost = true; } 

Très simple, il affiche votre SplashForm (que vous devez créer) pendant le chargement, puis le ferme automatiquement une fois le chargement du formulaire principal terminé.

Cela simplifie vraiment les choses et VisualBasic.WindowsFormsApplicationBase est bien sûr bien testé par Microsoft et dispose de nombreuses fonctionnalités qui peuvent vous simplifier la vie, même dans une application 100% C #.

En fin de compte, tout est IL et bytecode, alors pourquoi ne pas l’utiliser?

L’astuce consiste à créer un thread séparé responsable de l’affichage de l’écran d’accueil.
Lorsque vous exécutez votre application, .net crée le thread principal et charge le formulaire spécifié (principal). Pour cacher le travail difficile, vous pouvez masquer le formulaire principal jusqu’à ce que le chargement soit terminé.

En supposant que Form1 – est votre formulaire principal et que SplashForm est au niveau supérieur, borderles nice splash form:

 private void Form1_Load(object sender, EventArgs e) { Hide(); bool done = false; ThreadPool.QueueUserWorkItem((x) => { using (var splashForm = new SplashForm()) { splashForm.Show(); while (!done) Application.DoEvents(); splashForm.Close(); } }); Thread.Sleep(3000); // Emulate hardwork done = true; Show(); } 

Après avoir parcouru Google et SO pour trouver des solutions, ceci est mon préféré: http://bytes.com/topic/c-sharp/answers/277446-winform-startup-splash-screen

FormSplash.cs:

 public partial class FormSplash : Form { private static Thread _splashThread; private static FormSplash _splashForm; public FormSplash() { InitializeComponent(); } ///  /// Show the Splash Screen (Loading...) ///  public static void ShowSplash() { if (_splashThread == null) { // show the form in a new thread _splashThread = new Thread(new ThreadStart(DoShowSplash)); _splashThread.IsBackground = true; _splashThread.Start(); } } // called by the thread private static void DoShowSplash() { if (_splashForm == null) _splashForm = new FormSplash(); // create a new message pump on this thread (started from ShowSplash) Application.Run(_splashForm); } ///  /// Close the splash (Loading...) screen ///  public static void CloseSplash() { // need to call on the thread that launched this splash if (_splashForm.InvokeRequired) _splashForm.Invoke(new MethodInvoker(CloseSplash)); else Application.ExitThread(); } } 

Program.cs:

 static class Program { ///  /// The main entry point for the application. ///  [STAThread] static void Main(ssortingng[] args) { // splash screen, which is terminated in FormMain FormSplash.ShowSplash(); Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); // this is probably where your heavy lifting is: Application.Run(new FormMain()); } } 

FormMain.cs

  ... public FormMain() { InitializeComponent(); // bunch of database access, form loading, etc // this is where you could do the heavy lifting of "loading" the app PullDataFromDatabase(); DoLoadingWork(); // ready to go, now close the splash FormSplash.CloseSplash(); } 

J’avais des problèmes avec la solution Microsoft.VisualBasic – Solution trouvée sur XP, mais sur Windows 2003 Terminal Server, le formulaire principal de l’application apparaîtrait (après l’écran de démarrage) en arrière-plan et la barre des tâches clignotait. Et mettre une fenêtre au premier plan / focus dans le code est une toute autre canette de vers que vous pouvez utiliser pour Google / SO.

Je recommande d’appeler Activate(); directement après le dernier Show(); dans la réponse fournie par Aku.

Citant MSDN:

L’activation d’un formulaire le place au premier plan s’il s’agit de l’application active ou si la légende de la fenêtre clignote s’il ne s’agit pas de l’application active. Le formulaire doit être visible pour que cette méthode ait un effet.

Si vous n’activez pas votre formulaire principal, il peut être affiché derrière d’autres fenêtres ouvertes, ce qui le rend un peu idiot.

C’est une vieille question, mais je ne cessais de la rencontrer lorsque je tentais de trouver une solution d’écran de démarrage avec thread pour WPF pouvant inclure de l’animation.

Voici ce que j’ai finalement reconstitué:

App.XAML:

  

App.XAML.cs:

 void ApplicationStart(object sender, StartupEventArgs e) { var thread = new Thread(() => { Dispatcher.CurrentDispatcher.BeginInvoke ((Action)(() => new MySplashForm().Show())); Dispatcher.Run(); }); thread.SetApartmentState(ApartmentState.STA); thread.IsBackground = true; thread.Start(); // call synchronous configuration process // and declare/get reference to "main form" thread.Abort(); mainForm.Show(); mainForm.Activate(); } 

Je pense que l’utilisation d’une méthode comme celle d’ Aku ou de Guy est la solution, mais quelques exemples à retenir des exemples spécifiques:

  1. Le principe de base serait de montrer votre éclaboussure sur un thread séparé dès que possible. C’est comme ça que je me pencherais, similaire à ce que aku a illustré, puisque c’est ce que je connais le mieux. Je n’étais pas au courant de la fonction VB mentionnée par Guy. Et, même si c’est une bibliothèque VB , il a raison – tout est IL en fin de compte. Donc, même si ça fait mal, ce n’est pas si mal! 🙂 Je pense que vous voudrez être sûr que soit VB fournit un thread séparé dans ce remplacement ou que vous en créez un vous-même – certainement rechercher cela.

  2. En supposant que vous créez un autre thread pour afficher ce splash, vous devrez faire attention aux mises à jour de l’interface utilisateur cross thread. Je soulève cette question parce que vous avez mentionné la mise à jour des progrès. Fondamentalement, pour être sûr, vous devez appeler une fonction de mise à jour (que vous créez) sur le formulaire de démarrage à l’aide d’un délégué. Vous passez ce délégué à la fonction Invoke sur l’object de formulaire de votre écran de démarrage. En fait, si vous appelez le formulaire de démarrage directement pour mettre à jour les éléments de progression / interface utilisateur, vous obtenez une exception si vous utilisez le CLR .Net 2.0. En règle générale, tout élément de l’interface utilisateur d’un formulaire doit être mis à jour par le thread qui l’a créé – c’est ce que assure Form.Invoke.

Enfin, je choisirais probablement de créer le splash (si vous n’utilisez pas la surcharge VB) dans la méthode principale de votre code. Pour moi, cela vaut mieux que d’avoir la forme principale en train de créer l’object et d’y être si étroitement lié. Si vous adoptez cette approche, je vous suggère de créer une interface simple que l’écran de démarrage implémente – quelque chose comme IStartupProgressListener – qui reçoit les mises à jour de la progression de démarrage via une fonction membre. Cela vous permettra de basculer facilement entre les classes selon vos besoins et de bien séparer le code. Le formulaire de démarrage peut également savoir quand se fermer si vous en informez lorsque le démarrage est terminé.

Un moyen simple est d’utiliser quelque chose comme ceci comme main ():

  Public Shared Sub Main() splash = New frmSplash splash.Show() ' Your startup code goes here... UpdateSplashAndLogMessage("Startup part 1 done...") ' ... and more as needed... splash.Hide() Application.Run(myMainForm) End Sub 

Lorsque le CLR .NET démarre votre application, il crée un thread principal et commence à exécuter main () sur ce thread. L’application.Run (myMainForm) à la fin fait deux choses:

  1. Démarre la «pompe à messages» de Windows en utilisant le thread qui a exécuté main () en tant que thread d’interface graphique.
  2. Désigne votre «formulaire principal» comme «formulaire d’arrêt» pour l’application. Si l’utilisateur ferme ce formulaire, l’application.Run () se termine et le contrôle retourne à votre main (), où vous pouvez effectuer n’importe quel arrêt souhaité.

Il n’y a pas besoin de lancer un thread pour prendre soin de la fenêtre de démarrage, et en fait c’est une mauvaise idée, car il vous faudrait alors utiliser des techniques sécurisées pour mettre à jour le contenu de splash de main ().

Si vous avez besoin d’autres threads pour effectuer des opérations d’arrière-plan dans votre application, vous pouvez les générer depuis main (). Rappelez-vous juste de définir Thread.IsBackground sur True, afin qu’ils meurent lorsque le thread principal / GUI se termine. Sinon, vous devrez vous arranger pour terminer tous vos autres threads, ou ils garderont votre application en vie (mais sans interface graphique) lorsque le thread principal se terminera.

J’ai posté un article sur l’incorporation de l’écran de démarrage dans l’application à codeproject. Il est multithread et pourrait vous intéresser

Encore un autre écran de démarrage en C #

 private void MainForm_Load(object sender, EventArgs e) { FormSplash splash = new FormSplash(); splash.Show(); splash.Update(); System.Threading.Thread.Sleep(3000); splash.Hide(); } 

Je l’ai eu quelque part sur Internet, mais je n’arrive pas à le retrouver. Simple mais efficace.

J’aime beaucoup la réponse d’Aku, mais le code est pour C # 3.0 et supérieur car il utilise une fonction lambda. Pour les personnes ayant besoin d’utiliser le code dans C # 2.0, voici le code utilisant un délégué anonyme au lieu de la fonction lambda. Vous avez besoin d’un format de formulaire supérieur appelé formSplash avec FormBorderStyle = None . Le paramètre TopMost = True du formulaire est important car l’écran de démarrage peut sembler apparaître, puis disparaît rapidement si ce n’est pas le plus haut. J’ai également choisi StartPosition=CenterScreen pour qu’il ressemble à ce qu’une application professionnelle ferait avec un écran de démarrage. Si vous voulez un effet encore plus frais, vous pouvez utiliser la propriété TrasparencyKey pour créer un écran de démarrage de forme irrégulière.

 private void formMain_Load(object sender, EventArgs e) { Hide(); bool done = false; ThreadPool.QueueUserWorkItem(delegate { using (formSplash splashForm = new formSplash()) { splashForm.Show(); while (!done) Application.DoEvents(); splashForm.Close(); } }, null); Thread.Sleep(2000); done = true; Show(); } 

Je ne suis pas d’accord avec les autres réponses recommandant WindowsFormsApplicationBase . D’après mon expérience, cela peut ralentir votre application. Pour être précis, pendant qu’il exécute le constructeur de votre formulaire en parallèle avec l’écran de démarrage, il retarde l’événement de votre formulaire.

Considérez une application (sans écran splashs) avec un constructeur qui prend 1 seconde et un gestionnaire d’événements sur Shown qui prend 2 secondes. Cette application est utilisable après 3 secondes.

Mais supposons que vous installiez un écran de démarrage avec WindowsFormsApplicationBase . Vous pourriez penser que MinimumSplashScreenDisplayTime de 3 secondes est judicieux et ne ralentira pas votre application. Mais, essayez, votre application va maintenant prendre 5 secondes pour charger.


 class App : WindowsFormsApplicationBase { protected override void OnCreateSplashScreen() { this.MinimumSplashScreenDisplayTime = 3000; // milliseconds this.SplashScreen = new Splash(); } protected override void OnCreateMainForm() { this.MainForm = new Form1(); } } 

et

 public Form1() { InitializeComponent(); Shown += Form1_Shown; Thread.Sleep(TimeSpan.FromSeconds(1)); } void Form1_Shown(object sender, EventArgs e) { Thread.Sleep(TimeSpan.FromSeconds(2)); Program.watch.Stop(); this.textBox1.Text = Program.watch.ElapsedMilliseconds.ToSsortingng(); } 

Conclusion: n’utilisez pas WindowsFormsApplicationBase si votre application possède un gestionnaire sur l’événement Slown. Vous pouvez écrire un meilleur code qui exécute le splash en parallèle au constructeur et à l’événement Shown.

En fait, la lecture mutuelle n’est pas nécessaire.

Laissez votre logique métier générer un événement chaque fois que vous souhaitez mettre à jour l’écran de démarrage.

Laissez ensuite votre formulaire mettre à jour l’écran de démarrage en conséquence dans la méthode connectée à eventhandler.

Pour différencier les mises à jour, vous pouvez déclencher différents événements ou fournir des données dans une classe héritée de EventArgs.

De cette façon, vous pouvez avoir un bon écran de démarrage sans aucun problème de multithreading.

En fait, avec cela, vous pouvez même prendre en charge, par exemple, une image gif sur un formulaire de démarrage. Pour que cela fonctionne, appelez Application.DoEvents () dans votre gestionnaire:

 private void SomethingChanged(object sender, MyEventArgs e) { formSplash.Update(e); Application.DoEvents(); //this will update any animation }