Update GUI using BackgroundWorker - wpf

I've been searching and found that a good way to perform background work and update the GUI is using background workers. However, doing this (stupid) little task (counting from 1 to 10000) it doesn't update the label content but prints to the debug! (This is just a spike solution for another project of course...)
Here's the code:
public partial class MainWindow : Window
{
BackgroundWorker bw = new BackgroundWorker();
public MainWindow()
{
InitializeComponent();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.WorkerReportsProgress = true;
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
bw.RunWorkerAsync();
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
MessageBox.Show("DONE");
}
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
label1.Content = "going here: "+e.ProgressPercentage;
Debug.WriteLine(e.ProgressPercentage);
}
void bw_DoWork(object sender, DoWorkEventArgs e)
{
for (int i=0; i < 10000; i++)
{
bw.ReportProgress((i*100)/10000);
}
}
}

The ProgressChanged event is raised on the UI thread, not the worker thread. In your code, the worker thread is doing almost nothing (just loop from 0 to 10000 and call ReportProgress), most of the work is done on the UI thread. Basically, you're sending too many progress notifications. Because of this, the UI thread is almost always busy and has no time to render the new content of the label.
Rendering in WPF is not performed immediately when you change a property of a control, it is done on a separate dispatcher frame, which is processed when the dispatcher has nothing more urgent to do, based on the priority of the task. The priority used for rendering has a value of 7 (DispatcherPriority.Render); the ProgressChanged event is marshalled to the UI thread with a priority of 9 (DispatcherPriority.Normal), as specified on MSDN. So the ProgressChanged notifications always have a higher priority than rendering, and since they keep coming, the dispatcher never has time to process the rendering tasks.
If you just decrease the frequency of the notifications, your app should work fine (currently you're sending 100 notifications for each percentage value, which is useless):
void bw_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 10000; i++)
{
if (i % 100 == 0)
bw.ReportProgress(i / 100);
}
}

this.Dispatcher.BeginInvoke( (Action) delegate(){
label1.Content = "going here: "+e.ProgressPercentage;
});

Try to change the label using womething like this:
string Text = "going here: " + e.ProgressPercentage;
this.Invoke((MethodInvoker)delegate {
label1.Content = newText;
});
Note that i'm not sure it will work. I can not test it now. If it does not work, let me know and I will delete the answer.
If you need the a canonical way to do exactly what you want, look at the Hath answer in this post: How do I update the GUI from another thread?

Related

Working with ProgressBar and ComboBox

I'm in trouble with a Marquee ProgressBar. I need to execute a method (refreshList()) to get a List<string>. Then I assign this List to a ComboBox, so ComboBox refreshes with the new Items. As refreshList() take 3 or 4 sec, I wanted to run a Marquee ProgressBar. But I couldn't. ProgressBar is ok, but ComboBox doesn't load new Items.
My refreshList() method:
private void refreshList(List<string> list)
{
albumList.DataSource = null;
albumList.DataSource = list;
}
I have the following code, it works fine:
private void changeDirectoryToolStripMenuItem_Click(object sender, EventArgs e)
{
fbd.RootFolder = Environment.SpecialFolder.MyComputer;
folderPath = "";
if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
folderPath = fbd.SelectedPath;
refreshList(N.getList(folderPath));
}
}
But I added a ProgressBar and wrote this code:
private void changeDirectoryToolStripMenuItem_Click(object sender, EventArgs e)
{
fbd.RootFolder = Environment.SpecialFolder.MyComputer;
folderPath = "";
if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
folderPath = fbd.SelectedPath;
bgWorker.WorkerReportsProgress = true;
bgWorker.RunWorkerAsync();
}
}
And I placed refreshList() in doWork() method:
private void bgWorker_DoWork(object sender, DoWorkEventArgs e)
{
refreshList(N.getList(folderPath));
}
But unfortunately this isn't working. Can anybody help me solving this problem? Thanks in advance.
You can use the MarqueeAnimationSpeed and Value properties of the ProgressBar control to stop and start the Marquee. There's no need to use WorkerReportsProgress* as you aren't incrementing a normal progress bar - you just want to "spin" the Marquee.
You can do something like the following:
public Form1()
{
InitializeComponent();
//Stop the progress bar to begin with
progressBar1.MarqueeAnimationSpeed = 0;
//If you wire up the event handler in the Designer, then you don't need
//the following line of code (the designer adds it to InitializeComponent)
//backgroundWorker1.RunWorkerCompleted += backgroundWorker1_RunWorkerCompleted;
}
private void changeDirectoryToolStripMenuItem_Click(object sender, EventArgs e)
{
fbd.RootFolder = Environment.SpecialFolder.MyComputer;
folderPath = "";
if (fbd.ShowDialog() == System.Windows.Forms.DialogResult.OK)
{
folderPath = fbd.SelectedPath;
//This line effectively starts the progress bar
progressBar1.MarqueeAnimationSpeed = 10;
bgWorker.RunWorkerAsync(); //Calls the DoWork event
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
e.Result = N.getList(folderPath); //Technically this is the only work you need to do in the background
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//these two lines effectively stop the progress bar
progressBar1.Value = 0;
progressBar1.MarqueeAnimationSpeed = 0;
//Now update the list with the result from the work done on the background thread
RefreshList(e.Result as List<String>);
}
private void RefreshList(List<String> results)
{
albumList.DataSource = null; //You don't need this line but there is no real harm.
albumList.DataSource = list;
}
Remember to wire up the RunWorkerCompleted event to backgroundWorker1_RunWorkerCompleted via the Properties bar, Events section in the designer.
To begin with, we start the ProgressBar's animation by setting the MarqueeAnimationSpeed property to a non-zero positive number as part of your successful folder selection.
Then, after calling RunWorkerAsync, the code builds your list in the DoWork method, then assigns the result to the DoWorkEventArgs, which get passed to the RunWorkerCompleted event (which fires when DoWork is finished).
In the backgroundWorker1_RunWorkerCompleted method, we stop the progress bar (and set it's value to zero to effectively return it to it's original state), and then we pass the list to the refreshList method to databind it and populate the ComboBox.
Tested using VS2012, Windows Forms, .Net 4.0 (with a Thread.Sleep to emulate the time taken for N.getList)
*WorkerReportsProgress, and the associated ReportProgress method/event are used when you want to increment the progress bar - you can tell the GUI that you are 10% done, 20% done, 50% done etc etc.

wpf working state

I have a grid in my application. After user selects some files in ofdialog application processes some calculations. While app is making calculations it looks like it is not responding. How to display some picture and make main window in black&white while calculating? Maybe make some dp in MainWindow a la "IsBusy" and bind a popup with picture to it?
How you implement this logic in yours apps?
One easy way is to use the busy indicator from Extended WPF Toolkit:
Dowload the binaries and add project reference to WPFToolkit.Extended.dll.
Next add following namespace in your 'main window':
xmlns:ext="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit.Extended"
Then add the busy indicator in the view (place it so that when shown, it will occupy the whole screen) Here my main window has two rows and I want the control to span on both rows. The control's IsBusy property is bound to a bool property in the view's data context.
<ext:BusyIndicator Grid.RowSpan="2" x:Name="busyIndicator" IsBusy="{Binding IsBusy}" />
The long lasting calculation should be processed in another thread so that it won't block the user interface. For threading you can use BackgroundWorker class.
You should have the long running tasks in a seperate thread to avoid UI blocking.
Here's one way you could achieve that:
Define background thread as below:
//Delegate that you could pass into the worker thread
public delegate void ProgressMonitor(string s);
//Call this to start background work
void StartLongRunningWork(ProgressMonitor mon)
{
using (BackgroundWorker bgw = new BackgroundWorker())
{
bgw.DoWork += WorkerThread;
bgw.RunWorkerCompleted += WorkerThreadCompleted;
bgw.RunWorkerAsync(mon);
}
}
void WorkerThread(object sender, DoWorkEventArgs e)
{
ProgressMonitor pm = (ProgressMonitor)e.Argument;
WorkerActual(pm, <any other parameters>);
}
void WorkerActual(ProgressMonitor pm,<any other parameters>)
{
...
pm("Doing x");
Do long running task
pm("Doing y");
...
}
//This function is called in case of Exception, Cancellation or successful completion
//of the background worker. Handle each event appropriately
void WorkerThreadCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Error != null)
{
//Long running task threw an exception
}
else
if (e.Cancelled)
{
//Long running task was cancelled
}
else
{
//Long running task was successfuly completed
}
}
And Call it as below:
private void UpDateProgressLabel(string s)
{
this.Dispatcher.BeginInvoke((Action)delegate
{
NotificationLabel.Content = s;
});
}
private void Button_Click(object sender, RoutedEventArgs e)
{
StartLongRunningWork(UpDateProgressLabel);
}

WPF : I got problem in draw something on canvas using thread

I try to draw something on canvas every 1 second .Then, I create a thread to call a method to draw after sleep 1 second. The problem is calling Canvas object to draw. The program tell me error message
The calling thread cannot access this object because a different thread owns it.
Code
private void drawTimeTick() {
...
Thread iThread = new Thread(new ThreadStart(tickThread));
iThread.Start();
}
private void tickThread(){
try
{
Thread.Sleep(1000);
...
Canvas.SetLeft(tick, 700);
Canvas.SetTop(tick, 30);
}catch(Exception ex){
MessageBox.Show("Exception tickThread : "+ex.Message);
}
}
How to access Canvas in Thread to draw?
Have a look at the DispatcherTimer class. It is designed for exactly this scenario, as can be seen in the MSDN examples.
You can call
this.Dispatcher.Invoke((Action)(() =>
{
Canvas.SetLeft(tick, 700);
Canvas.SetTop(tick, 30);
}));
(I'm assuming that this is some UI control. If not you can use Application.Current.Dispatcher instead). It will execute your canvas operations on the UI thread.
I have try DispatcherTimer, It's work.
DispatcherTimer timer = new DispatcherTimer();
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += tickThread;
timer.Start();
}
private void tickThread(object sender, EventArgs e)
{
try
{
tick.thisTime = new DateTime(DateTime.Now.Year, DateTime.Now.Month, DateTime.Now.Day, DateTime.Now.Hour, DateTime.Now.Minute, DateTime.Now.Second);
double TickPixel = GetPixel(ClassTimeline.Width, startTime, endTime, tick.thisTime);
Canvas.SetLeft(tick, TickPixel);
Canvas.SetTop(tick, 30);
}catch(Exception ex){
MessageBox.Show("Exception tickThread : "+ex.Message);
}
}
You can't access graphical objects from a different thread from the ones that created it.

Winforms StatusStrip - why are there periods where it is blank when I'm updating it?

BACKGROUND: I have a WindowForms v3.5 application with a StatusStrip set to be used as a TooStripStatusLabel. I'm issues quite a lot of updates to it during a task that is running, however there are noticable periods where it is BLANK. There are no points when I am writing a blank to the status strip label either.
QUESTION: Any ideas why I would be seeing period where the status strip label is blank, when I don't expect it to be?
How I update it:
private void UpdateStatusStrip(string text)
{
toolStripStatusLabel1.Text = text;
toolStripStatusLabel1.Invalidate();
this.Update();
}
PS. Calling Application.DoEvents() after the this.Update() does not seem to help. I actually am calling this via the backgroundworker control, so:
(a) I start up the background worker:
private void Sync_Button_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
DisableUpdateButtons();
}
(b) the background worker calls updates:
private void backgroundWorker1_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
backgroundWorker1.ReportProgress(1, "Example string");
MainForm.MyC.SyncFiles(sender);
}
(c) The MyC business class uses it too, e.g.
public void SyncFiles(object sender)
{
BackgroundWorker bgw = (System.ComponentModel.BackgroundWorker) sender;
bgw.ReportProgress(1, "Starting sync...");
.
.
.
}
(d) This event picks it up:
private void backgroundWorker1_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
UpdateStatusStrip((string)e.UserState);
}
(e) And again the update status strip
private void UpdateStatusStrip(string text)
{
toolStripStatusLabel1.Text = text;
toolStripStatusLabel1.Invalidate();
this.Update();
}
Does this help?
The reason is possibly in the caller of this function. If you call it from another thread, use Control.BeginInvoke instead of direct call. If you call it from the main application thread during long processing, try Application.DoEvents after UpdateStatusStrip call.

2 Issues with BackgroundWorker component

Firstly, I know I should be using proper Threading techniques (Threadpool, BeginInvoke, etc.) to accomplish this, but thats a bit over my head currently and will call for some time to read over material and understand it (if you have any URL references for my scenario, please feel free to post it).
In the interim I am using the backgroundWorker to pull a very large resultset and populate a DatagridView with it. I successfully create a SortableBindingList<TEntities> in my DoWork event and pass that out in the result. And in the RunWorkerCompleted event, I cast and bind that SortableBindingList<TEntities> to my Grid. My 2 main areas of concern are as follows:
1) Access to private variables.
I want to pass one of two parameters List<long> into my DoWork event, but run a different query depending on which list was passed to it. I can get around this by declaring a class-level private boolean variable that acts a flag of sorts. This seems silly to ask, but in my DoWork, am I allowed to access that private variable and route the query accordingly? (I've tested this and it does work, without any errors popping up)
private bool SearchEngaged = false;
private void bgw_DoWork(object sender, DoWorkEventArgs e) {
BackgroundWorker worker = sender as BackgroundWorker;
e.Result = GetTasks((List<long>)e.Argument, worker, e);
}
SortableBindingList<Task> GetTasks(List<long> argsList, BackgroundWorker worker, DoWorkEventArgs e) {
SortableBindingList<Task> sbl = null;
if (worker.CancellationPending) {
e.Cancel = true;
}
else {
if (SearchEngaged) {
sbl = DU.GetTasksByKeys(argsList);
}
else {
sbl = DU.GetTasksByDivision(argsList);
}
}
return sbl;
}
2) UI Thread freezes on beginning of RunWorkerCompleted.
Ok, I know that my UI is responsive during the DoWork event, 'cos it takes +/- 2seconds to run and return my SortableBindingList<Task> if I don't bind the List to the Grid, but merely populate it. However my UI freezes when I bind that to the Grid, which I am doing in the RunWorkerCompleted event. Keep in mind that my Grid has 4 image columns which I handle in CellFormatting. This process takes an additional 8 seconds to accomplish, during which, my UI is completely non-interactive. Im aware of the cross-thread implications of doing so, but is there any way I can accomplish the Grid population and formatting either in the background or without causing my UI to freeze? RunWorkeCompleted looks like so:
private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
if (e.Cancelled) {
lblStatus.Text = "Operation was cancelled";
}
else if (e.Error != null) {
lblStatus.Text = string.Format("Error: {0}", e.Error.Message);
}
else {
SortableBindingList<Task> sblResult = (SortableBindingList<Task>)e.Result;
dgv.DataSource = sblResult;
dgv.Enabled = true;
TimeSpan Duration = DateTime.Now.TimeOfDay - DurationStart;
lblStatus.Text = string.Format("Displaying {0} {1}", sblResult.Count, "Tasks");
lblDuration.Visible = true;
lblDuration.Text = string.Format("(data retrieved in {0} seconds)", Math.Round(Duration.TotalSeconds, 2));
cmdAsyncCancel.Visible = false;
tmrProgressUpdate.Stop();
tmrProgressUpdate.Enabled = false;
pbStatus.Visible = false;
}
}
Sorry for the lengthy query, but I will truly appreciate your responses! thank you!
Your code appears to be doing exactly the right thing.
As for the 8 seconds that it takes for the UI thread to update the screen, there's not much you can do about that. See my answer to this question.
To optimise the UI part, you could try calling SuspendLayout and ResumeLayout on the grid or its containing panel.
You could also look at trying to reduce the amount of processing that is done during the data binding. For example:
Calculations done in the grid could be moved into the data model (thereby doing them in the worker thread).
If the grid auto-calculates its columns based on the data model, then try hard-coding them instead.
EDIT: Page the data in the Business Layer and make the grid only show a small number of rows at a time.
I think the easiest solution for your problem is setting the datasource of your grid in DoWork instead of RunWorkerCompleted using Dispatcher.BeginInvoke which you have mentioned yourself. Something like this:
private bool SearchEngaged = false;
private void bgw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
SortableBindingList<Task> sblResult = GetTasks((List<long>)e.Argument, worker, e);
BeginInvoke((Action<object>)(o => dataGridView1.DataSource = o), sblResult);
}
private void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled) {
lblStatus.Text = "Operation was cancelled";
}
else if (e.Error != null) {
lblStatus.Text = string.Format("Error: {0}", e.Error.Message);
}
else
{
dgv.Enabled = true;
TimeSpan Duration = DateTime.Now.TimeOfDay - DurationStart;
lblStatus.Text = string.Format("Displaying {0} {1}", sblResult.Count, "Tasks");
lblDuration.Visible = true;
lblDuration.Text = string.Format("(data retrieved in {0} seconds)", Math.Round(Duration.TotalSeconds, 2));
cmdAsyncCancel.Visible = false;
tmrProgressUpdate.Stop();
tmrProgressUpdate.Enabled = false;
pbStatus.Visible = false;
}
}
As far as the private variable issue is concerned, I don't think it will be of any problem in your case. In case you are changing it using some UI event, just mark the private field as volatile. The documentation of the volatile keyword can be found here:
http://msdn.microsoft.com/en-us/library/x13ttww7.aspx

Resources