简体   繁体   中英

How to check a thread is done, then fill progress bar in C# / WPF

I am just working on my first GUI application on Windows.

I have a WPF GUI to a small C# utility which copies files. When the button is clicked to copy, I obviously don't want the GUI to hang. So, I fire off a new thread to run the method which copies the files. I assume I'm on track so far and there's no "better" way of doing it in C#?

Now, I have a ProgressBar which I want to appear filled when the thread is done. (It's fine running as indeterminate for now). How do I check when the copying is done?

So, so far I have:

Thread t = new Thread(delegate() 
{ 
    po.Organise(inputPath, outputPath, recursive); 
});

t.Start();

PBar.IsIndeterminate = true;

And I want something after that that works like:

if (t.Done)
{
    PBar.Value = 100;
}

Have a look at the BackgroundWorker class. It supports events like RunWorkerCompleted or ProgressChanged .
Have a look here , too (this is about threading in general + backgroundworker, again).

As already stated, consider the use of the BackgroundWorker class. It was designed for these situations and exposes events suited for what you are trying to accomplish.

Use the ProgressChanged event to report progress incrementally and the RunWorkerCompleted for when the task finishes. Check the MSDN page for code samples.

You need a callback method. This should get you started. It uses an AsyncCallback, which is the best way to tackle this type of issue.

I just looked up an example I've been using for a project and stripped out the code specific to my app:

System.Windows.Forms.MethodInvoker mi = new System.Windows.Forms.MethodInvoker(delegate()
{
    // Do your file copy here
});

AsyncCallback ascb = new AsyncCallback(delegate(IAsyncResult ar)
{
    this.Dispatcher.Invoke(new ThreadStart(delegate (){
    // set progressbar value to 100 here
    }), null);
});

mi.BeginInvoke(ascb, null);

Wrap the if (t.Done) block in its own method. Invoke this method from the end of your worker thread.

Also, you might want to give the worker thread a name to make it easier to spot in the debugger.

The quick and easy hack would be to just update the UI at the end of your anonymous method in your thread. Obviously you can't update it directly, but you can use Dispatcher.Invoke :

Thread t = new Thread(delegate() 
{ 
    po.Organise(inputPath, outputPath, recursive); 
    Dispatcher.Invoke(new Action(()=>{PBar.Value = 100;}),null);
});

t.Start();

As a general Windows programming principal, you have to make calls to update the UI from the UI thread (the one that is processing messages through a message pump).

In Windows Forms, the way that this was done was through the implementation of the ISynchronizeInvoke interface on the Control class, primarily through the implementation of the Invoke method .

With the release of .NET 2.0, it was realized that a better mechanism was needed to marshal calls into the correct context. That's where the SynchronizationContext comes in.

This class abstracts the interface you would use for marshaling calls to different contexts, allowing for specific implementations depending on the context.

So whether or not Windows Forms is the environment, or WPF, one call can be made in the same way across those contexts with the same effect (marshaling the call).

In your particular case, because you are using a closure (anonymous method), you can take advantage of the fact that a SynchronizationContext is available to you (through the static Current property ) at the invocation site of the Thread to provide the mechanism to call back to the UI thread from your background thread:

// Get the synchronization context.
// This is in the UI thread.
SynchronizationContext sc = SynchronizationContext.Current;

// Create the thread, but use the SynchronizationContext
// in the closure to marshal the call back.
Thread t = new Thread(delegate()  
{  
    // Do your work.
    po.Organise(inputPath, outputPath, recursive);  

    // Call back using the SynchronizationContext.
    // Can call the Post method if you don't care
    // about waiting for the result.
    sc.Send(delegate()
    {
        // Fill the progress bar.
        PBar.Value = 100;
    });
}); 

// Make the progress bar indeterminate.
PBar.IsIndeterminate = true;

// Start the thread.
t.Start(); 

Note, if you don't care about waiting for the result of the call back to the UI thread, you can make a call to the Post method instead, which will dispatch the call to the UI thread without waiting for that call to complete.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM