4

The concept of a splash screen doesn't strike me as something that should be so complicated, but I'm having trouble getting the whole splash screen painted.

Let's say I have two System.Windows.Forms.Forms: RealForm and SplashScreen. The RealForm contains the initial GUI. During the RealForm's loading process (that is, in the event handler for the Load event), RealForm creates a connection to a database and then tests the connection. This can take several seconds, so before I do all of this, I try to throw up a splash screen like so:

private void RealForm_Load(object sender, EventArgs e)
{
    SplashScreen splash = new SplashScreen();
    splash.Show();
    loadAndCheckDatabase();
    splash.Close();
}

The problem is that the splash screen only gets partially drawn and so it doesn't really look like much of a splash screen. Any idea why? What should I do?

For bonus points, does anyone know where to find an explanation for all series of events that occur in typical creation, usage, and destruction of forms? The above problem is probably because I don't understand what order things occur or where to hook into form events.


UPDATE Close, but not quite: looking for a little more advice. Here's the current code:

private SplashScreen splash = new SplashScreen();

private void RealForm_Load(object sender, EventArgs e)
{

    splash.Show();

    BackgroundWorker worker = new BackgroundWorker();
    worker.DoWork += new DoWorkEventHandler(worker_DoWork);
    worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler
         (worker_RunWorkerCompleted);
    worker.RunWorkerAsync();
    this.Visible = false;

}

void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
    splash.Close();
    this.Visible = true;
}

void worker_DoWork(object sender, DoWorkEventArgs e)
{
    loadAndCheckDatabase();
}

Unfortunately, the this.Visible = false doesn't do anything, because it is automatically set to true in code that I don't control. So, to get around this, I put the this.Visible = false into the handler for the form's Shown event. However, as you can probably guess, you can still see the form for a split second... so this is not really a good solution.

Is there any way to wait on the worker thread while I'm still in the Load handler?

Wai Ha Lee
  • 8,598
  • 83
  • 57
  • 92
JnBrymn
  • 24,245
  • 28
  • 105
  • 147

6 Answers6

9

You are displaying the splash screen and checking your database on the same thread. The thread can only do one thing at a time.

A quick and cheap way to fix this is to have loadAndCheckDatabase() call Application.DoEvents() periodically. However that's a cheap fix.

You really want to run loadAndCheckDatabase() on its own thread, a BackgroundWorker is a nice simple way to do this.

Matt Greer
  • 60,826
  • 17
  • 123
  • 123
  • 2
    This might be obvious but make sure you don't have any UI code in your loadAndCheckDatabase call, not even a message box or you could run into trouble. – Steve Sheldon Aug 13 '10 at 16:45
  • 2
    I wouldn't even bother with Application.DoEvents(), just use BackgroundWorker. If you want to execute a simple unit of logic whilst the UI remains responsive, use BackgroundWorker. If you want the UI thread to go and execute everything thats waiting on the UI's message queue, *even* if the user's managed to queue up mouse clicks that perform random other bits of logic which interfere with your current operation in a way that looks like loads of threads are running, but actually only one is, then use Application.DoEvents(). – Alex Humphrey Aug 13 '10 at 18:00
2

Like me you probably created this as an afterthought and do not want to go through all the heck of redesigning your code to fit multi-threaded architecture...

First create a new Form called SpashScreen, in the properties click on BackgroundImage and import whatever image you want. Also set FormBorderStyle to None so that you can't click on the x to close the screen.

    public Form1()
    {
        InitializeComponent();

        BackgroundWorker bw = new BackgroundWorker();
        bw.WorkerSupportsCancellation = true;
        bw.DoWork += new DoWorkEventHandler(bw_DoWork);
        bw.RunWorkerAsync();  // start up your spashscreen thread
        startMainForm();      // do all your time consuming stuff here  
        bw.CancelAsync();     // close your splashscreen thread  
    }


    private void bw_DoWork(object sender, DoWorkEventArgs e)
    {
        BackgroundWorker worker = sender as BackgroundWorker;

        SplashScreen ss = new SplashScreen();
        ss.Show();

        while (!worker.CancellationPending) //just hangout and wait
        {
            Thread.Sleep(1000);
        }

        if (worker.CancellationPending)
        {
            ss.Close();
            e.Cancel = true;
        }

    }

This does not support progress bar or any fancy stuff but I am sure it can be tweaked.

Mr.Black
  • 465
  • 5
  • 9
  • Thanks for the sample. The last 'if' statement is unnecessary - the code will not get there unless worker.CancellationPending is true, given the above while loop. – Timur K Sep 16 '15 at 22:27
1

Why not when you run the app open a form which loads whatever you need to load into a class, then when it's done loading, open your main form and send the class into it? Alternatively you can use a singleton to load everything.

In your Program.cs:

    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.Run(new SplashScreen());
    }

Then in SplashScreen:

    public SplashScreen()
    {
        InitializeComponent();
        LoadEverything();
        this.Visible = false;
        MainForm mainForm = new MainForm(LoadedClass);
        mainForm.ShowDialog();
        this.Close();
    }

I need to update this:

Here's working code (the above is just the concept).

    public SplashScreen()
    {
        InitializeComponent();
        _currentDispatcher = Dispatcher.CurrentDispatcher;

        // This is just for the example - start a background method here to call
        // the LoadMainForm rather than the timer elapsed
        System.Timers.Timer loadTimer = new System.Timers.Timer(2000);
        loadTimer.Elapsed += LoadTimerElapsed;
        loadTimer.Start();
    }

    public void LoadMainForm()
    {
        // Do your loading here
        MainForm mainForm = new MainForm();

        Visible = false;
        mainForm.ShowDialog();
        System.Timers.Timer closeTimer = new System.Timers.Timer(200);
        closeTimer.Elapsed += CloseTimerElapsed;

        closeTimer.Start();
    }

    private Dispatcher _currentDispatcher;

    private void CloseTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        if (sender is System.Timers.Timer && sender != null)
        {
            (sender as System.Timers.Timer).Stop();
            (sender as System.Timers.Timer).Dispose();
        }
        _currentDispatcher.BeginInvoke(new Action(() => Close()));
    }

    private void LoadTimerElapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        if (sender is System.Timers.Timer && sender != null)
        {
            (sender as System.Timers.Timer).Stop();
            (sender as System.Timers.Timer).Dispose();
        }
        _currentDispatcher.BeginInvoke(new Action(() => LoadMainForm()));
    }
Marc K
  • 392
  • 3
  • 11
1

Try putting the call to loadAndCheckDatabase in a background thread, moving the close of the splash screen there, or simply closing it with a timer in the splash screen. With the work in a background thread, the all UI functions will be able to operate without interruption.

Russ
  • 4,091
  • 21
  • 32
1

You should be running the splash screen in a different thread, that should allow it to draw properly.

Have a look here:

http://www.codeproject.com/KB/cs/prettygoodsplashscreen.aspx

Daniel Frear
  • 1,459
  • 14
  • 22
  • 2
    Since the splash screen is a UI component, it really should run on the UI thread (the main thread of a WinForms app). The loadAndCheckDatabase() should be pushed off to a worker thread. – Matt Greer Aug 13 '10 at 16:22
  • UI components really can't and shouldn't run on a background thread--they should only run on the main thread. – Russ Aug 13 '10 at 16:38
  • Right, no UI on a background thread, you will get a bunch of cross thread call exceptions. – Steve Sheldon Aug 13 '10 at 16:43
  • I thought this looked promising. Can you help me understand why this is a bad thing? It seems easier, and since the SplashScreen doesn't really *do* anything I don't see why it can't go in a different thread. – JnBrymn Aug 13 '10 at 16:52
  • You might be able to get away with it in this case, but in general it's a bad idea. I really do recommend learning `BackgroundWorker`. It might seem confusing at first, but it's actually really easy to use. It's a very powerful tool to have in your WinForms arsenal. – Matt Greer Aug 13 '10 at 16:58
  • @Matt cheers, I "did" know that but apparently I conveniently forgot when it mattered! – Daniel Frear Aug 19 '10 at 13:51
  • 1
    Technically, you can happily run two windows on two separate threads. There is no problem with this. The problem only comes when you try and interact with UI elements created on a different thread. Often, moving all app initialization into a background worker is much more complicated than throwing up a thread to run the splash screen. – Mark Aug 15 '13 at 11:19
-2

You can use the await command with .Net Framework 4.5 Your form will not be visible until the task is completed

    private void YourForm_Load(object sender, EventArgs e)
    {
        //call SplashScreen form
        SplashScreen splash = new SplashScreen();
        splash.Show();
        Application.DoEvents();
        //Run your long task while splash screen is displayed i.e. loadAndCheckDatabase
        Task processLongTask = loadAndCheckDatabase();
        //wait for the task to be completed
        processLongTask.Wait();
        splash.Close();

        //...
    }
  • 2
    There is absolutely no advantage to the above code, because the main thread immediately blocks waiting for the task to complete. You may as well do the long task in the main thread, it won't make any difference. – Mark Aug 15 '13 at 11:16