Set up Backgroundworker MVVM, Update a Progressbar - c#

I have got a StartCommand Class:
public class StartCommand : ICommand
{
public void Execute(object parameter)
{
//Fill Listview1
...
//Here I want to increase the Progressbarvalue
//Fill Listview2
...
//Here again and so far..
}
}
Execute Command will be executed, when clicking the Startbutton on my MainWindow.xaml (where's also the progressBar).
What I want now, is updating the Progressbar on these places (look at Code), while the ListViews are loading. How do I set up the Backgroundworker?
I tried something like that:
public class StartCommand : ICommand
{
MainWindow mainWindow;
public StartCommand(MainWindow mainWindow)
{
this.mainWindow = mainWindow
}
public void Execute(object parameter)
{
//Fill Listview1
...
mainWindow.backgroundWorker1.RunWorkerAsync(10);
//Fill Listview2
...
mainWindow.backgroundWorker1.RunWorkerAsync(20);
}
}
MainWindow:
public partial class MainWindow : Window
{
BackgroundWorker backgroundWorker1;
public MainWindow()
{
InitializeComponent();
InitializeBackgroundWorker();
}
private void InitializeBackgroundWorker()
{
backgroundWorker1.DoWork +=
new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(
backgroundWorker1_RunWorkerCompleted);
backgroundWorker1.ProgressChanged +=
new ProgressChangedEventHandler(
backgroundWorker1_ProgressChanged);
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
e.Result = UpdateProgressBar((int)e.Argument, worker);
}
private int UpdateProgressBar(int value, BackgroundWorker worker)
{
worker.ReportProgress(value);
return Convert.ToInt32(progressBar.Value);
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
}
private void backgroundWorker1_ProgressChanged(object sender,ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
}
}
}
Thats not working (This is only some Copy/Paste arrangement, cause I have no idea on how to accomplish that, working first time with Threads in WPF). But maybe u got now a better sight, of what I'm looking for..

You have to choose: use one background worker to load all the listviews or use multiple background workers, each filling a listview.
Currently you are trying to force the backgroundworker to start working on another job before the previous job has finished.
To fix this quickly: put all the code to load the listviews in the DoWork handler.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
// fill listview1
worker.ReportProgress(percentageDone);
// fill listview2
worker.ReportProgress(percentageDone);
// fill listview3
worker.ReportProgress(percentageDone);
}
To use multiple Backgroundworkers create multiple Backgroundworkers:
var bw1 = new BackgroundWorker();
bw1.DoWork += ...;
bw1.RunAsync(...);
var bw2 = new BackgroundWorker();
bw2.DoWork += ...;
bw2.RunAsync(...);
var bw3 = new BackgroundWorker();
bw3.DoWork += ...;
bw3.RunAsync(...);

This all depends on what "filling list view" actually means. If you're going off to a database to get data then that definitely should be on a background thread. However, if you already have the data and just need to populate the list view, then a BackgroundWorker won't help at all - it'll make matters worse. That's because you'll need to marshal back to the UI thread in order to access the ListView!
If you have a lot of data to add to a ListView and you want to keep the UI responsive throughout, you need to add the data in chunks in separate dispatcher messages. When a chunk has been added, queue another message to add the next chunk. This gives the dispatcher time to process other messages whilst the list is populating.

Related

Updating GUI multithreading application

I have main program
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Worker w1 = new Worker(1);
Worker w2 = new Worker(2);
Thread w1Thread = new Thread(new ThreadStart(w1.StartWorking));
Thread w2Thread = new Thread(new ThreadStart(w2.StartWorking));
w1Thread.Start();
w2Thread.Start();
Application.Run(new MainWindow());
if (w1Thread.IsAlive)
{
w1Thread.Abort();
}
if (w2Thread.IsAlive)
{
w2Thread.Abort();
}
}
}
and worker class:
class Worker
{
public int m_workerId;
public bool m_workerLifeBit;
public bool m_workerWork;
public Worker(int id)
{
m_workerId = id;
m_workerLifeBit = false;
}
public void StartWorking()
{
while (!m_workerWork)
{
m_workerLifeBit = false;
System.Threading.Thread.Sleep(5000);
m_workerLifeBit = true;
System.Threading.Thread.Sleep(5000);
}
}
}
I have checkBox on MainWindow form.
How to monitor state of Worker variable m_workerLifeBit and display its changes in MainWindow checkBox?
I have found this q&a How to update the GUI from another thread in C#? hovewer the answer does not show complete example, and I failed with using thread safe delegate.
I want some event mechanism that I fire in Worker.StartWorking and catch in slot in MainWindow form.
Here is a simple version using events:
class Worker
{
public event Action<bool> WorkerLifeBitChanged;
// ...
public void StartWorking()
{
// ...
m_workerLifeBit = false;
OnWorkerLifeBitChanged();
// ...
private void OnWorkerLifeBitChanged()
{
if (WorkerLifeBitChanged != null)
WorkerLifeBitChanged(m_workerLifeBit);
}
Then you wire up the event in Main:
//...
var mainWindow = new MainWindow();
w1.WorkerLifeBitChanged += mainWindow.UpdateWorkerLifeBit;
w2.WorkerLifeBitChanged += mainWindow.UpdateWorkerLifeBit;
w1Thread.Start();
w2Thread.Start();
Application.Run(mainWindow);
//...
And UpdateWorkerLifeBit implementation in MainWindow:
public void UpdateWorkerLifeBit(bool workerLifeBit)
{
if (this.checkBox.InvokeRequired)
{
this.Invoke(new Action(() => checkBox.Checked = workerLifeBit));
}
else
{
checkBox.Checked = workerLifeBit;
}
}
As mentioned in the comments, if this is a WinForms application then I'd recommend using a BackgroundWorker.
Kicking off the bg worker and subscribing to events:
BackgroundWorker worker = new BackgroundWorker();
// Subscribing to the worker method. Do all of your work here
worker.DoWork += worker_DoWork;
// Subscribing to the progress changed event where you'll want to update the UI
worker.ReportProgress = true;
worker.ProgressChanged += worker_ProgressChanged;
// Subscribing to the worker completed event. Fires when the work is complete
worker.RunWorkerCompleted += worker_RunWorkerCompleted;
// This line starts the worker
worker.RunWorkerAsync();
You would then have your methods defined as such:
void worker_DoWork(object sender, DoWorkEventArgs e)
{
// Perform some work with the object you've passed in e.g.
MyObj foo = (MyObj)e.Argument;
foo.Name = "foobar";
// Notify UI
worker.ReportProgress(100, foo);
}
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Update UI
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Worker has finished
}
One solution would be passing a reference of your Program class (or even a delegate in your program class, or a data reference in your worker class) to the Worker thread. You can call a function of your Program directly from the thread code then. You can also use signals, but for this small example my previous "solution" is acceptable.

using backgroundworker in Winforms (C#) with MVP pattern

I've been trying to refactor a spaghetti code of an app by using MVP pattern. But now I'm struggling with this:
A form that has button that calls a the DoWork method (of a backgroundworker) which is a long operation. My question is if I move the long operation out of the view into the Presenter then how do I send progress changes from this operation to the View? The BGW must be in the Presenter also?
Can you give me a sample of how to do this?
Thank you in advance.
This outlines the use of the BackgroundWorker:
private BackgroundWorker _backgroundWorker;
public void Setup( )
{
_backgroundWorker = new BackgroundWorker();
_backgroundWorker.WorkerReportsProgress = true;
_backgroundWorker.DoWork +=
new DoWorkEventHandler(BackgroundWorker_DoWork);
_backgroundWorker.ProgressChanged +=
new ProgressChangedEventHandler(BackgroundWorker_ProgressChanged);
_backgroundWorker.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(BackgroundWorker_RunWorkerCompleted);
// Start the BackgroundWorker
_backgroundWorker.RunWorkerAsync();
}
void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// This method runs in a background thread. Do not access the UI here!
while (work not done) {
// Do your background work here!
// Send messages to the UI:
_backgroundWorker.ReportProgress(percentage_done, user_state);
// You don't need to calculate the percentage number if you don't
// need it in BackgroundWorker_ProgressChanged.
}
// You can set e.Result = to some result;
}
void BackgroundWorker_ProgressChanged(object sender,
ProgressChangedEventArgs e)
{
// This method runs in the UI thread and receives messages from the backgroud thread.
// Report progress using the value e.ProgressPercentage and e.UserState
}
void BackgroundWorker_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e)
{
// This method runs in the UI thread.
// Work is finished! You can display the work done by using e.Result
}
UPDATE
This BackgroundWorker has to be in the presenter of cause. The idea of patterns like MVP, MVC or MVVM is to remove as much code from the view as possible. The view would only have code very specific to the view itself, like creating the view or drawing in the Paint event handler and so on. Another kind of code in the view is the code necessary to communicate with the presenter or controller. The presenting logic, however, has to be in the presenter.
You would use the BackgroundWorker_ProgressChanged method that runs in the UI thread to send changes to the view. Either by calling public methods of the view or by setting public properties of the view or by exposing public properties the view can attach to by binding its properties or the properties of its controls to it. (This is borrowed from the MVVM pattern.) The presenter must implement INotifyPropertyChanged in order to notify the view that a property has changed, if you decide to bind the view to properties of the presenter.
Note: Another thread than the UI thread is not allowed to interact with the view directly (an exception is thrown if you try to do so). Therefore the BackgroundWorker_DoWork cannot interact with the view directly and therefore calls ReportProgress, which in turn runs BackgroundWorker_ProgressChanged in the UI thread.
You can place the BackGroundWorker in the presenter and add a method to the view to show the progress.
Something like this:
//Add a method to your view interface to show progress if you need it.
public interface IView
{
void ShowProgress(int progressPercentage);
}
//Implement method in the view.
public class MyView : Form, IView
{
public MyView()
{
//Assume you have added a ProgressBar to the form in designer.
InitializeComponent();
}
public void ShowProgress(int progressPercentage)
{
//Make it thread safe.
if (progressBar1.InvokeRequired)
progressBar1.Invoke(new MethodInvoker(delegate { progressBar1.Value = progressPercentage; }));
else
progressBar1.Value = progressPercentage;
}
}
// In your presenter class create a BackgroundWorker and handle it's do work event and put your time consuming method there.
public class MyPresenter
{
private BackgroundWorker _bw;
public MyPresenter()
{
_bw = new BackgroundWorker();
_bw.WorkerReportsProgress = true;
_bw.DoWork += new DoWorkEventHandler(_bw_DoWork);
}
private void _bw_DoWork(object sender, DoWorkEventArgs e)
{
//Time consuming operation
while (!finished)
{
//Do the job
_bw.ReportProgress(jobProgressPercentage);
}
}
public void StartTimeConsumingJob()
{
_bw.RunWorkerAsync();
}
}
Don't forget to Dispose the BackgroundWorker when you're finished.
with your input I've managed to work this out. Please comment any flaws you may find with this approach:
* View interface *
public interface IView
{
void ShowProgress( int progressPercentage);
}
* View (a form) *
public partial class Form1 : Form, IView
{
MyPresenter p ;
public Form1()
{
InitializeComponent();
p = new MyPresenter(this);
}
private void button1_Click(object sender, EventArgs e)
{
if (p.IsBusy())
{
return;
}
p.StartTimeConsumingJob();
}
public void ShowProgress(int progressPercentage)
{
if (progressBar1.InvokeRequired)
progressBar1.Invoke(new MethodInvoker(delegate { progressBar1.Value = progressPercentage; }));
else
progressBar1.Value = progressPercentage;
}
private void button2_Click(object sender, EventArgs e)
{
p.Cancel();
}
}
* Presenter *
public class MyPresenter
{
private BackgroundWorker _bw;
private IView _view;
public MyPresenter(IView Iview)
{
_view = Iview;
_bw = new BackgroundWorker();
_bw.WorkerReportsProgress = true;
_bw.WorkerSupportsCancellation = true;
_bw.DoWork += new DoWorkEventHandler(_bw_DoWork);
_bw.ProgressChanged += new ProgressChangedEventHandler(_bw_ProgressChanged);
_bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(_bw_Completed);
}
public void StartTimeConsumingJob()
{
_bw.RunWorkerAsync();
}
private void _bw_DoWork(object sender, DoWorkEventArgs e)
{
//Time consuming operation Do the job
Thread.Sleep(1000);
_bw.ReportProgress(50);
Thread.Sleep(2000);
if(_bw.CancellationPending)
{
e.Result = false;
}
}
public bool IsBusy()
{
return _bw.IsBusy;
}
public void Cancel()
{
_bw.CancelAsync();
}
private void _bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
_view.ShowProgress(e.ProgressPercentage);
}
private void _bw_Completed(object sender, RunWorkerCompletedEventArgs e)
{
if((bool)e.Result)
_view.ShowProgress(100);
else
_view.ShowProgress(0);
_bw.Dispose();
}
}

Run method with BackgroundWorker on second Form

Let's imagine that we have two Forms: MainForm and WaitingForm. I want to pass, from MainForm, to the WaitingForm the method to run in background using BackgroundWorker.
Now, I'm doing things that way:
MainForm.cs:
public partial class MainForm: Form
{
private void btnImport_Click(object sender, EventArgs e)
{
var waitingFrm = new WaitingForm();
waitingFrm.DoWork = (o, args) => this.LongRunningOperation(this, new DoWorkEventArgs("foo bar"));
waitingFrm.OnWorkCompleted = (o, args) => MessageBox.Show("Finished!");
waitingFrm.Show();
waitingFrm.Run(); // should execute LongRunningOperation, method below.
}
private void LongRunningOperation(object sender, DoWorkEventArgs e)
{
MessageBox.Show("Running long operation!....");
// some long running stuff here;
}
}
WaitingForm.cs
public partial class WaitingForm: Form
{
private BackgroundWorker worker = new BackgroundWorker();
public DoWorkEventHandler DoWork { get; set; }
public RunWorkerCompletedEventHandler OnWorkCompleted { get; set; }
public WaitingForm()
{
this.worker.DoWork += DoWork;
this.worker.RunWorkerCompleted += OnWorkCompleted;
InitializeComponent();
}
public void Run()
{
this.worker.RunWorkerAsync();
}
}
But after waitingFrm.Run(); my LongRunningOperation is not executed.
In your WaitingForm I'd do:
public event DoWorkEventHandler DoWork {
add { worker.DoWork += value; }
remove { worker.DoWork += value; }
}
(Instead of the get;set; property).
And then in your main window btnImport_Click handler just:
waitingFrm.DoWork += LongRunnignOperation;
And the same for completed handler. Your syntax seems overly complicated. This is just a clean way to expose an event (in this case on your waitingform) and the pass event handler through to the real handler (in this case worker.DoWork). It is equivalent to
waitingFrm.worker.DoWork += LongRunnignOperation;
which would do just as well.
I want to pass, from MainForm, to the WaitingForm the method to run
in background using BackgroundWorker
I would in this case
declare an event in WaitingForm
before Form1 shows WaitingForm subscribes to that event
when long running operation has to be runned WaitingForm raise an event, Form1 gets it and
Form1 runs its method in other thread.
Hope this helps.
In this particular case you want all of the work to happen in MainForm and it looks like WaitingForm is just a display for the user. If that's the case then I would just put the BackgroundWorker in the MainForm and use the event to call into WaitingForm
public partial class MainForm: Form
{
private void btnImport_Click(object sender, EventArgs e) {
var waitingForm = new WaitingForm();
waitingForm.Show();
var worker = new BackgroundWorker();
worker.DoWork += (o, args) => this.LogRunningOperation(o, args);
worker.OnWorkComplete += (o, args) => {
waitingForm.Close();
worker.Dispose();
};
worker.RunWorkerAsync();
}
private void LongRunningOperation(object sender, DoWorkEventArgs e) {
MessageBox.Show("Running long operation!....");
// some long running stuff here;
}
}
So, the simple answer is that. Your code is not working because the mainform is not seeing the BackgroundWorker object instance events. Instead of doing:
this.worker.DoWork += DoWork;
this.worker.RunWorkerCompleted += OnWorkCompleted;
in WaitingForm - InitializeComponent(), do this instead in mainForm like this:
waitingFrm.worker.DoWork += waitingFrm.DoWork;
waitingFrm.worker.RunWorkerCompleted += waitingFrm.OnWorkCompleted;

BackgroundWorker track method from another class

[EDIT]
I edited my question with complete code and explanation and hope something can give me clearer explanation.
I have the following Class that has a backgroundworker to track the percentage progress of a loop and update the percentage progress on a Label on ProgressWin's XAML. The following code works perfectly. (My question is far below, after the code.)
public partial class ProgressWin : Window
{
BackgroundWorker backgroundWorker1 = new BackgroundWorker();
public ProgressWin()
{
InitializeComponent();
InitializeBackgroundWorker();
startAsync();
}
// Set up the BackgroundWorker object by
// attaching event handlers.
private void InitializeBackgroundWorker()
{
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.RunWorkerCompleted += new RunWorkerCompletedEventHandler(backgroundWorker1_RunWorkerCompleted);
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
}
private void startAsync()
{
backgroundWorker1.RunWorkerAsync();
}
public void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 1; i <= 10; i++)
{
System.Threading.Thread.Sleep(500);
worker.ReportProgress(i * 10);
}
}
// This event handler updates the progress.
public void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Content = (e.ProgressPercentage.ToString() + "%");
}
// This event handler deals with the results of the background operation.
public void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
resultLabel.Content = "Done!";
}
}
Here comes my problem, now instead of tracking the loop within the ProgressWin, I need to track the loop in my MainWindow:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
for (int i = 1; i <= 10; i++)
{
System.Threading.Thread.Sleep(500);
????.ReportProgress(i * 10);
}
}
}
And I have no idea where to go from here. I tried instantiating an object from ProgressWin and call the DoWork but I end up frozen the ProgressWin window.
Due to the fact that the question was rewritten, i also rewrote my whole answer.
To get this to work your MainWindowhas a ProgressWindow and should use it like a background worker:
public partial class MainWindow : Window
{
private ProgressWindow _Progress;
public MainWindow()
{
InitializeComponent();
_Progress = new ProgressWindow();
_Progress.ProgressChanged += OnProgressChanged;
}
private void OnProgressChanged(object sender, ProjectChangedEventArgs e)
{
//ToDo: Update whatever you like in your MainWindow
}
}
To accomplish this your ProgressWindow should simply subscribe to the worker event and rethrow it:
public partial class ProgressWin : Window
{
// Add this to your class above in your question
public event ProgressChangedEventHandler ProgressChanged;
// Change or merge this with your existing function
private void backgroundWorker1_ProgressChanged(object sender, ProjectChangedEventArgs e)
{
var temp = ProgressChanged;
if(temp !=null)
temp(this, e);
}
}
You can simply call method of another class by doing
backgroudnWorker.DoWork += new DoWorkEventHandler(SomeClass.SomeStaticMethod);
or
backgroudnWorker.DoWork += new DoWorkEventHandler(SomeClassInstance.SomeMethod);
for calling a method of MainWindow class from another class ProgressScreen you should have reference of instance of MainWindow class in ProgressScreen then using that instance you can call any public method of MainWindow from ProgressScreen class
and as Oliver Said, you will need the instance of backgroundworker to send the progress updates from other method.

Data Binding on multi thread application?

The following code is the simplified version of my problem:
public partial class Form1 : Form
{
BackgroundWorker bw;
Class myClass = new Class();
public Form1()
{
InitializeComponent();
bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
label1.DataBindings.Add("Text", myClass, "Text", true, DataSourceUpdateMode.Never);
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
myClass.Text = "Hi";
}
private void button1_Click(object sender, EventArgs e)
{
bw.RunWorkerAsync();
}
}
public class Class : INotifyPropertyChanged
{
private string _Text;
private void SetText(string value)
{
if (_Text != value)
{
_Text = value;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public string Text
{
get { return _Text; }
set { SetText(value); OnPropertyChanged(new PropertyChangedEventArgs("Text")); }
}
protected void OnPropertyChanged(PropertyChangedEventArgs e)
{
if (PropertyChanged != null) PropertyChanged(this, e);
}
}
What happens is that when I click the button1 (which invokes button1_Click) the text in label1 doesn't update. This is because the label1.Text property is internally trying to be changed from my BackgroundWorker's thread, which internally causes an exception. What would it be the best way, in general, to fix this kind of problem? Which part of this code would you change if you must update my Class.Text property from a different thread but must also have a control binded to it?
Try this:
//This is the handler to execute the thread.
void DoWork(object sender, EventArgs a) {
Action updateControl = ()=>myClass.Text = "Blah";
if(myForm.InvokeRequired) {
myForm.Invoke( updateControl);
}
else {updateControl();}
}
this routine executes on the background worker thread.
Fundamentally, you'll have to use either Invoke or BeginInvoke from within your worker thread to perform the actual update. It's illegal to interact directly with a control from within a thread other than the main UI thread (unless it's been written to account for this). With lambda syntax, using Invoke or BeginInvoke is somewhat cleaner than before. For example:
void bw_DoWork(object sender, DoWorkEventArgs e)
{
Invoke(new Action(() => myClass.Text = "Hi"));
}
This will execute the code on the UI thread. However, the BackgroundWorker class (which I'm assuming is what you're using, based on the names here) has a ReportProgress function, which raises the ProgressChanged event on the UI thread. It uses the same mechanism that I described above, but it might be a little cleaner looking. The choice is yours really.
Only the UI thread is allowed to change properties of UI controls. Your BackgroundWorker shouldn't be doing it.
Fortunately BackgroundWorker supports a ProgressChanged event. Your DoWork should raise that, and THAT can update the UI because the backgroundworker will marshal it back to the UI thread for you.

Categories