Update GUI in a loop [duplicate] - c#

This question already has answers here:
Can I use a timer to update a label every x milliseconds
(4 answers)
Closed 7 years ago.
I want to print "ms" value in textbox1 by clicking the button firstly, then it should wait for 100ms (one by one), then it show next value.
But it's showing only the last value by clicking button, still after coding Thread.Sleep(100). It should show the value like stop watch.
private void button1_Click(object sender, EventArgs e)
{
int ms = 00;
for (ms = 0; ms < 10; ms++)
{
textBox1.Text = ms.ToString();
Thread.Sleep(100);
textBox1.Text = "";
}
}

The reason your textbox only shows tha last value is because your code runs on the UI thread. The UI doesn't get the time to update its layout until your code has reached its end.
You could solve this by using some sort of background threading mechanism so that the UI has the time and resources to update itself.
You could use timers and update the textbox text on its tick event.
You could use task await to enforce background threading instead of Thread.Sleep
private async void button1_Click(Object sender, EventArgs e)
{
for (int ms = 0; ms < 10; ms++)
{
textBox1.Text = ms.ToString();
await Task.Delay(100);
}
}

Thats because you button click is running on the gui thread and will block painting.
You could try updating the textbox:
private void button1_Click(object sender, EventArgs e)
{
int ms = 00;
for (ms = 0; ms < 10; ms++)
{
textBox1.Text = ms.ToString();
textBox1.Update();
Thread.Sleep(100);
textBox1.Text = "";
}
}
But this will still block you gui thread. If you don't want to block the current thread, you could create a new Thread that invokes the gui thread to alter the textBox1.Text.
Writing to a TextBox from another thread?

Related

How do I update several controls on my Form1 using BackgroundWorker?

I found several examples that shows how to update UI control using BackgroundWorker.
For example the following code:
BackgroundWorker backgroundWorker1 = new BackgroundWorker();
backgroundWorker1.WorkerReportsProgress = true;
//start the operation on another thread
private void btnStart_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
//DoWork event handler is executed on a separate thread
private void backgroundWorker1_DoWork(object sender, DoWorkeventArgs e)
{
//a long running operation
for (int i = 1; i < 11; i++)
{
Thread.Sleep(2000);
backgroundWorker1.ReportProgress(i * 10);
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.value = e.ProgressPercentage;
}
That I found here:
https://www.c-sharpcorner.com/UploadFile/Ashush/using-the-backgroundworker-component
The problem is that ALL the examples that I found, shows how to update Only One control on the form. For example the code that I gave, demonstrate how to update a progressBar value.
But this is not a realistic situation, in the real world, I would usually want to update SEVERAL controls on my form.
For example:
progressBar1.Value = 54;
listBox1.SelectedIndex = 2;
button1.BackgroundColor = Color.Yellow;
image1.left = image1.left + 20;
How do I change the code that I showed, in order to update the controls that I gave in the previous lines? There must be an easy way to do that. By the way, my application uses several threads, and each one of them have to update several controls on the UI while they are still running...
I Hope that my question is clear,
Thanks.
Edit:
Neil suggested me to find the solution in this thread:
How do I update the GUI from another thread?
But I saw that people there recommended using BackgroundWorker... which is exactly what I'm asking about.
They didn't explain how to use this method in order to update several UI controls in parallel.
Try something like this:
button1.Invoke(
new Action(
() =>
button1.BackgroundColor = Color.Yellow;
)
);
image1.Invoke(
new Action(
() =>
image1.left = image1.left + 20;
)
);
//etc.

timer stopped and jump to certain second

I made a sample project. Where every 10 second it do some function. But when I try to show a timer tick in label it always stuck, and jump to certain second (eg: stuck at 9s and suddenly jump to 12s). What i want to ask,
Is my function run properly ?
Is my tick, skipped a few ms ? (it will overlap with my function)
How do I run it as a thread ?
My code
private void button2_Click(object sender, EventArgs e)
{
timer1.Start();
}
int x = 0;
private void timer1_Tick(object sender, EventArgs e)
{
x += 1;
label1.Text = x.ToString();
if (x % 10 == 0)
{
addpoint();
//MessageBox.Show("success");
}
}
how to keep my label1.text keep updating, while do addpoint() function
Note:
I have set timer1 interval = 1000
update
i test it with this.
public void addpoint()
{
string x = #"c:\test\a.txt";
string text = "haiaiaia";
using (FileStream fs = new FileStream(x, FileMode.Create))
{
Byte[] xx = Encoding.ASCII.GetBytes(text);
fs.Write(xx, 0, xx.Length);
}
Messagebox.show("Created !");
}
It looks like your using a Windows.Forms.Timer which is executed on the main thread. The advantage is that you don't need to call Invoke, the disadvantage is that addpoint is also executed on the main thread and hence blocks your GUI from update, when in the mean time the next tick events are fired.
You can verify it be replacing the call of addpoint with Thread.Sleep(3000) and you will experience the same behaviour.
What you could do is to try and run the method on another thread:
private void timer1_Tick(object sender, EventArgs e)
{
x += 1;
label1.Text = x.ToString();
if (x % 10 == 0)
{
Thread t = new Thread(addpoint);
t.Start();
}
}
This should avoid the blocking of the GUI.
Disclaimer:
It is important to know what you actually do in addpoint, because this solution might lead to race condition and wrong functioning of the method. For example if you are using class variables in it, and if the possibility exists that a second thread can be started while the first has not finished yet! Be aware.

Update GUI components from Begininvoke

So I have a very basic Windows Application where I want to count from 1 to 10000
and show the numbers in label:
private void button1_Click(object sender, EventArgs e)
{
Thread thread = new Thread(() =>
{
for (int i = 0; i < 10000; i++)
{
BeginInvoke((MethodInvoker)delegate ()
{
label3.Text = i.ToString();
});
}
});
thread.Start();
}
The problem is that the label text doesn't update and shows only the last loop counter i.e. 9999. Is BeginInvoke called on UI thread? Why does not the label get updated correctly?
Thanks.
Because BeginInvoke is an asynchronous call, you're sending too many updates to the text box for it to update fast enough, by the time the text box has got around to drawing, it's already counted up to 10000!
You can synchronously update the text, that is, the calling loop will halt until the text box has updated and finished, use Invoke instead of BeginInvoke.

Backgroundworker blocks UI

I try to perform an easy task in an other backgroundthread, so the UI doesn't get blocked, but it still gets blocked. Did I forget anything?
public partial class backgroundWorkerForm : Form
{
public backgroundWorkerForm()
{
InitializeComponent();
}
private void doWorkButton_Click(object sender, EventArgs e)
{
if (backgroundWorker.IsBusy != true)
{
// Start the asynchronous operation.
backgroundWorker.RunWorkerAsync();
}
}
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//BackgroundWorker worker = sender as BackgroundWorker;
if (textBoxOutput.InvokeRequired)
{
textBoxOutput.Invoke(new MethodInvoker(delegate
{
for (int i = 0; i < 10000; i++)
{
textBoxOutput.AppendText(i + Environment.NewLine);
}
}));
}
}
}
While the textBox gets filled, the UI is blocked:
Your app wants to repeatedly send updates from the background thread to the UI. There is a built-in mechanism for this: the ProgressChanged event for the background worker. A ReportProgress call is triggered in the background, but executes on the UI thread.
I do change one thing, however. Performance can degrade with too many cross-thread calls. So instead of sending an update every iteration, I instead will batch them into 100.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
const int maxIterations = 10000;
var progressLimit = 100;
var staging = new List<int>();
for (int i = 0; i < maxIterations; i++)
{
staging.Add(i);
if (staging.Count % progressLimit == 0)
{
// Only send a COPY of the staging list because we
// may continue to modify staging inside this loop.
// There are many ways to do this. Below is just one way.
backgroundWorker1.ReportProgress(staging.Count, staging.ToArray());
staging.Clear();
}
}
// Flush last bit in staging.
if (staging.Count > 0)
{
// We are done with staging here so we can pass it as is.
backgroundWorker1.ReportProgress(staging.Count, staging);
}
}
// The ProgressChanged event is triggered in the background thread
// but actually executes in the UI thread.
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (e.ProgressPercentage == 0) return;
// We don't care if an array or a list was passed.
var updatedIndices = e.UserState as IEnumerable<int>;
var sb = new StringBuilder();
foreach (var index in updatedIndices)
{
sb.Append(index.ToString() + Environment.NewLine);
}
textBoxOutput.Text += sb.ToString();
}
EDIT:
This requires you set the background worker's WorkerReportsProgress property to true.
It's not important that you pass a count with the ReportProgress call. I do so just to have something and to quickly check if I can return.
One really should keep in mind about how many events are being invoked and queued up. Your original app had 10,000 cross thread invocations and 10,000 changed text events for textBoxOutput. My example uses 100 cross thread calls since I use a page size of 100. I could still have generated 10,000 changed text events for the textbox, but instead use a StringBuilder object to hold a full page of changes and then update the textbox once for that page. That way the textbox only has 100 update events.
EDIT 2
Whether or not your app needs paging is not the main deal. The biggest take away should be that the background worker really should use ReportProgress when trying to communicate info back to the UI. See this MSDN Link. Of particular note is this:
You must be careful not to manipulate any user-interface objects in
your DoWork event handler. Instead, communicate to the user interface
through the ProgressChanged and RunWorkerCompleted events.
Your invocation code should be outside the loop. Everything in the invoked codeblock, will be executed on the UI thread, thus blocking it.
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
//BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 0; i < 10000; i++)
{
// do long-running task
//if (textBoxOutput.InvokeRequired)
//{
textBoxOutput.Invoke(new MethodInvoker(delegate
{
textBoxOutput.AppendText(i + Environment.NewLine);
}));
//}
}
}
an easier way would be to do completely create your output text, and then paste the full output into the TextBox, then you only need one invocation
protected delegate void SetTextDelegate(TextBox tb, string Text);
protected void SetText(TextBox tb, string Text)
{
if (tb.InvokeRequired) {
tb.Invoke(new SetTextDelegate(SetText), tb, Text);
return;
}
tb.Text = Text;
}
and then inside your dowork
private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
StringBuilder sb = new StringBuilder();
//BackgroundWorker worker = sender as BackgroundWorker;
for (int i = 0; i < 10000; i++)
{
sb.AppendLine(i.ToString());
}
SetText(textBoxOutput, sb.ToString());
}

asking data from external device with selected delay using C#

private void timer1_Tick(object sender, EventArgs e)
{
string[] DF_Engines = { Form2.Engine1, Form2.Engine2, Form2.Engine3, Form2.Engine4, Form2.Engine5, Form2.Engine6 };
foreach (string DF_Engine in DF_Engines)
{
if (Convert.ToDouble(DF_Engine) != 99)
{
string Hex_ADD1 = "{" + DF_Engine + "|";
Console.WriteLine(Hex_ADD1);
serialPort1.Write(Hex_ADD1);
n = Convert.ToInt16(DF_Engine);
}
}
}
Form2.Engine1, Form2.Engine2....... are the values come from Form2 when the corresponding checkbox is selected. these would be 1, 2 , 3 so on....
My code send 01 when checkbox1 is selected but it send 01,02 without any delay when checkbox1 and checkbox2 are selected in Form2.
I need delay when asking 01 and 02 and so on as per my interest.
how could i do it convineintly
and when i use Thread.Sleep(500), the application gets slow.
need guidance.
Do not use Thread.Sleep on UI thread. It will definitely slow down your application. Rather start a new thread, pass data to that thread and let that thread send input to your application with delay.
System.Threading.Thread thread = new System.Threading.Thread((inputList) =>
{
foreach (var input in inputList as IEnumerable<int>)
{
//Send input
System.Threading.Thread.Sleep(500);
}
});
thread.Start();
Where inputList is an array of data(1,2,etc depending upon your checkbox selected).
You can consider using a separate thread of just a BackgroundWorker control. This way, you could use Thread.Sleep() method and it should not make the application's GUI unresponsive. You can find some more information about it on msdn: http://msdn.microsoft.com/en-us/library/system.componentmodel.backgroundworker.aspx.
Just add a BackgroundWorker control to your form and use this code to handle appropriate events:
private void timer1_Tick(object sender, EventArgs e)
{
string[] DF_Engines = { Form2.Engine1, Form2.Engine2, Form2.Engine3, Form2.Engine4, Form2.Engine5, Form2.Engine6 };
//disable timer1, so it wont tick again until the current work is finished
timer1.Stop();
//start processing asynchronously, so GUI is still responsive
sampleBackgroundWorker.RunWorkerAsync(DF_Engines);
}
//this method should be attached to DoWork event of the BackgroundWorker
private void sampleBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
string[] DF_Engines = (e.Argument as string[]);
foreach (string DF_Engine in DF_Engines)
{
if (Convert.ToDouble(DF_Engine) != 99)
{
string Hex_ADD1 = "{" + DF_Engine + "|";
Console.WriteLine(Hex_ADD1);
serialPort1.Write(Hex_ADD1);
//n gets overwritten in each iteration, is this line required?
n = Convert.ToInt16(DF_Engine);
Thread.Sleep(500);
}
}
//you can also pass a result from this method back to the GUI thread like this
//e.Result = "job done";
//this can be read later in RunWorkerCompleted method of the BackgroundWorker
}
//this method should be attached to RunWorkerCompleted event of the BackgroundWorker
private void sampleBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//start timer1, so it can invoke the worker again
timer1.Start();
}
If you need more help with this, let me know.

Categories