Update GUI components from Begininvoke - c#

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.

Related

C# - Refresh WPF RichTextBox during a loop

I'm new learner in C# and i have a simple problem.
I do classic loop of 15 iterations with a Threat.Sleep of 3 secondes in each ones and inside this loop at each iteration i'm addind text to my RichTextBox.
BUT the text only appear at the end of the 15 iterations of 3 secondes ! :(
I want the text to be added at each iteration in the loop :) Is there a way to do that ?
Thank you for helping :)
AMIGA RULEZ
I tested this code and it worked.
When i press the button, the RTB adds the word "hello" each 3 seconds.
private async void button1_Click(object sender, EventArgs e)
{
for(int i=0; i<16; i++)
{
richTextBox1.Text += "hello";
await Task.Delay(3000);
}
}
Thread.Sleep will block the current thread. When a thread is blocked it cannot do anything else. So if it is the UI thread it cannot re-render the UI until the thread is unblocked. Because of this it is recommended to never block the UI thread.
A workaround is to use Task.Delay and async/await instead. Internally this will cause your code to be rewritten to a state machine. In principle transforming it to something like the following pseudo code
int i = 0;
timer = new Timer(TimerCallback, period: 3s);
timer.Start();
...
public void TimerCallback(){
if(i >= 16){
timer.Stop();
return;
}
richTextBox1.Text += "hello";
}

C# Threading using invoke, freezing the form

I'm trying to use threads and prevent the program from freezing while the thread is busy. It should show the progress (writing of 0's / 1's) and not just show the result after its done, freezing the form in the meanwhile.
In the current program I'm trying to write to a textbox, and actually see constant progress, and the form can't be affected by the tasks of the other thread.
What I have now is I can write to a textbox with a thread using invoke, but It only shows the result (Form freezes while thread is busy), and the form freezes.
Form image:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace MultiThreading
{
public partial class MultiThreading : Form
{
public MultiThreading()
{
InitializeComponent();
}
Thread writeOne, writeTwo;
private void writeText(TextBox textBox, string text)
{
if (textBox.InvokeRequired)
{
textBox.BeginInvoke((MethodInvoker)delegate()
{
for (int i = 0; i < 500; i++)
{
textBox.Text += text;
}
});
}
else
{
for (int i = 0; i < 500; i++)
{
textBox.Text += text;
}
}
}
private void btnWrite1_Click(object sender, EventArgs e)
{
writeOne = new Thread(() => writeText(txtOutput1, "0"));
writeOne.Start();
}
private void btnWrite2_Click(object sender, EventArgs e)
{
writeTwo = new Thread(() => writeText(txtOutput2, "1"));
writeTwo.Start();
}
private void btnClear1_Click(object sender, EventArgs e)
{
txtOutput1.Clear();
}
private void btnClear2_Click(object sender, EventArgs e)
{
txtOutput2.Clear();
}
private void btnWriteBoth_Click(object sender, EventArgs e)
{
writeOne = new Thread(() => writeText(txtOutput1, "0"));
writeTwo = new Thread(() => writeText(txtOutput2, "1"));
writeOne.Start();
writeTwo.Start();
}
private void btnClearBoth_Click(object sender, EventArgs e)
{
txtOutput1.Clear();
txtOutput2.Clear();
}
}
}
EDIT:
Btw for anyone wondering, I'm new to multithreading and I'm just trying to write a small program to understand the best way to do this.
I understand that my previous invoke didn't realy help because I still wasn't giving the form a chance to update, so its getting there.
Ok so running 1 thread like this works, but still running multiple threads together, won't update the form till after the thread is done.
I've added a thread.sleep() so I can try and clear while writing, to see if I can still use the form.
When writing to 1 textbox I can still clear the screen while writing.
But once I use 2 threads, I can't use the form anymore till the thread completes, and gives the output.
private void writeText(TextBox textBox, string text)
{
for (int i = 0; i < 500; i++)
{
Invoke(new MethodInvoker(() =>
{
textBox.Text += text;
Thread.Sleep(2);
}));
}
}
(If I'm totally wrong on this I don't mind having to read through some examples/threads, I'm still trying to see what is the best way to do this, besides a backgroundworker)
EDIT 2:
I've reduced the number of invokes by reducing the amount to write, but to increase delay giving the same effect of constant writing, just reducing the load.
private void writeText(TextBox textBox, string text)
{
for (int i = 0; i < 500; i++)
{
Invoke(new MethodInvoker(() =>
{
textBox.Text += text;
Thread.Sleep(2);
}));
}
}
EDIT 3:
Sumeet's example works using
Application.DoEvents();
(notice the s, .DoEvent doesn't work, typo probably :P), writing multiple strings simultaneously & having them show the progress and not just the result.
So Code update again :)
*Using a new button to create 5 threads that write a random number to both textboxes
private void writeText(TextBox textBox, string text)
{
for (int i = 0; i < 57; i++)
{
Invoke(new MethodInvoker(() =>
{
textBox.Text += text;
Thread.Sleep(5);
Application.DoEvents();
}));
}
}
private void btnNewThread_Click(object sender, EventArgs e)
{
Random random = new Random();
int[] randomNumber = new int[5];
for (int i = 0; i < 5; i++)
{
randomNumber[i] = random.Next(2, 9);
new Thread(() => writeText(txtOutput1, randomNumber[i-1].ToString())).Start();
new Thread(() => writeText(txtOutput2, randomNumber[i-1].ToString())).Start();
}
}
This solution works ! Have checked it.
The problem is you keep telling the UI thread to change the Text, but never letting it have time to show you the updated text.
To make your UI show the changed text, add the Application.DoEvents line like this :
textBox.Text += text;
Application.DoEvents();
p.s. : Remove the else block of your If / Else loop, it is redundant, and also as pointed by others there is not any use of creating those 2 Threads as all they are doing is post the message on the UI Thread itself.
You're still performing a single-threaded task, just re-launching it on the UI thread if needed.
for (int i = 0; i < 500; i++){
string text = ""+i;
textBox.BeginInvoke((MethodInvoker)delegate()
{
textBox.Text += text;
});
}
The problem is that you're starting a new thread, and then that new thread is doing nothing except adding one new task for the UI thread to process that does a lot of work. To keep your form responsive you need to have time where the UI thread is doing nothing, or at least not spending a significant amount of time doing any one task.
To keep the form responsive we need to have lots of little BeginInvoke (or Invoke) calls.
private void writeText(TextBox textBox, string text)
{
for (int i = 0; i < 500; i++)
{
Invoke(new MethodInvoker(() =>
{
textBox.Text += text;
}));
}
}
By having lots of little invoke calls it allows things like paint events, mouse move/click events, etc. to be handled in the middle of your operations. Also note that I removed the InvokeRequired call. We know that this method will be called from a non-UI thread, so there's no need for it.
You're defeating the purpose of using threads.
All your thread does is tell the UI thread to execute some code using BeginInvoke().
All of the actual work happens on the UI thread.
Either you're doing data processing or you're just trying to animate the UI.
For data processing you should do all the heavy lifting on a background thread and only update the UI occasionally. In your example a TextBox is particularly troublesome in this regard, as you're adding data to the underlying data model several hundred times and the UI element (a TextBox) takes longer to render each time. You must be careful about how often to update the UI so that processing for UI updates does not overwhelm data model updates. TextBoxes are nasty like that.
In the example below, a flag set during the paint event ensures that additional UI updates aren't queued until the TextBox has finished painting the last update:
string str = string.Empty;
public void DoStuff()
{
System.Threading.ThreadPool.QueueUserWorkItem(WorkerThread);
}
void WorkerThread(object unused)
{
for (int i = 0; i < 1000; i++)
{
str += "0";
if (updatedUI)
{
updatedUI = false;
BeginInvoke(new Action<string>(UpdateUI), str);
}
}
BeginInvoke(new Action<string>(UpdateUI), str);
}
private volatile bool updatedUI = true;
void textbox1_Paint(object sender, PaintEventArgs e) // event hooked up in Form constructor
{
updatedUI = true;
}
void UpdateUI(string str)
{
textBox1.Text = str;
}
On the other hand if UI animation is your goal then you probably ought to be using something other than a TextBox. It's just not designed to handle updates so frequently. There might be some optimizations to text rendering you could make for your specific use case.
You must never use a string in high volume applications. UI or not. Multi-threading or not.
You should use StringBuilder to accumulate the string. and then assign
tb.Text = sb.ToString();

How to update a text box continously without halting the button in windows form application C#?

I need to update my text box continuously after clicking the button but the button should perform its remaining task as it is.
simple is that when click event is performed then Text box should not wait for the completion of click event but to start updating its text continuously.
sample code
using System.threading;
namespace name
{
public class sA
{
public void th()
{
textbox.invoke(new MethodInvoke(()=> textbox.AppendText("hello\n")));
}
private void Button1Click(object sender, EventArgs e)
{
thread cThread=new thread(th);
cThread.start();
while(true)
{
// do any thing
}
}
}
}
Important :: when it performs the event " Cthread.start();" text box should immediately start updating the text while the remaining functions of click event like "while loop" should perform in parallel.
IF this is inside Windows Forms.. then add Application.DoEvents(); anywhere in the loop
e.g.
private void Button1Click(object sender, EventArgs e)
{
thread cThread=new thread(th);
cThread.start();
while(true)
{
// do any thing
textbox.Invalidate();
Application.DoEvents(); // Releases the current thread back to windows form
// NOTE Thread sleep different in Application.DoEvents();
//Application.DoEvents() is available only in System.Windows.Forms
}
}
Hope this help you although late.. :)
Your while(true) block has to happen on another thread as well.
Right now its blocking the UI thread from performing any updates.
Method th() is running on a background thread but the call to Invoke can't run until the UI thread is available again.
If I understood your question correctly, you need to keep updating the TextBox's text while the button click procedure is running inside it's "while" loop. You didn't really specify where will the textbox be updated from, but I will assume that it is coming from the code inside your "while" loop.
As "akatakritos" has stated, your while loop inside the button click is the reason why your application is halting. That happens because the while loop is blocking the User Interface (UI) Thread.
What you should be doing is moving the code inside your "while" loop to run inside a different thread, and use the button click to start this new thread.
Here is a way to do this, maybe not the best, but it will do what you need:
Create a new class:
public class ClassWithYourCode
{
public TextBox TextBoxToUpdate { get; set; }
Action<string> updateTextBoxDelegate;
public ClassWithYourCode()
{ }
public void methodToExecute()
{
bool IsDone = false;
while (!IsDone)
{
// write your code here. When you need to update the
// textbox, call the function:
// updateTextBox("message you want to send");
// Below you can find some example code:
for (int i = 0; i < 10; i++)
{
Thread.Sleep(1000);
updateTextBox(string.Format("Iteration number: {0}", i));
}
// Don't forget to set "IsDone" to "true" so you can exit the while loop!
IsDone = true;
}
updateTextBox("End of method execution!");
}
private void updateTextBox(string MessageToShow)
{
if (TextBoxToUpdate.InvokeRequired)
{
updateTextBoxDelegate = msgToShow => updateTextBox(msgToShow);
TextBoxToUpdate.Invoke(updateTextBoxDelegate, MessageToShow);
}
else
{
TextBoxToUpdate.Text += string.Format("{0}{1}", MessageToShow, Environment.NewLine);
}
}
}
and, inside your button1_Click method, you can add the following code:
private void button1_Click(object sender, EventArgs e)
{
ClassWithYourCode myCode = new ClassWithYourCode();
myCode.TextBoxToUpdate = textBox1;
Thread thread = new Thread(myCode.methodToExecute);
thread.Start();
}
Now, your "while" loop is executing inside a new thread and, whenever you need to update the textbox, you do so from the UI thread, because you cannot update Windows Forms controls from a thread other than the UI thread.

Can't change the text on an object?

I can't seem to change the text on any object - I've tried both labels and buttons so far. Why doesn't this work?
void Button1Click(object sender, EventArgs e)
{
for(int i = 60; i >=1; i--){
Thread.Sleep(1000);
i -= 1;
label1.Text = i.ToString();
}
}
It doesn't work because you are using a busy loop to update the text.
This code runs in the main thread, so it's busy setting the Text property for a whole minute, and can't update the user interface.
You would use a timer to update the text, so that the main thread regains the control in between changes.
Try taking out the Thread.Sleep() command to see if the label is updated. You may find that the UI thread exits the function even though you requested it to sleep.
What you can do is use a Timer control, and set the interval to 1000 (1 second). Then you can set the label1.Text to a counter or static field value (or hidden field).
As long as you stay in the Button1Click the UI thread seams to be sleeping. Delete the Thread.Sleep and you will see that the text is shown in the labels.
put Application.DoEvents(); in your code after last line, then it will work for sure.
for (int i = 60; i >= 1; i--)
{
Thread.Sleep(1000);
i -= 1;
label1.Text = i.ToString();
Application.DoEvents();
}
First of all you have to learn How to: Make Thread-Safe Calls to Windows Forms Controls.
You need to create a thread and use Invoke delegate.
Thread th = new Thread(test);
th.Start(); //start the thread
This method will update the lable.text
void test()
{
for (int i = 60; i >= 1; i--)
{
Thread.Sleep(1000);
if (label1.InvokeRequired)
{
label1.Invoke(new Action(() => {
label1.Text = i.ToString();
}));
}
}
}

Form Not Responding when any other operation performed in C#

I have a form (Developed in C# using VS2010) with a Progress Bar.
It's kind of stopwatch form where I fill the progress bar in say 10secs.... As Time elapses, Progress bar fills accordingly.... Means after 5secs, Progress Bar will be filled 50% and so on....
I used a for loop to perform this operation:-
for(int count=0;count<200;count++)
{
progbar.performstep();
progbar.update();
update();
Thread.Sleep(50);
}
I have used Thread.Sleep of 50msec so that progress bar updates smoothly.
For 1sec, it increments in steps.
Problem is if I do anyother operation like Moving this form or even clicking on another icon on desktops makes this form "Not Responding".... But it perfoms the operation and at the end of 10 secs, It regains it's state and shows the pop up indicating 10secs are elapsed with Progress Bar Full.
Thanks for help and Sorry for using such complicated language.
Regards,
Swanand
Update: I solved this problem with the help of Answers below.... One common mistake I got to know is forgetting "Applications.DoEvents()" along with "update()".... If you enter this line, there are less chances of getting "hanged"!
You're performing a long-running operation on the UI thread, which means that the UI "message loop" (responsible for handling events such as user input and updating the screen) doesn't get a chance to run.
You should perform the long-running operation on a different thread (whether one you create yourself or a background thread) and either use BackgroundWorker to easily update your progress bar, or use Control.Invoke/BeginInvoke to marshall a delegate call back to the UI thread when you need to update the UI. (You mustn't update controls from the wrong thread.)
If your only UI interaction is filling in a progress bar, I suggest using BackgroundWorker.
If you're not really doing "real" work, just waiting for time to pass, you could use a System.Windows.Forms.Timer instead of all of this, however. That will "tick" on the UI thread, but won't block the UI thread between ticks. You should only use this if you don't have a lot of work to do though - if it really is just updating a progress bar, not (say) processing a file etc. Note that you shouldn't rely on the timer firing exactly "on time" - you should probably set the position of the progress bar based on the observed time, rather than the observed number of ticks.
You are blocking the UI thread, which means it isn't processing events such as "paint". To do this properly, you should be using something like BackgroundWorker, and just updating the UI from the progress event.
using System;
using System.Windows.Forms;
using System.ComponentModel;
using System.Threading;
static class Program
{
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MyForm());
}
}
class MyForm : Form
{
Button btn;
BackgroundWorker worker;
ProgressBar bar;
public MyForm()
{
Controls.Add(btn = new Button { Text = "Click me" });
btn.Click += new EventHandler(btn_Click);
Controls.Add(bar = new ProgressBar { Dock = DockStyle.Bottom, Visible = false, Minimum = 0, Maximum = 100 });
worker = new BackgroundWorker { WorkerReportsProgress = true };
worker.ProgressChanged += new ProgressChangedEventHandler(worker_ProgressChanged);
worker.DoWork += new DoWorkEventHandler(worker_DoWork);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(worker_RunWorkerCompleted);
}
void worker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
bar.Visible = false;
if (e.Error != null)
{
Text = e.Error.Message;
}
else if (e.Cancelled)
{
Text = "cancelled";
}
else
{
Text = e.Result == null ? "complete" : e.Result.ToString();
}
btn.Enabled = true;
}
void worker_DoWork(object sender, DoWorkEventArgs e)
{
for (int count = 0; count < 100; count++)
{
worker.ReportProgress(count);
Thread.Sleep(50);
}
}
void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
bar.Value = e.ProgressPercentage;
}
void btn_Click(object sender, EventArgs e)
{
bar.Value = 0;
bar.Visible = true;
btn.Enabled = false;
worker.RunWorkerAsync();
}
}
You are blocking the Main UI thread. You can use a background worker to do this. You can find more details in MSDN
If you want to run your code you should put this code in a function and call this function with one thread.
public static void fun1()
{
for (int i = 0; i <= 10; i++)
{
Console.Write("This is function1");
Console.Write("\n");
}
}
Thread firstthread = new Thread(new ThreadStart(fun1));
firstthread.Start();
firstthread.suspend();//whenever you want your current control to stop.
b'caz Thread.sleep(100) will stop the whole context not that particular you want..
Answer suggested by Marc will help. Lon running operations can make your application crash or not responsive. I have a blog post related to the usage of the background worker class.
http://midnightprogrammer.net/post/Using-Background-Worker-in-C.aspx

Categories