How to Correctly Implement a Backgroundworker with Progressbar Updates

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
}
}
}

Update ProgressBar by accessing backgroundWorker in another class

I'm new at programming in WPF and C# and getting used to OOP...

I advise you to use more typical modern WPF and Sharp implementations in your training.

  1. BackgroundWorker - slightly outdated. Nowadays, Task and async/await methods are used to implement asynchrony.
  2. The main way to get values ​​for the properties of UI elements is to
    bind to the properties of the Data Context. Bindings also solve the
    problem of asynchronously changing source properties in any thread,
    which cannot be done directly with UI element properties.
  3. To call methods of the Data Context in WPF, they are wrapped in
    commands. And some UI elements (including buttons, menu items) can
    call these commands.
  4. Since the main way of communicating with data is bindings, WPF
    practically does not use Code Behind Windows.

Demo example of your task, but implemented in a more modern way.

ViewModel class - This is used to set the data context.

Implemented using BaseInpc and RelayCommand.

using Simplified;
using System;
using System.Threading.Tasks;

namespace ProgressView
{
public class ProgressViewModel : BaseInpc
{
private bool isProgressExecute;
private RelayCommand _startProgressCommand;
private TimeSpan _progressTime;
private double _rangeEnd;
private double _rangeBegin;
private double _currentValue;

public double RangeBegin { get => _rangeBegin; set =>Set(ref _rangeBegin, value); }

public double RangeEnd { get => _rangeEnd; set => Set(ref _rangeEnd, value); }

public TimeSpan ProgressTime { get => _progressTime; set =>Set(ref _progressTime, value); }

public double CurrentValue { get => _currentValue; private set => Set(ref _currentValue, value); }

public RelayCommand StartProgressCommand => _startProgressCommand
?? (_startProgressCommand = new RelayCommand
(
StartProgressExecuteAsync,
() => !isProgressExecute
));


private async void StartProgressExecuteAsync()
{
if (isProgressExecute)
return;
isProgressExecute = true;
StartProgressCommand.RaiseCanExecuteChanged();

double begin = RangeBegin;
double end = RangeEnd;
double range = end - begin;
TimeSpan time = ProgressTime;


DateTime beginTime = DateTime.Now;
TimeSpan elapsed;
while ((elapsed = DateTime.Now - beginTime) < time)
{
CurrentValue = begin + range * elapsed.TotalMilliseconds / time.TotalMilliseconds;
await Task.Delay(10);
}

CurrentValue = end;

isProgressExecute = false;
StartProgressCommand.RaiseCanExecuteChanged();
}



}
}

Window XAML:

<Window x:Class="ProgressView.ProgressWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:ProgressView"
mc:Ignorable="d"
Title="ProgressWindow" Height="450" Width="800">
<Window.DataContext>
<local:ProgressViewModel RangeBegin="0"
RangeEnd="100"
ProgressTime="0:0:10"/>
</Window.DataContext>
<UniformGrid Columns="2">
<TextBlock Text="Begin: " VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox Text="{Binding RangeBegin}" VerticalAlignment="Center"/>
<TextBlock Text="End: " VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox Text="{Binding RangeEnd}" VerticalAlignment="Center"/>
<TextBlock Text="Time: " VerticalAlignment="Center" HorizontalAlignment="Right"/>
<TextBox Text="{Binding ProgressTime}" VerticalAlignment="Center"/>
<TextBlock VerticalAlignment="Center" HorizontalAlignment="Right">
<Run Text="Curent:"/>
<Run Text="{Binding CurrentValue, Mode=OneWay}"/>
</TextBlock>
<ProgressBar VerticalAlignment="Center" Height="20"
Minimum="{Binding RangeBegin}"
Maximum="{Binding RangeEnd}"
Value="{Binding CurrentValue, Mode=OneWay}"/>
<Button Content="Start Progress"
Command="{Binding StartProgressCommand, Mode=OneWay}"/>
</UniformGrid>
</Window>

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

Make a BackgroundWorker so ProgressBar will work

I think if you look at this example you will know how you can do it. I made up some values and named the variables same as you. If you know how long it takes for the while loop to run you can declare a DoWork only write this ---> progressBarWorker.DoWork+= and double tab. And you get an DoWork evenhandler. And in that eventhandler you can make a for-loop)

for(int i = 0; i < 100; i++)

{

Thread.Delay(100);

progressBarWorker.ReportProgress(i);

}

and then put ProgressBarWorker.RunWorkerAsync(); as the first thing in the button3 eventhandler.

A BackroundWorker Example I made for you

ProgressBar with BackgroundWorker in WPF

When you define your BackgroundWorker, enable reporting progress:

worker.WorkerReportsProgress = true;
worker.ProgressChanged += _printWorker_ProgressChanged;

Add a ProgressBar to your window. Set the Maximum to however many "updates" you want to report:

<ProgressBar x:Name="ProgressBar1" Maximum="8" />

Then increase the Value of the ProgressBar via the BackgroundWorker.ReportProgress method, after each line of code in your DoWork event:

void _printWorker_DoWork(object sender, DoWorkEventArgs e)
{
var worker = (BackgroundWorker)sender;

worker.ReportProgress(0);
_receiptReport = new Receipt(_invoice.InvoiceID, _invoice.ItemsinInvoice.Count);
worker.ReportProgress(1);
printerSettings = new System.Drawing.Printing.PrinterSettings();
...
...
worker.ReportProgress(6);
instanceReportSource.ReportDocument = _receiptReport;
worker.ReportProgress(7);
reportProcessor.PrintReport(instanceReportSource, printerSettings);
worker.ReportProgress(8);
}

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

You'll also need to make the ProgressBar visible prior to calling RunWorkerAsync(), then hide it in the RunWorkerCompleted event.

Specific Use Case For Background Updating Progress Bar

Here's a trivial example you can play around with and apply to your view model. It's important to use prototypes to create code and learn how it works in order to apply it to a larger and more complex application.

Please note that the example doesn't include trivial stuff like how to implement INotifyPropertyChanged and ICommand--those are easy to do.

Also, note the comments within TraverseYo. Specifically, the ones that tell you what thread you're currently on. Understanding the flow of execution across threads is important to get this working correctly. If you don't know what thread you're on, simply get the ApartmentState of the current thread. If it's STA, you're most likely on the UI thread.

public class LongLastingWorkViewModel : INotifyPropertyChanged
{
public bool Busy
{
// INotifyPropertyChanged property implementation omitted
}
public double PercentComplete
{
// INotifyPropertyChanged property implementation omitted
}

public ICommand PerformWork { get; set; }

public LongLastingWorkViewModel()
{
// delegated ICommand implementation omitted--there's TONS of it out there
PerformWork = new DelegatedCommand(TraverseYo);
}

private void TraverseYo()
{
// we are on the UI thread here
Busy = true;
PercentComplete = 0;
Task.Run(() => {
// we are on a background thread here
// this is an example of long lasting work
for(int i = 0; i < 10; i++)
{
Thread.Sleep(10 * 1000); // each step takes 10 seconds

// even though we are on a background thread, bindings
// automatically marshal property updates to the UI thread
// this is NOT TRUE for INotifyCollectionChanged updates!
PercentDone += .1;
}
Busy = false;
});
}

You can bind Busy to an overlay that blocks all UI while execution runs, bind PercentComplete to a progress bar, and PerformWork to a button.

Update progressbar from a backgroundworker in WPF

In case you trying to change the UI content, you should put the calls on UI Dispatcher. You can't modify UI objects from background thread. Replace your lines with these -

App.Current.Dispatcher.Invoke((Action)delegate()
{
Title += j.ToString();
});

and

App.Current.Dispatcher.Invoke((Action)delegate()
{
Title = "Done";
});

Using background worker and updating UI

If you need to update some UI components from a background thread you should use the Dispatcher in order to marshal the call to the main thread. This being said, you seem to be trying to update some progress bar. The BackgroundWorker class already provides a mechanism for that. So you can simply have a ProgressBar control on your form and then:

var bgWorker = new BackgroundWorker();

// Specify that this worker supports progress
bgWorker.WorkerReportsProgress = true;

bgWorker.OnProgressChanged += e =>
{
// This is called on the main thread. It safe to update your UI components here
myProgressBar.Value = e.ProgressPercentage;
};

bgWorker.DoWork += (s, e) =>
{
// Do something long lasting
for(int i = 0; i < x; i++) {
//Performing action i of x

// While doing so notify the subscribers of the progress event
var progress = (int)((i / x) * 100.0);
bgWorker.ReportProgress(progress);
}
};

bgWorker.RunWorkerCompleted += (s, e) =>
{
//Finish up things
};

bgWorker.RunWorkerAsync();


Related Topics



Leave a reply



Submit