Running a Method in Backgroundworker and Showing Progressbar

Running a method in BackGroundWorker and Showing ProgressBar

Instead of using one ParseFiles method (which should depend on myBGWorker) use loop and method which parse one file. Report progress percentage in that loop:

private void parseButton_Click(object sender, EventArgs e)
{
parseButton.Enabled = false;
myBGWorker.RunWorkerAsync();
}

private void myBGWorker_DoWork(object sender, DoWorkEventArgs e)
{
for(int i = 0; i < filesCount; i++)
{
ParseSingleFile(); // pass filename here
int percentage = (i + 1) * 100 / filesCount;
myBGWorker.ReportProgress(percentage);
}
}

void myBGWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
myProgressBar.Value = e.ProgressPercentage;
}

void myBGWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
parseButton.Enabled = true;
MessageBox.Show("Done");
}

Using BackgroundWorker with Progressbar in WindowsForm with different Classes

First, you need to configure your backgroundworker:

    public FrmMain()
{
InitializeComponent();

_backgroundWorker.WorkerReportsProgress = true;
_backgroundWorker.ProgressChanged += _backgroundWorker_ProgressChanged;
_backgroundWorker.DoWork += _backgroundWorker_DoWork;
_backgroundWorker.RunWorkerCompleted += _backgroundWorker_RunWorkerCompleted;
}

You want a progressBar, so your backgroundWorker needs to report progress. The work will be done asynchronously, so you need to specify a delegate where your download will be executed.

    private void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
DownloadWikis wikis = new DownloadWikis();

}

In your DownloadWikis class, write a delegate. It will help us to call the backgroundworker ReportProgress method.

     public delegate void ReportProgressDelegate(int percentage);

Update your Download signature, it should be like this:

    public void Download(ReportProgressDelegate reportProgress)
{
// FirstLoop
for(int i=0; i < 100; i++)
{
// Do some work with GetLinks
// this will call the backgroundworker ReportProgress method.
reportProgress(i);
}
}

Now, you can complete with this:

    private void _backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
DownloadWikis wiki = new DownloadWikis();
wiki.Download(_backgroundWorker.ReportProgress);
}

To handle the progress bar value, the second delegate is here:

     private void _backgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}

With this step, you will be able to see when your work will be completed:

     private void _backgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("It's done !");
}

Last step, the easy one:

    private void btnDownloadWikis_Click(object sender, EventArgs e)
{
_backgroundWorker.RunWorkerAsync();
}

Now, you have to do some maths do determine your percentage.

Progress Bar not working properly in background worker

You cannot access Windows controls like ProgressBar from inside the DoWork method or any method it has called because the thread that runs this code is a background thread and not the same thread that created the Control. You will get an exception whose message states that the control is being access by a thread other than the thread that created it, if you try. This is an inviolable rule about windows controls; they must always only be accessed by the thread that created them

The BackgroundWorker has a WorkerReportsProgress property that must be set to true, and a ReportProgress() method that you can call with an int (and pass an optional object for more info) of the percentage complete. When you call this method in your DoWork, the BackgroundWorker will automatically raise the ProgressChanged event and critically, it does so using the foreground thread it was created with(the same thread your other controls were created with) so code inside your ProgressChanged event handler is run using the proper thread and can access the ProgressBar Control without causing an exception

In summary:

  • Set WorkerReportsProgress to true
  • Call ReportProgress inside DoWork, passing a percentage complete or using the int to indicate the process has reached some stage (it doesn't have to be a percentage)
  • Attach an event handler to your worker's ProgressChanged event
  • Move your ProgressBar code to the ProgressChanged event handler

using progress bar in background worker

Showing the progress dialog with ShowDialog blocks the UI thread, so there is no way that progress will be updated. The code updating stuff on the UI after the background worker is done can't execute for the same reason.

The way to go is:

  1. Implement the background worker's progress event and have the background worker report progress in this event (this will be called in the context of the UI thread, so you don't have to worry about cross-thread problems)
  2. Make the progress "dialog" a normal window that is topmost and looks like a dialog (you may need to implement further stuff to make sure it can't be deactivated and disable the main window, because it will continue being responsive).
  3. In the progress event handler, update the progress status in the secondary window.

You say now in the comments that your progress bar is actually a marquee, not showing any real progress. Doesn't really matter to the solution - just don't do the progress update stuff.

The thing is that ShowDialog blocks your UI thread until the dialog is closed, which you can not do from your code. Make it a non-modal dialog and you should be fine.

How to correctly implement a BackgroundWorker with ProgressBar updates?

As you haven't shown your full BackgroundWorker code, I can't tell if you have implemented it correctly. As such, all I can do is to show you a simple working example of updating a ProgressBar control:

UserControl XAML:

<UserControl x:Class="WpfApplication1.Views.TestView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300" Loaded="UserControl_Loaded">
<ProgressBar x:Name="progressBar" Height="25" Margin="20" Minimum="0"
Maximum="50" />
</UserControl>

MainWindow XAML:

<Window x:Class="WpfApplication1.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Views="clr-namespace:WpfApplication1.Views"
Title="MainWindow" Height="350" Width="525">
<Views:TestView />
</Window>

UserControl code behind:

using System.ComponentModel;
using System.Threading;
using System.Windows;
using System.Windows.Controls;

namespace WpfApplication1.Views
{
public partial class TestView : UserControl
{
private BackgroundWorker backgroundWorker = new BackgroundWorker();

public TestView()
{
InitializeComponent();
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.ProgressChanged += ProgressChanged;
backgroundWorker.DoWork += DoWork;
// not required for this question, but is a helpful event to handle
backgroundWorker.RunWorkerCompleted += BackgroundWorker_RunWorkerCompleted;
}

private void UserControl_Loaded(object sender, RoutedEventArgs e)
{
backgroundWorker.RunWorkerAsync();
}

private void DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i <= 100; i++)
{
// Simulate long running work
Thread.Sleep(100);
backgroundWorker.ReportProgress(i);
}
}

private void ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// This is called on the UI thread when ReportProgress method is called
progressBar.Value = e.ProgressPercentage;
}

private void BackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// This is called on the UI thread when the DoWork method completes
// so it's a good place to hide busy indicators, or put clean up code
}
}
}


Related Topics



Leave a reply



Submit