2

I have a program that takes 10-20 seconds to start. I need to show a window with a progress bar when the program starts up. I know BackgroundWorker's are the correct way to do this but I unfortunately don't have the time to rework the whole gui using threads right now. Here is some code I'm trying but it is not working. Can anyone help..?

using System;
using System.Threading;
using System.Windows;

namespace WpfApplication1
{
    public partial class Window1 : Window
    {
        public Window1()
        {
            InitializeComponent();

            Thread t = new Thread(ShowLoadingWindow);
            t.SetApartmentState(ApartmentState.STA);
            t.Priority = ThreadPriority.Highest;
            t.Start();

            DoSomeLongTask();
            keepLooping = false;
        }

        bool keepLooping = true;
        private void ShowLoadingWindow()
        {
            LoadingWindow lw = new LoadingWindow();
            lw.Show();

            while (keepLooping)
            {
                Thread.Sleep(1);
            }

            lw.Close();
        }

        private void DoSomeLongTask()
        {
            for (int i = 0; i < 20000; i++)
            {
                GC.Collect();
            }
        }
    }
}

The loading window is just a bare window with a progress bar. Howcome this doesn't work?

user113164
  • 1,437
  • 5
  • 17
  • 17
  • 6
    You should stick with BackgroundWorker. It has the necessary hooks to do this properly, and in the long run will be simpler and faster to code than trying to roll your own threading solution. You don't have to rework the entire GUI to make it work; you just need some background task to execute in the worker. – Robert Harvey Feb 04 '10 at 15:22
  • Totally agree with Robert. I have no idea how you plan on updating the progress bar with code shown above but the BackgroundWorker makes it trivial. – Sailing Judo Feb 04 '10 at 15:24

3 Answers3

10

You're still doing the long task in the main thread. You need to show the loading window in the main thread and do the long task in the background.

Using a BackgroundWorker is really the simplest solution for this:

BackgroundWorker bw;
LoadingWindow lw;

public Window1() {
    bw = new BackgroundWorker();
    lw = new LoadingWindow();

    bw.DoWork += (sender, args) => {
        // do your lengthy stuff here -- this will happen in a separate thread
        ...
    }

    bw.RunWorkerCompleted += (sender, args) => {
        if (args.Error != null)  // if an exception occurred during DoWork,
            MessageBox.Show(args.Error.ToString());  // do your error handling here

        // close your loading window here
        ...
    }

    // open and show your loading window here
    ...

    bw.RunWorkerAsync(); // starts the background worker
}
Heinzi
  • 167,459
  • 57
  • 363
  • 519
  • +1 I've posted the fix for his code, but yours is probably the better solution. – Ian Feb 04 '10 at 15:28
5

It really is very easy to use backgroundworker.

public partial class Window1 : Window
{
    BackgroundWorker worker = new BackgroundWorker();

    public Window1()
    {
        worker.DoWork += new DoWorkEventHandler(worker_DoWork);
        worker.ReportsProgress = true;
        worker.ProgressChanged += new ProgressChangedEventHandler(update_progress);
    }


    void worker_DoWork(object sender, DoWorkEventArgs e){
        DoSomeLongTask();
        //call worker.ReportProgress to update bar
    }

    void update_progress(object sender, ProgressChangedEventArgs e)
    {
        myscrollbar.Value = e.Value;
    }
}

The keything to keep in mind is to never touch gui stuff from DoWork method. That has to through ProgressChanged/ReportProgress

Robert Harvey
  • 178,213
  • 47
  • 333
  • 501
Esben Skov Pedersen
  • 4,437
  • 2
  • 32
  • 46
  • I think this one should be marked as an answer. It gave me the answer to my problem and that answer is like super simple. Really great piece of example code. – Pijusn Oct 16 '11 at 05:26
3

If you don't want the background worker you need to adjust your code, to do the long task in the new thread.

using System;
using System.Threading;
using System.Windows;

namespace WpfApplication1
{
  public partial class Window1 : Window
  {
      public Window1()
      {
          InitializeComponent();

          Thread t = new Thread(DoSomeLongTask);
          t.Start();

          // switch this with the DoSomeLongTask, so the long task is
          // on the new thread and the UI thread is free.
          ShowLoadingWindow();
    }
  }
}

If you want to then update a progress bar from your "DoSomeLongTask" method then you'll need to ensure that you call invoke. For example:

delegate void ProgressDelegate(int Progress);  
private void UpdateProgress( int  Progress)  
{  
   if (!progressBar1.Dispatcher.CheckAccess())  
   {  
     progressBar1.Value = Progress;  
   }  
   else  
   {  
     ProgressDelegate progressD = new ProgressDelegate(UpdateProgress);  
     progressBar1.Dispatcher.Invoke(progressD, new object[] { Progress });  
   }  
}  
Ian
  • 33,605
  • 26
  • 118
  • 198
  • +1 for showing how to improve the existing solution. BTW: `keepLooping = false` needs to be moved to the end of `DoSomeLongTask()`, IMO, to make sure that `ShowLoadingWindow()` terminates. – Heinzi Feb 04 '10 at 15:34
  • lw.Close() should be called in the UI thread, rather than the new Thread, so should be ok. Updating the progress bar is another matter though, so have updated my answer. – Ian Feb 04 '10 at 15:35
  • You are right, of course. I updated my comment before reading your answer. :-) – Heinzi Feb 04 '10 at 15:37
  • That's true about the keepLooping = false. I will update my answer at some point :P – Ian Feb 04 '10 at 15:50