I have read many answers on this question, and yet I still cannot get this to work. I have a simple C# WinForms app with a timer control. When the timer fires, I have some code do some processing. I want to update a textbox with status during this processing. But the textbox never gets updated until the eventhandler finishes. Please tell me how I can get the textbox to update during the processing.
Here is my code:
My Form:
public Form1()
{
InitializeComponent();
timer1.Interval = 60000;
timer1.Tick += new EventHandler(CheckStatus);
timer1.Start();
}
private void CheckStatus(object Sender, EventArgs e)
{
// Set the caption to the current time.
textBox1.AppendText(DateTime.Now.ToString() + Environment.NewLine);
ProcessStatus();
}
private void ProcessStatus()
{
textBox1.AppendText("Now updated" + Environment.NewLine);
}
If I step through my code, the textbox is not updated until I step out of CheckStatus. (I'm using Visual Studio 2017)
I have tried several things like what is found here: StackOverflow
When the timer ticks it's firing on the GUI thread. While the GUI thread is busy processing (I assume whatever you're doing takes a long time) all other GUI updates will pause.
You can run textBox.Update() to force the update at that point, but that's not considered a best practice.
Instead, you should run your process on a background thread. One option is BackgroundWorker and use the ProgressChanged event to show your updates in your GUI.
Related
My goal is to make a desktop pet. I've already programmed a lot of logic that executes in a while loop and updates every iteration. To display the creature I'm looking to use windows forms, but that has brought up a dilemma.
I want to be able to execute the logic, and then update the window in the same loop (process events and redraw), without having to deal with Application.Run() or multi threading. As an example, and as someone who's come from python, using tkinter it's possible to call the update() method on a window in a loop, which is essentially the same as calling mainloop() once, except it doesn't block the program.
Do forms offer any similar functionality?
As Scott Chamberlain mentioned, you should use a timer to run your 'loop'. Winforms is event based so adding an infinite loop will freeze the program since events are blocked.
This code illustrates how to use a timer. I added a picture box to the form and it moves across the screen as the timer fires.
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
Timer tmr = new Timer();
tmr.Interval = 50; // milliseconds
tmr.Tick += Tmr_Tick; // set handler
tmr.Start();
}
private void Tmr_Tick(object sender, EventArgs e) //run this logic each timer tick
{
pictureBox1.Left += 1; // move image across screen, picture box is control so no repaint needed
}
}
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 write below code in click event of one button, and want ProgressBar & for loops start working together simultaneously. So at first I started timer1
private void Button_Click(object sender, EventArgs e)
{
this.timer1.Start();
if (comboBox.SelectedIndex == 0)
{
TextBox.Clear();
for (int j = 0; j < N; j++)
for (int i = 0; i < N; i++)
{
TextBox.Text += array[i, j].ToString()+" , " ;
}
}
}
But when I click button, first TextBox start to fill( for loops work), then the timer1 start to work and ProgressBar start to incrementing.
I use Visual Studio 2010, Windows Forms application and write bottom code for timer1:
private void timer1_Tick(object sender, EventArgs e)
{
this.ProgressBar.Increment(1);
}
Can every body tell how can I set them to start and work simultaneously?
You are using a System.Windows.Forms.Timer which runs on the UI thread and as you are blocking the UI thread it won't execute until your Button_Click method completes.
You must either update the ProgressBar from within your for loops directly (which will still block the UI thread and cause your application to be unresponsive for the duration) or to do it correctly you should create an asynchronously worker such as a System.Threading.Task or a System.ComponentModel.BackgroundWorker and implement it in such as way as to provide progress updates to the main thread which will update the ProgressBar as needed.
Here is a simple tutorial on how to use a BackgroundWorker which is probably the simpler solution.
You'll notice in this tutorial a method called bw_ProgressChanged which updates a TextBox. You would simply want to change this method to update your ProgressBar as well as your TextBox.
Windows Forms uses a single threaded message loop, so until your Button_Click hander has returned, the handler for the timer will not be able to run. Read up on the windows message loop to understand why.
The key here is that you should never perform long-running tasks in an event handler. You may have noticed that your window hangs until your code is running.
To get this to work, you could use a BackgroundWorker, or start a thread yourself. Also note that if you run your long-running task on another thread, it cannot access the form directly.
Check this old msdn article out.
I have some WinForms in my application.
On some form , I am loading other forms which have lot of data to show.
So when I click a button to load these forms they take, 9-10 seconds to load,
I wana improve this. I have heard about Background thread but don't know how to use this.
Means on the button click what should i do so that I can see my form imidiately and the processing is done by background thread ?
Please guide me so that I can improve my UI.
Getting the form to show immediately, should be fairly simple and you don't need a background thread to do it.
My guess is that you are retrieving all that data prior to loading the form or you are retrieving the data on the same thread that loads the form. You should be able to call the form with the data it needs to retrieve the rest of the data (such as ID's).
Then you just need to find a way for the data processing to happen on a thread other than the thread that creates the form.
This may not be the most elegant solution, but it gets the job done. It uses a System.Windows.Forms.Timer
private void Form1_Load(object sender, EventArgs e)
{
timer1.Start();
}
private void timer1_Tick(object sender, EventArgs e)
{
timer1.Stop();
//fill controls with data
}
If you have several sets of data that you are retrieving, you may want to consider setting up a queue of tasks to retrieve each set.
Any way that you do it. Make sure to disable any buttons(or other ui) that could cause errors if the data is not loaded.
Edit: It should be noted that winform controls don't always play nice with threads. The nice thing about the System.Windows.Forms.Timer (as opposed to System.Timers.Timer)is that it designed to play nice with controls.
BackgroundWorker are easy to use. Mainly you subscribe to DoWork event, this is where you'd put your work that will be done in a seperate thread. And you also subscribe to RunWorkerCompleted event. This will get raised when your DoWork has completed.
In this method you'd simply bind the data fetched by the DoWork method.
You can use the DoWork eventarg to pass data between DoWork and RunWorkerCompleted.
You can also cancel the work and also the you can use the RunWorkerCompleted eventArg to get the data from the DoWork and also inspect the Error property for exception handling.
IF you have lots of data, you can use a batching process, to get data per batch and display them into your UI. You can show how many total records will be fetch and the progress of it 100 out of 1000 have been loaded, 200 out of 1000 have been loaded etc, to keep the user informed. You could also provide a Cancel option so the user can stop the fetch.
In your button click you'd simply call myBackgroundWorker.RunWorkerAsync().
this will call your DoWork Handler which will be done in a seperate thread not the main UI Thread, so your UI will not FREEZE during the background worker DoWork.
The backgroundworker also support report progress.
If your problem is the main form Freezing, what you need is to collect the data on a separated Thread than the Main form is running.
To achieve that, you can do something like this:
BackgroundWorker worker;
public Form1()
{
InitializeComponent();
worker = new BackgroundWorker();
}
private void button1_Click(object sender, EventArgs e)
{
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerAsync();
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
//Collect data and open your second form here;
}
I'm running a data import, using a Windows form to kick off the import and show progress.
I've got this whole thing so nice and user friendly, with major and minor progress bars and everything... but just one problem... the form refresh keeps going AWOL.
I have a call to Form.Refresh() every time I update my labels/progress bars, and it usually starts off working. But if ever I need to break into debug mode, just to hand-hold the import a bit, the Refresh() call stops working, and sometimes even if I'm running without Debug mode, at some unpredictable point the same thing happens: the labels and progress bars do not get updated, and if you hide the form and reopen it, the form does not repaint at all - it just shows up entirely white.
Why, oh why, does Form.Refresh() stop working, and how can I fix this?
It sounds as if the import runs on the UI thread, which means that this thread is blocked, preventing the form from repainting itself. A better approach would be to use a BackgroundWorker component, do the import in the DoWork event handler and use the ProgressChanged to update the UI.
Example:
private void StartImport()
{
backgroundWorker.WorkerReportsProgress = true;
backgroundWorker.RunWorkerAsync();
}
private void BackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
// do some work simulating a lenghy process which occasionally
// reports progress with data back to the caller
for (int i = 0; i < 100; i++)
{
Thread.Sleep(200);
backgroundWorker.ReportProgress(i, "Item No " + i.ToString());
}
}
private void BackgroundWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
listBox.Items.Add(e.UserState.ToString());
}
Using this approach you will typically not need to call Refresh to force a repaint of the form.
You may want to change your code into using BeginUpdate and EndUpdate, like so:
Control.BeginUpdate();
// Do something to the control, e.g. add items or whatnot
Control.EndUpdate();
This way Refresh shouldn't be necessary.
AFAIK constantly calling Refresh is really a hack and should be avoiding, as it stresses the CPU quite a bit (it has to refresh everything instead of just the things which are changed).
Edit: If the form starts being white, it seems the drawing code is not been called at all, which indicates it's somewhat not responding.
I'd check the code for anything that can deadlock or otherwisely hang.
You could use observer pattern..in short if anything changes in model observer pattern will make sure that change is visible on form..
google it for some examples..
Depending on what .NET framework you're using, you can use the Task.Run approach:
private void btnShowProgress_Click(object sender, EventArgs e)
{
progressBar1.Value = 0;
Task.Run(() =>
{
for (int i = 0; i <= 100; i++)
{
Thread.Sleep(100);
progressBar1.Invoke(new MethodInvoker(delegate { progressBar1.Value = i; }));
}
});
}
Task.Run info
Using invoke with controls
The solution may not be the best practice but it definitely works for small applications.
In Form1 create a bool to check to see if the form is closed.
public bool formclosed = false
Then in Form2 on the Form Closing Event Handler add
formclosed = true
also in the Form2 after
InitializeComponent();
add
formclosed = false;
In Form1 create a timer.
In the timer1.Tick event handler say
private void timer1_Tick(object sender, EventArgs e)
{
if(formclosed == true)
{
Application.Restart();
}
}
This will restart the application and refresh everything ... I also had my text saved to the Properties.Settings.Default so everytime the application started the default settings would show.
I created an initial version of a Progress control using a BackgroundWorker. The Progress control computed and displayed nice things like Estimated Duration, Estimated Time to Completion. Each statistic was displayed by custom control based on a Label control. The Progress control worked in my production code.
Then I made some changes to the Progress control. I switched from basing my custom statistics controls from Label to Panel. I successfully ran all my tests (using NUnit). Then, I created a Test Windows Forms app. The Progress control successfully worked in the Test Windows Forms app.
When I ran my production Windows app with the updated Progress control it didn't display the statistics.
I tried Thread.Sleep(N), Form.Refresh(). None of those worked to update the statistics.
I eventually called Control.Invalidate() on each Statistic control. The Invalidate caused OnPaint to be called and the control happily updated the display with its specific statistic.
For Each Stat_Obj As Control in Statistics_Controls
Stat_Obj.Invalidate()
Next
You might need to give the window time to redraw itself. I understand you're doing the import in a loop and the loop is running on the main UI thread? Try adding this line to the loop:
Application.DoEvents();