In some games, there is a splash screen that downloads content from a server. It might give some tips while you're waiting. I'm doing something similar, only that the loading happens really quick, but I want it to wait a few more seconds.
When the user first loads my application, it has a screen with a progress bar. At the moment, it checks if the server is online. If it is, it says "Connected!" However, it immediately fades out my controls. I want it to wait about 5 more seconds so the reader can read it. Then fade the controls out.
private void frmMain_Loaded(object sender, RoutedEventArgs e)
{
// Start background worker
m_bgWorker = new System.ComponentModel.BackgroundWorker();
m_bgWorker.ProgressChanged += M_bgWorker_ProgressChanged;
m_bgWorker.WorkerReportsProgress = true;
m_bgWorker.RunWorkerAsync();
m_bgWorker.ReportProgress(100);
}
private void M_bgWorker_ProgressChanged(object sender, System.ComponentModel.ProgressChangedEventArgs e)
{
progConnect.Value = 0;
if (SystemBO.IsOnline())
{
lblConnection.Content = "Connected!";
}
progConnect.Value = e.ProgressPercentage;
// TODO: Wait 5 seconds here...
// Fade out controls
lblTitle.BeginAnimation(Label.OpacityProperty, doubleAnimation);
progConnect.BeginAnimation(Label.OpacityProperty, doubleAnimation);
lblConnection.BeginAnimation(Label.OpacityProperty, doubleAnimation);
}
Note:
I tried System.Sleep(), but that made no difference. I understand why. The idea is the same though: I want the background worker to sleep for 5 seconds before completing.
Solution:
I added a few more events: DoWork and RunWorkerCompleted.
Then I added this code:
private void M_bgWorker_DoWork(object sender, System.ComponentModel.DoWorkEventArgs e)
{
for (int i = 0; i < 100; i++)
{
m_index++;
System.Threading.Thread.Sleep(40);
m_bgWorker.ReportProgress(m_index);
}
System.Threading.Thread.Sleep(3000); // wait 3 seconds to read
}
It worked. The progress bar animates quite smoothly.
You shouldn't do the fade-out in the ProgressChanged handler. That handler really should only be used to update progress information like text and a percentage bar.
Your fade-out logic should be moved to the separate RunWorkedCompleted event handler. Here you can let the thread sleep/await a scheduled task for a while before beginning to fade out the controls. (Using async/await over Thread.Sleep() here has the advantage of not blocking the UI thread while waiting, so the UI still remains responsive while waiting for the time to pass.)
It also ensures that the code is called at the very end and not every time the worker reports any kind of progress. And it's also cleaner because it allows you to handle different termination states by checking the RunWorkedCompletedEventArgs:
If e.Error is set, an exception occurred in the worker thread.
If e.Cancelled is true, the worker thread got cancelled by calling CancelAsync() on the worker. (Only possible if WorkerSupportsCancellation is set to true and only useful if code in the DoWork handler actually checks for the cancellation flag)
Otherwise everything went okay.
Side note:
I am assuming that your code is not a full example, because you have no actual handler assigned to the DoWork event. So right now it would not do anything. And calling ReportProgress() right from the main thread is wrong as well. That method is designed to be called from within the DoWork event handling method in order to let the asynchronous thread report status updates back to the UI, since those events are handled on the main thread.
rather than the last line in frmMain_Loaded:
m_bgWorker.ReportProgress(100);
I would put a for loop with Sleep(), for simulating some extra processing:
for (int i=0; i<=100; i+=10)
{
if (SystemBO.IsOnline())
i = 100;
Sleep(1000);
m_bgWorker.ReportProgress(i);
}
Related
I would like to do the following:
Have a button and a table on a GUI.
When I press the button, a task is started
This task is a while loop, giving me data on each iteration
How can I run this loop and obtain data from each iteration of it in the main GUI table, without blocking the GUI? This is important, because the while stop condition is again a button on the GUI.
I have tried using a BackgroundWorker, but I really cannot figure out how to send back data at every loop iteration (???) I can get back the result at the end, but that is not the target. If I launch the worker in a loop (but not have the loop in the worker), it does not work.
private void ContinuousCoordinateAquisition(object sender, DoWorkEventArgs e)
{
while (continuousPositionAquisitionFlag == true) // while the monitoring is not stopped, get positions
{
// get xyzwpr world coordinates
robotCoordinatesXYZWPRworld XYZWPRworld = robi.getRobotPosition_xyzwpr_world();
Do something........... retuns values I need in GUI
// sleep for defined time
System.Threading.Thread.Sleep(1000); // wait
}
}
the calling would be
BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler(ContinuousCoordinateAquisition);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(ContinuousCoordinateAquisitionCompleted);
continuousPositionAquisitionFlag is set from a button (stop button).
ContinuousCoordinateAquisitionCompleted here is only done once unfortunately, not every iteration.
You're on the right track. You should use the background worker, but don't wait for the RunWorkerCompletedEventHandler, that happens when everything is done.
Instead, inside your loop periodically call the ReportProgress method on your background worker. That will trigger the ProgressChanged event that you can handle in your GUI thread.
See here: https://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.progresschanged(v=vs.110).aspx
Try setting the BackgroundWorkClass WorkerReportsProgress property and handle the ProgressChanged event, as described by this article:
https://msdn.microsoft.com/en-us/library/cc221403%28v=vs.95%29.aspx
I have a Console app that displays a WinForms Form.
In the Form, the user clicks button 1 and it runs a long subroutine. I want to have a button 2 that can kill the subroutine at any point. However, the UI freezes when I click button 1 until the subroutine has finished. How can I get the UI to not freeze?
Your long-running code is blocking the UI thread, so you can no longer click the second button, nor interact with the UI in any way until the code is finished executing.
You'll need to move your long-running code to a separate thread. There are various (and newer) ways of doing this, but one way is the BackgroundWorker. It's pretty easy to learn, and wraps some nice functionality, like cancelling the thread.
Here's a short WinForms app to demonstrate. You have to explicitly enable the ability for the thread to be cancelled. In this example, the while loop continues indefinitely, but checks every 100ms to see if there's a request for it to be cancelled. When you click the second button, the cancellation request is sent, and the thread ends.
public partial class Form1 : Form
{
private BackgroundWorker bg;
public Form1()
{
InitializeComponent();
bg = new BackgroundWorker
{
WorkerSupportsCancellation = true
};
bg.DoWork += (sender, args) =>
{
while (true)
{
Thread.Sleep(100);
if (bg.CancellationPending)
break;
}
MessageBox.Show("Done!");
};
}
private void button1_Click(object sender, EventArgs e)
{
bg.RunWorkerAsync();
}
private void button2_Click(object sender, EventArgs e)
{
bg.CancelAsync();
}
}
Following up on chouaib's comment, another nice thing about using the BackgroundWorker in a WinForms environment is that you can drop and drop it onto your designer, similar to a Menu, Timer, etc. You can then access its members in the "properties" panel, setting "WorkerSupportsCancellation" to true, subscribing to events, etc.
From your comment:
"is there a way to run this background process and be able to update the main userform? I keep getting the "cross-thread operation not valid control accessed from a thread other than the..." I want to run the long-running background operation, and have it update the main UI with text in a label (like a percentage of its progress)"
If you want to update the UI while the thread is running, you should do that from the ProgressChanged event. First, enable that option and subscribe to the event:
bg.WorkerReportsProgress = true;
bg.ProgressChanged += bg_ProgressChanged;
Then call ReportProgress() when you want to update the UI. You could pass back a percentage complete and some text, for instance:
bg.ReportProgress(50, "Almost there...");
Finally, update the UI from inside the event:
void bg_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
var message = e.UserState.ToString();
var percent = e.ProgressPercentage;
lblStatus.Text = message + " " + percent;
}
You need to make it multithreaded as suggested in the comments. The older way of doing this was manage your own thread. Then along came the background worker (cheap and easy). Now a days you have other options such as the Task Library.
Remember - anything the runs on the UI thread prevents the UI from sending and receiving events until that operation is finished.
Look into the BackgroundWorker component
I am facing an issue with progress bar in background worker.
public void UpdateDevices(object sender, EventArgs e) {
bw_1.RunWorkerAsync();
Thread.Sleep(100);
WebSwitchHandler.GetDeviceStats(NewRoom);
//Above line opnes a socket and takes 5 seconds to return back//
}
private void bw_1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= 100; i++)
{
Thread.Sleep(100);
bw_1.ReportProgress(i);
}
}
private void bw_1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
The progress bar updates only after the socket has returned. The socket class has no threads, it blocks the entire gui for the 5 seconds.
Why doesnt this work? and suggestions how I should handle this with GUI.
The problem is because the progress bar is being updated by the UI thread. But the UI thread is "busy" waiting for the GetDeviceStats function to complete. So it cannot handle the progres bar update until it becomes free again.
If the GetDeviceStats function is a long running process (which to me it is) then you would be best including that work within you background worker thread. However, you still wont be able to report the progress (and therefore update the progress bar) until the call has once again finished.
Some possible solutions to that would be to either have GetDeviceStats report progress as it completes (I am not sure what that function actually does, so this may not be an option), OR if you need to "fake" the progress then use a second background thread or a timer.
Personally, I would just set the progress bar style to "Marquee" so the user knows the program is busy doing something, and then run the GetDeviceStats function on a single background worker.
Actually, You need two background worker to achieve the GUI working for you with progress bar. one you already have to update the progress bar.
Second one you can use to call the WebSwitchHandler.GetDeviceStats(NewRoom); as async operation so that your UI thread doesn't get blocked. On completed of this BG worker you can notify the first one to finish immediately rather waiting for a fixed time always. This way you can also optimize your wait time.
I am attempting to add a progress bar (MARQUEE style) in my c# excel add in. The progress bar appears but it does not indicate any progress until the function is finished executing, meaning that it remains frozen.
These are the functions in the Windows Form Class:
public void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i < 100; i++)
{
Thread.Sleep(100);
//run in back thread
backgroundWorker1.ReportProgress(i);
}
}
//call back method
public void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
//call back method
public void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressBar1.Value = progressBar1.Maximum;
}
This is how I call the function from the add in button:
private void buttonClicked(object sender, RibbonControlEventArgs e)
{
AddInPlanView.Form1 pBar = new AddInPlanView.Form1();
pBar.Visible = true;
pBar.backgroundWorker1.WorkerReportsProgress = true;
pBar.backgroundWorker1.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(pBar.backgroundWorker1_ProgressChanged);
pBar.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(pBar.backgroundWorker1_DoWork);
pBar.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(pBar.backgroundWorker1_RunWorkerCompleted);
pBar.backgroundWorker1.RunWorkerAsync();
FUNCTIONTORUN();
pBar.Visible = false;
}
My code now attempts to put the progress bar on a background worker. I know I should switch this so that FUNCTIONTORUN() should be on a background worker, but how do I go about doing that? Since it is a marquee progress bar I do not need to update the value of the progress bar at all, it just needs to run until FUNCTIONTORUN() ends. Note that FUNCTIONTORUN() updates the cells of an Excel worksheet.
The progress bar is initialized using the Visual Studio designer. The name of the ProgressBar object in the form class is progressbar1.
to start a marquee style progress bar, type
progressbar1.Style = ProgressBarStyle.Marquee;
progressbar1.MarqueeAnimationSpeed = 100;
to stop the marquee type
progressbar1.MarqueeAnimationSpeed= 0;
Source: http://social.msdn.microsoft.com/forums/en-US/winforms/thread/9e51ad57-988e-4c17-98f0-cd2a8abec503/
FUNCTIONTORUN(); // make your UI thread busy.
System.Windows.Forms.Application.DoEvents(); // might help in this case.
In most of this sort of cases DoEvent doesn't help (according to my experience). So, it's quite difficult to get process bar to work smoothly.
When the Background worker completes just stop the progress bar.
Inside of this function pBar.backgroundWorker1_RunWorkerCompleted just call the Stop function for the progress bar.
I believe you have to put FUNCTIONTORUN() into sepratae thread, and still it's execution call
ReportProgress method, in order to raise backgroundWorker1_ProgressChanged where you can update your porgress bar value. I suppose, as I don't see it in the code provided, you don't call ReportProgress function somewhere in the code.
EDITED: saw ReportProgress function.
So move your function into separate thread and update the value of progress bar into the main thread.
Hope this helps.
Regards.
Your FUNCTIONTORUN() blocks the UI thread.
Spread some System.Windows.Forms.Application.DoEvents() inside your long-running functions to process events (so the UI updates).
This, as mentioned, will update the UI and let the application respond to other events.
You might want to disable the button that triggers this long-running function, or other long-running functions.
You could create an additional background worker with a DoWork that contains the call to your FUNCTIONTORUN() inside of it and a RunWorkerCompleted that makes your bar stop or go invisible. Then, instead of calling FUNCTIONTORUN(), call backGroundWorker2.RunWorkerAsync();
This should prevent the function from freezing your UI. I ran into this issue last summer doing nearly the exact same thing, and fixed it using backgroundworkers.
I am stuck and was hoping someone could help me.
I have made a class/gui with a loading bar set to marquee so that when a task is being carried out i could display it to the user.
In one of my gui classes, in the constructor on the first line i am making a new instance of this class and then doing
LoadingBar bar = new LoadingBar();
Thread thread = new Thread(bar.Show);
thread.Start();
However, even tho the main programme thread is going off doing some more intensive stuff, this gui still seems to freeze, even if i use backround worker.
Is there anything wrong with the approach i have mentioned and if so what do i need to change?
Thanks
You need to reverse your method. The GUI needs to stay in the main thread while the work is done in a "worker thread" (typically a BackGroundWorker). Then the worker reports back to the GUI which then updates.
You'd better do the opposite. Make your intensive work in the thread (or a background worker), and show the wait screen in the main application thread.
You need to use a BackgroundWorker. Drag one onto your form, click backgroundWorker1 and set the WorkerReportsProgress property to True
Then goto the events (via the properties window) and attach handlers for
DoWork, this is where all the work that is represented by the progress bar. You will "report progress" via this and the background worker will make sure ProgressChanged is called on the UI thread.
ProgressChanged, this is where you update the UI based on progress and state data reported to the method
DoWork event looks something like this
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
var userState = new StateClass();
while (working)
{
// TODO: do work here
// update the state surrounding this task via userState
userState.property = "some status";
// report the progress so that backgroundWorker1_ProgressChanged gets called
this.backgroundWorker1.ReportProgress(percentComplete, userState);
}
}
ProgressChanged event looks like this
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// e.UserState contains the state data you passed to ReportProgress,
// you have to cast it to the right type though, since its of type object
var userState = (StateClass)e.UserState;
int progress = e.ProgressPercentage;
// TODO: report progress to the UI with the above variables
}
Now all you have to do is tell the background worker to do work by calling
this.backgroundWorker1.RunWorkerAsync()
I don't think you're doing all your work within the background worker or the GUI wouldn't be freezing. We'll need to see more of your code or you'll need to have another look at background worker examples to see what could be wrong.
EDIT
Add a call to System.Threading.Thread.Sleep(1); after your report progress call