views:

278

answers:

4

Hi guys,

I have a windows form application that needs to load a bunch of things before loading the Main window. I thought this would justify a progressbar, so I thought I display another form that contains the progressbar control using the constructor of my main form.

It all works fine but if I try to put text in a label on the intro form its content won't show until the main form is loaded. Is here a way to avoid this other than loading the Intro window first?

Cheers

+2  A: 

There sure is. It's called a BackgroundWorker.

Here is a code snippet from Figo Fei with slight modification for explanation purposes:

    private void button1_Click(object sender, EventArgs e)
    {
        progressBar1.Maximum = 100;
        backgroundWorker1.WorkerReportsProgress = true;
        backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
        backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
        backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
        backgroundWorker1.RunWorkerAsync();
    }

    private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
    {
        // This would be the load process, where you put your Load methods into.
        // You would report progress as something loads.

        for (int i = 0; i < 100; i++)
        {
            Thread.Sleep(100);
            backgroundWorker1.ReportProgress(i); //run in back thread
        }
    }

    private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e) //call back method
    {
        progressBar1.Value = e.ProgressPercentage;
    }
    private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) //call back method
    {
        progressBar1.Value = progressBar1.Maximum;
    }

Hope this helps you.

Kyle Rozendo
+1 for BackgroundWorker reference - it's amazing how many people actually don't know of the existence and simplicity of that class.
Ian Kemp
I seriously doubt that a BGW is the right choice - it needs a running MessagePump (for ProgressChange) and that was the problem to begin with.
Henk Holterman
The problem was that the UI thread is being blocked. Putting in the BGW in will open the UI thread back up, allowing for the ProgressBar to run 100%. That was the problem, the ProgressChange event will fire to the UI thread. This is what the BGW was made for. So I completely disagree with you Henk, and your downvote.
Kyle Rozendo
No, a BackgroundWorker is definitely the wrong choice here. You can use it to update the SplashForm but only through the normal Invoke mechanism, not through the ProgressChanged event. All those events will be queued and executed en masse when the MessagePump starts (ie when loading is completed). I just checked.
Henk Holterman
The question was structured in a way that a different form was going to be shown because of the long load with the progress bar. My solution was aimed at taking away the need for a secondary form and using the progress bar on the first. Your solution is aimed at keeping the Splash Screen. Therefore, that means we're both correct, just for different solutions, if that makes sense.
Kyle Rozendo
+1  A: 

You can show your SplashForm from either the main program or the MainForm constructor, that doesn't really matter. What you are seeing is that as long as your Loading process isn't completed, no messages are processed and hence no Screen updates are happening. The ProgressBar is an exception, it runs it's own thread for precisely this reason.

The short solution is to do a SplashForm.Update() after changing the Label. A little more involved would be to start a separate Thread with a MessagePump (Application.Run). Here is a SO question with some more leads.

Henk Holterman
+1 Thanks Henk, that is all I needed. All work fine now - I never thought of updating the form because all values have been set initially in design mode...
G Berdal
+4  A: 

Warning: this post contains elements of self promotion ;o)

I would probably use a splash form in this case. I wrote a blog post a while ago (triggered by this SO Q&A) about a thread-safe splash form that could be used together will long-running main form initializations.

In short the approach is to using ShowDialog, but to create and display the form on a separate thread so it doesn't block the main thread. The form contains a status message label (could of course be extended with a progressbar as well). Then there is a static class that provides thread-safe methods for displaying, updating and closing the splash form.

Condensed code samples (for commented code samples, check the blog post):

using System;
using System.Windows.Forms;
public interface ISplashForm
{
    IAsyncResult BeginInvoke(Delegate method);
    DialogResult ShowDialog();
    void Close();
    void SetStatusText(string text);
}

using System.Windows.Forms;
public partial class SplashForm : Form, ISplashForm
{
    public SplashForm()
    {
        InitializeComponent();
    }
    public void SetStatusText(string text)
    {
        _statusText.Text = text;
    }
}

using System;
using System.Windows.Forms;
using System.Threading;
public static class SplashUtility<T> where T : ISplashForm
{
    private static T _splash = default(T);
    public static void Show()
    {
        ThreadPool.QueueUserWorkItem((WaitCallback)delegate
        {
            _splash = Activator.CreateInstance<T>();
            _splash.ShowDialog();
        });
    }

    public static void Close()
    {
        if (_splash != null)
        {
            _splash.BeginInvoke((MethodInvoker)delegate { _splash.Close(); });
        }
    }

    public static void SetStatusText(string text)
    {
        if (_splash != null)
        {
            _splash.BeginInvoke((MethodInvoker)delegate { _splash.SetStatusText(text); });
        }
    }
}

Example of usage:

SplashUtility<SplashForm>.Show();
SplashUtility<SplashForm>.SetStatusText("Working really hard...");
SplashUtility<SplashForm>.Close();
Fredrik Mörk
Fredrik, nice interface but I wonder if a ThreadPool thread is the best choice here. Loading could take many seconds and technically you ought to use a STA thread.
Henk Holterman
Thanks very much. This will be very useful in the future.
G Berdal
A: 

The problem is most likely because there is not a running message loop at the time you are attempting to display the progress bar form. There should be a line of code that looks something like the following in the entry point of your application.

Application.Run(new Form1());

The call to Application.Run will start the message loop, but do you see how the Form1 constructor is executed before the message loop is running? And since your progress bar logic is in that constructor then there is no mechanism running that can dispatch the form's painting messages.

I think the best approach is to load a splash screen first and kick off a worker thread (you could use BackgroundWorker for that) that does the time consuming work. The progress bar will live on the splash screen form and you will update that periodically. Once the work is complete then you can close the splash screen and load the main form.

Brian Gideon
Usually the Loading takes place inside Application.Runs, when it shows the Form, but before it dives into the MessageLoop.
Henk Holterman