62

I want a splash screen to show while the application is loading. I have a form with a system tray control tied to it. I want the splash screen to display while this form loads, which takes a bit of time since it's accessing a web service API to populate some drop-downs. I also want to do some basic testing for dependencies before loading (that is, the web service is available, the configuration file is readable). As each phase of the startup goes, I want to update the splash screen with progress.

I have been reading a lot on threading, but I am getting lost on where this should be controlled from (the main() method?). I am also missing how Application.Run() works, is this where the threads for this should be created from? Now, if the form with the system tray control is the "living" form, should the splash come from there? Wouldn't it not load until the form is completed anyway?

I'm not looking for a code handout, more of an algorithm/approach so I can figure this out once and for all :)

Randika Vishman
  • 7,983
  • 3
  • 57
  • 80
John Virgolino
  • 1,828
  • 3
  • 16
  • 25

12 Answers12

49

The trick is to to create separate thread responsible for splash screen showing.
When you run you app .net creates main thread and loads specified (main) form. To conceal hard work you can hide main form until loading is done.

Assuming that Form1 - is your main form and SplashForm is top level, 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();
}
aku
  • 122,288
  • 32
  • 173
  • 203
  • This is probably the best method I've seen for doing splash screens. Works BEAUTIFULLY on .NET compact with my pocket PC app. – Damien Jul 09 '09 at 09:53
  • 6
    @aku: Is it a good idea to use Application.DoEvents() in the application? I read lot about not using this function on internet like http://blogs.msdn.com/jfoscoding/archive/2005/08/06/448560.aspx – K Singh Jan 22 '10 at 13:19
  • 2
    It might not be a good idea to create the form in a non-UI thread...could end up in a world of pain :) – Cocowalla May 04 '10 at 20:29
  • 1
    +1 Damn multithreading is hard. Most solutions on the 'net - including the both codeproject projects *(the top two results on google)* and most of the other answers here - have **race-conditions where CloseSplashScreen might be called *before* ShowSplashScreen!** Both this and the accepted solution should work, but we were having problems with the accepted solution, so this answer helped greatly. Thanks! – BlueRaja - Danny Pflughoeft Mar 11 '11 at 20:03
  • Works like a charm.For me this is the best solution I have seen for this topic. Glad I reached this after a tremendous search.Thank you. – hima Jan 31 '14 at 12:02
  • After lots of searching we found that this piece of code causes a dreaded Multi-threading deadlock in our application as explained here: http://www.ikriv.com/dev/dotnet/MysteriousHang.html#WhatToDo Mysterious Hang or The Great Deception of InvokeRequired We've switched to the accepted solution. – Run CMD Apr 16 '14 at 12:05
  • @RunCMD: I ran into the same issue - see [here](http://stackoverflow.com/questions/8141342) – BlueRaja - Danny Pflughoeft May 29 '15 at 21:49
  • 2
    Spinning a loop on `Application.DoEvents()` is a terrible waste of CPU time. This answer should _not_ be used, and it is unfortunate it's so highly up-voted. [This answer](https://stackoverflow.com/a/4994912) is a much better option (i.e. use `Application.Run()` to handle message pumping). – Peter Duniho Oct 24 '17 at 06:34
46

Well, for a ClickOnce app that I deployed in the past, we used the Microsoft.VisualBasic namespace to handle the splash screen threading. You can reference and use the Microsoft.VisualBasic assembly from C# in .NET 2.0 and it provides a lot of nice services.

  1. Have the main form inherit from Microsoft.VisualBasic.WindowsFormsApplicationBase
  2. Override the "OnCreateSplashScreen" method like so:

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

Very straightforward, it shows your SplashForm (which you need to create) while loading is going on, then closes it automatically once the main form has completed loading.

This really makes things simple, and the VisualBasic.WindowsFormsApplicationBase is of course well tested by Microsoft and has a lot of functionality that can make your life a lot easier in Winforms, even in an application that is 100% C#.

At the end of the day, it's all IL and bytecode anyway, so why not use it?

Guy Starbuck
  • 21,603
  • 7
  • 53
  • 64
  • 2
    This was the most elegant solution since it uses built-in functionality that is abstracted very well by the Visual Basic library. I didn't know how to create a VB ApplicationBase program, so more research was needed...I'll add more info for completeness. Thanks for all the great feedback!! – John Virgolino Sep 09 '08 at 04:43
  • I've had to fix some badly-written manual splash screens-- using the supported functionality like Guy suggests is definitely the awesome way to go. :) – Greg D May 11 '09 at 17:26
  • Need to display a progress bar in the splash screen. So I created a public property to set the progressBar1.Value and accessed it from the main frmSplash splash = this.SplashScreen as frmSplashform; then, splash.Progress = 50 this throws invalidOperation exception. How to fix this? – RasikaSam Mar 17 '15 at 06:17
14

After looking all over Google and SO for solutions, this is my favorite: 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();
    }

    /// <summary>
    /// Show the Splash Screen (Loading...)
    /// </summary>
    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);
    }

    /// <summary>
    /// Close the splash (Loading...) screen
    /// </summary>
    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
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main(string[] 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();
    }

I had issues with the Microsoft.VisualBasic solution -- Worked find on XP, but on Windows 2003 Terminal Server, the main application form would show up (after the splash screen) in the background, and the taskbar would blink. And bringing a window to foreground/focus in code is a whole other can of worms you can Google/SO for.

Adam Nofsinger
  • 4,004
  • 3
  • 34
  • 42
10

I recommend calling Activate(); directly after the last Show(); in the answer provided by aku.

Quoting MSDN:

Activating a form brings it to the front if this is the active application, or it flashes the window caption if this is not the active application. The form must be visible for this method to have any effect.

If you don't activate your main form, it may be displayed behind any other open windows, making it look a bit silly.

DelftRed
  • 196
  • 1
  • 11
10

This is an old question, but I kept coming across it when trying to find a threaded splash screen solution for WPF that could include animation.

Here is what I ultimately pieced together:

App.XAML:

<Application Startup="ApplicationStart" …

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();
  }
Jay
  • 56,361
  • 10
  • 99
  • 123
6

I think using some method like aku's or Guy's is the way to go, but a couple of things to take away from the specific examples:

  1. The basic premise would be to show your splash on a separate thread as soon as possible. That's the way I would lean, similar to what aku's illustrated, since it's the way I'm most familiar with. I was not aware of the VB function Guy mentioned. And, even thought it's a VB library, he is right -- it's all IL in the end. So, even if it feels dirty it's not all that bad! :) I think you'll want to be sure that either VB provides a separate thread for in that override or that you create one yourself -- definitely research that.

  2. Assuming you create another thread to display this splash, you will want to be careful of cross thread UI updates. I bring this up because you mentioned updating progress. Basically, to be safe, you need to call an update function (that you create) on the splash form using a delegate. You pass that delegate to the Invoke function on your splash screen's form object. In fact if you call the splash form directly to update progress/UI elements on it, you'll get an exception provided you are running on the .Net 2.0 CLR. As a rule of thumb, any UI element on a form must be updated by the thread that created it -- that's what Form.Invoke insures.

Finally, I would likely opt to create the splash (if not using the VB overload) in the main method of your code. To me this is better than having the main form perform creation of the object and to be so tightly bound to it. If you take that approach, I'd suggest creating a simple interface that the splash screen implements -- something like IStartupProgressListener -- which receives start-up progress updates via a member function. This will allow you to easily swap in/out either class as needed, and nicely decouples the code. The splash form can also know when to close itself if you notify when start-up is complete.

Community
  • 1
  • 1
Peter Meyer
  • 25,711
  • 1
  • 34
  • 53
5

One simple way is the use something like this as main():

<STAThread()> 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

When the .NET CLR starts your application, it creates a 'main' thread and starts executing your main() on that thread. The Application.Run(myMainForm) at the end does two things:

  1. Starts the Windows 'message pump', using the thread that has been executing main() as the GUI thread.
  2. Designates your 'main form' as the 'shutdown form' for the application. If the user closes that form, then the Application.Run() terminates and control returns to your main(), where you can do any shutdown you want.

There is no need to spawn a thread to take care of the splash window, and in fact this is a bad idea, because then you would have to use thread-safe techniques to update the splash contents from main().

If you need other threads to do background operations in your application, you can spawn them from main(). Just remember to set Thread.IsBackground to True, so that they will die when the main / GUI thread terminates. Otherwise you will have to arrange to terminate all your other threads yourself, or they will keep your application alive (but with no GUI) when the main thread terminates.

McKenzieG1
  • 13,960
  • 7
  • 36
  • 42
4

I posted an article on splash screen incorporation in the application at codeproject. It is multithreaded and might be of your interest

Yet Another Splash Screen in C#

K Singh
  • 1,710
  • 1
  • 17
  • 32
4
private void MainForm_Load(object sender, EventArgs e)
{
     FormSplash splash = new FormSplash();
     splash.Show();
     splash.Update();
     System.Threading.Thread.Sleep(3000);
     splash.Hide();
}

I got this from the Internet somewhere but cannot seem to find it again. Simple but yet effective.

LPL
  • 16,827
  • 6
  • 51
  • 95
3

I like Aku's answer a lot, but the code is for C# 3.0 and up since it uses a lambda function. For people needing to use the code in C# 2.0, here's the code using anonymous delegate instead of the lambda function. You need a topmost winform called formSplash with FormBorderStyle = None. The TopMost = True parameter of the form is important because the splash screen might look like it appears then disappears quickly if it's not topmost. I also choose StartPosition=CenterScreen so it looks like what a professional app would do with a splash screen. If you want an even cooler effect, you can use the TrasparencyKey property to make an irregular shaped splash screen.

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();
  }
Christian Specht
  • 35,843
  • 15
  • 128
  • 182
Patratacus
  • 1,651
  • 1
  • 16
  • 19
2

I disagree with the other answers recommending WindowsFormsApplicationBase. In my experience, it can slow your app. To be precise, while it runs your form's constructor in parallel with the splash screen, it postpone your form's Shown event.

Consider an app (without splashs screen) with a constructor that takes 1 second and a event handler on Shown that takes 2 seconds. This app is usable after 3 seconds.

But suppose you install a splash screen using WindowsFormsApplicationBase. You might think MinimumSplashScreenDisplayTime of 3 seconds is sensible and won't slow your app. But, try it, your app will now take 5 seconds to load.


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

    protected override void OnCreateMainForm()
    {
        this.MainForm = new Form1();
    }
}

and

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.ToString();
}

Conclusion: don't use WindowsFormsApplicationBase if your app has a handler on the Slown event. You can write better code that runs the splash in parallel to both the constructor and the Shown event.

Colonel Panic
  • 132,665
  • 89
  • 401
  • 465
0

Actually mutlithreading here is not necessary.

Let your business logic generate an event whenever you want to update splash screen.

Then let your form update the splash screen accordingly in the method hooked to eventhandler.

To differentiate updates you can either fire different events or provide data in a class inherited from EventArgs.

This way you can have nice changing splash screen without any multithreading headache.

Actually with this you can even support, for example, gif image on a splash form. In order for it to work, call Application.DoEvents() in your handler:

private void SomethingChanged(object sender, MyEventArgs e)
{
    formSplash.Update(e);
    Application.DoEvents(); //this will update any animation
}
Do-do-new
  • 794
  • 8
  • 15