I've run into some problems while using progress bars in Windows Forms. Say I have an algorithm with ten parts that runs on a button click. After each part, I'd want to update a progress bar on the form to 10% further along. However, when code is running, the Windows Form will not respond or update.
What is the correct do show progress on a form while code is running?
You need to use a BackgroundWorker.
A nice example can be found here: http://www.dotnetperls.com/progressbar
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, System.EventArgs e)
{
// Start the BackgroundWorker.
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 1; i <= 100; i++)
{
// Wait 100 milliseconds.
Thread.Sleep(100);
// Report progress.
backgroundWorker1.ReportProgress(i);
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
// Change the value of the ProgressBar to the BackgroundWorker progress.
progressBar1.Value = e.ProgressPercentage;
// Set the text.
this.Text = e.ProgressPercentage.ToString();
}
}
}
Or you can use something like:
private void StartButtonClick(object sender, EventArgs e)
{
var t1 = new Thread(() => ProgressBar(value));
t1.Start();
}
private void ProgressBar(value1)
{
ProgressBar.BeginInvoke(new MethodInvoker(delegate
{
ProgresBar.Value++
}));
}
I suggest use TPL for such operations as more standardized, lightweight, robust and extendable
See for ex.: http://blogs.msdn.com/b/pfxteam/archive/2010/10/15/10076552.aspx
Related
I have a 2 forms, MainForm and ProgressForm.
#1 MainForm.cs has a button, which when clicked starts my backgroundWorker1.
ProgressForm pg = new ProgressForm();
private void StarBtn_Click(object sender, EventArgs e)
{
pg.Show();
if(!backgroundWorker1.IsBusy)
{
backgroundWorker1.RunWorkerAsync();
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//time consuming tasks...
}
#2 ProgressForm is where I have added a progress bar and a label.
namespace MainApp
{
public partial class ProgressForm : Form
{
public ProgressForm()
{
InitializeComponent();
}
}
}
I need to show the progress of my background process in MainForm to progressBar1 in ProgressForm (as below)
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
//something like..
//this.progressBar1.Value = e.ProgressPercentage;
//in ProgressForm...
}
and show a completed in label and close the form.
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//label.Text = "Completed"
// Close the ProgressForm...
}
How can I access the elements in ProgressFrom from backgroundworker in MainForm and make the progress update.
Your background worker events are in the MainForm? If so then add public properties to your ProgressForm and set the label and / or progress bar accordingly to the provided values.
ProgressForm
public int Progress {
set {
progressBar1.Value = value;
}
}
public string LabelText{
set {
label.Text = value;
}
}
MainForm
pg.Progress = e.ProgressPercentage;
It doesn't show me anything inside the logBox, it just stays blank
namespace Clipboard_Logger
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
backgroundWorker1.RunWorkerAsync();
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
logBox.SelectionStart = logBox.TextLength;
logBox.ScrollToCaret();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
if (Clipboard.ContainsText(TextDataFormat.Text))
logBox.Text = logBox.Text + Clipboard.GetText(TextDataFormat.Text) + "\r\n";
}
}
}
}
You are using a background thread ( BackGroundWorker.DoWork ) to access controls on the UI thread. Controls can only be accessed from the UI thread.
Try adding a BackGroundWorker.ProgressChanged event and access your control from that. ProgressChanged runs from the UI thread.
Edit from your comment:
No, that's not what I meant, your're creating a new backgroundworker, you should use the existing one, like this:
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.ReportProgress(1);
}
Also, you need to copy text to the clipboard.
I have win form which represents simple wcf client app. This client consumes wcf service over http.
Inside form there is loadingLabel.Text property where I want to display loading ... text. When wcf service returns data other property labelAllBooksNr.Text should be populated.
Service will return integer in allBooksNumber property.
private void Form1_Load(object sender, EventArgs e)
{
int allBooksNumber = BookAgent.CountAllBooks();
}
Since I do not have any experience in using threads I'm asking to someone provide the best pattern I should follow.
the best pattern you can use is the BackgroundWorker as Executes an operation on a separate thread and offers many methods
from MSDN
When you want a responsive UI and you are faced with long delays
associated with such operations, the BackgroundWorker class provides a
convenient solution.
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
int allBooksNumber = BookAgent.CountAllBooks();
e.Result = allBooksNumber;
}
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
label1.Text = "Loading....";
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
label1.Text = e.Result.ToString;
}
}
}'
Hope this help
I have a following form Form3 that is opened by another form Form1, and when closed Form1 opens back up.
The problem is when I close Form3 DoSomething keeps running after form is closed.
I understand that I can make DoSomething into a thread and set IsBackground = true but is there another way to stop all processes when form closes.
This code is just example, For illustration.
public partial class Form3 : Form
{
public Form3()
{
InitializeComponent();
}
private void DoSomething()
{
int i = 0;
while(true)
{
if (!this.IsDisposed)
{
Application.DoEvents();
i++;
Thread.Sleep(10);
label1.Text = i.ToString();
dataGridView1.Rows.Add();
}
}
}
private void button1_Click(object sender, EventArgs e)
{
DoSomething();
}
private void Form3_FormClosed(object sender, FormClosedEventArgs e)
{
this.Dispose();
Form1.Default.Show();
}
}
You never break out of the while(true). You should either break the loop when it's IsDisposed is true, change your while loop to while(!IsDisposed), or store use a class level variable that determines when to break the loop.
I would probably opt for the latter, as it gives you a little more control.
public partial class Form3 : Form
{
volatile bool clDoSomething;
public Form3()
{
InitializeComponent();
}
private void DoSomething()
{
int i = 0;
clDoSomething = true;
while(clDoSomething)
{
Application.DoEvents();
++i;
Thread.Sleep(10);
label1.Text = i.ToString();
dataGridView1.Rows.Add();
}
}
private void button1_Click(object sender, EventArgs e)
{
DoSomething();
}
private void Form3_FormClosed(object sender, FormClosedEventArgs e)
{
clDoSomething = false;
Form1.Default.Show();
}
}
Your fundamental approach is flawed.
First off, Application.DoEvents should be avoided unless you are sure that you really need it, and that you are using it correctly. You do not need it here, and you are not using it correctly.
What you really need here is a Timer.
private Timer timer = new Timer();
private int count = 0;
public Form3()
{
InitializeComponent();
timer.Tick += timer_Tick;
timer.Interval = 10;
//when the form is closed stop the timer.
FormClosed += (_, args) => timer.Stop();
}
private void button1_Click(object sender, EventArgs e)
{
count = 0;
timer.Start();
}
private void timer_Tick(object sender, EventArgs e)
{
count++;
label1.Text = count.ToString();
dataGridView1.Rows.Add();
}
When the Form is create the Timer is configured. The tick event is set, along with the interval. The tick event will look similar to your DoSomething method; it will involve running some bit of code every 10 seconds, from the UI thread, while keeping the UI responsive. When the form is closed simply stop the timer and it will stop firing off these events.
Also note that in this example here pressing the button multiple times simply resets the timer and the count, it doesn't end up creating two loops that each fire every 10 milliseconds.
Override this.Dispose() or this.Close() as appropriate and kill off DoSomething() manually.
Thanks to cdhowie suggestions and input of all others. Mowing DoEvents to the end and adding IsDipsosed solved my problem.
public partial class Form3 : Form
{
public Form3()
{
InitializeComponent();
}
private void DoSomething()
{
int i = 0;
while ((true) && !this.IsDisposed)
{
i++;
Thread.Sleep(10);
label1.Text = i.ToString();
dataGridView1.Rows.Add();
Application.DoEvents();
}
}
private void button1_Click(object sender, EventArgs e)
{
DoSomething();
}
private void Form3_FormClosed(object sender, FormClosedEventArgs e)
{
Form1.Default.Show();
}
}
try to add this intsruction in the FormClosing event :
System.Diagnostics.Process.GetCurrentProcess().Kill();
it's a little bit like this:
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
System.Diagnostics.Process.GetCurrentProcess().Kill();
}
I have started to play with threads in c#, but need now help, here is my code:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
DoCount();
}
public void DoCount()
{
for (int i = 0; i < 100; i++)
{
objTextBox.Text = i.ToString();
Thread.Sleep(100);
}
}
}
its a simple win forms with a textbox, i want to see the "counting", but as you see in my code, the textbox shows me 99, it count till 99 and then shows up.. i`ll think, i have to manage this in a new thread but dont know how!
Use a BackgroundWorker. There is a BackgroundWorker overview on MSDN.
Here is an example of how your code might look:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker backgroundWorker = (BackgroundWorker)sender;
for (int i = 0; i < 100; i++)
{
backgroundWorker.ReportProgress(i);
Thread.Sleep(100);
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
textBox1.Text = e.ProgressPercentage.ToString();
}
private void button1_Click(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
}
Other notes:
Remember to set WorkerReportsProgress in the designer if you want the progress to work.
When using a BackgroundWorker it is also often useful to use the ProgressBar control.
If you want to be able to cancel the background worker, that is possible too. See CancelAsync and WorkerSupportsCancellation.
When the background worker completes it fires the RunWorkerCompleted event.
This might be what you are looking for:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
DoCount();
}
public void DoCount()
{
Thread t = new Thread(new ThreadStart(delegate
{
for (int i = 0; i < 100; i++)
{
this.Invoke((Action) delegate { objTextBox.Text = i.ToString(); });
Thread.Sleep(1000);
}
}));
t.IsBackground = true;
t.Start();
}
}
Notes
Uses a basic Thread not a BackgroundWorker
Uses Invoke to update the textbox on the UI thread
Sets IsBackground to true so the program exits if the form is closed before the loop is done.
You may want to try out the SynchronizationContext to do this.
Here's a quick example I threw together a while back:
public partial class Form1 : Form
{
private SynchronizationContext c;
private Thread t;
private EventWaitHandle pause =
new EventWaitHandle(false, EventResetMode.ManualReset);
public Form1()
{
this.InitializeComponent();
this.c = SynchronizationContext.Current;
}
private void Form1Activated(object sender, EventArgs e)
{
this.t = new Thread(new ThreadStart(delegate
{
this.pause.Reset();
while (this.t.IsAlive && !this.pause.WaitOne(1000))
{
this.c.Post(
state => this.label1.Text = DateTime.Now.ToString(),
null);
}
}));
this.t.IsBackground = true;
this.t.Start();
}
private void Form1Deactivate(object sender, EventArgs e)
{
this.pause.Set();
this.t.Join();
}
/// <summary>
/// Button1s the click.
/// </summary>
/// <param name="sender">The sender.</param>
/// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
private void Button1Click(object sender, EventArgs e)
{
this.Close();
}
}
You don't need a thread to do this kind of thing at all - consider changing your code to be event driven and use a System.Windows.Forms.Timer object to implement your timings. Using timers for this has a huge advantage - it doesn't cost 1MB of memory (a thread does), and you don't need to synchronize them - windows does it for you.
After Thread.Sleep, try this:
this.Update();
Don't call DoCount directly, call ThreadPool.QueueUserWorkItem(DoCount). This will run DoCount in a new thread. (There are other ways that also work, but this is the simplest that works well.)
The next problem is that you can't directly set the textbox from the thread.
To solve this, use code similar to:
if (this.textBox1.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
this.Invoke(d, new object[] { text });
}
See http://msdn.microsoft.com/en-us/library/ms171728(VS.80).aspx for the full example.
My solution is virtually the same as Mark's. The only difference is I check InvokeRequired in my ProgressChanged event. Here's my sample code:
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
namespace tester
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
if (!backgroundWorker1.IsBusy)
backgroundWorker1.RunWorkerAsync();
}
/// <summary>
/// This delegate enables asynchronous calls for setting the text property on a control.
/// </summary>
delegate void SetTextCallback(string status);
private void BackgroundWorker1DoWork(object sender, DoWorkEventArgs e)
{
for (var i = 0; i < 100; i++)
{
backgroundWorker1.ReportProgress(i);
Thread.Sleep(100);
}
}
private void BackgroundWorker1ProgressChanged(object sender, ProgressChangedEventArgs e)
{
if (label1.InvokeRequired)
Invoke(new SetTextCallback(SetLabelText), new object[] { e.ProgressPercentage.ToString()});
else
SetLabelText(e.ProgressPercentage.ToString());
}
private void SetLabelText(string text)
{
label1.Text = text;
}
}
}
Multithreading could solve this but for something as simple as this counter it is unnecessary.
Another user recommended this.Update(). This works to make the numbers appear because the UI will redraw itself. But it doesn't address the fact that the window is not responsive (you can't move it around).
The third solution and my recommendation for this particular program is Application.DoEvents(). What this does is tell the underlying native window to execute its ProcessMessages method on the message pool. The message pool contains event messages that Windows has sent to it when the window needed to be redrawn, mouse has moved, the form has been moved, minimized, etc. Those instructions were sent by Windows and have been queued. The problem is that the program will not process them until the UI is idle. You can force it by calling this method.
Application.DoEvents() will yield a window which responds as expected in 100 ms intervals. It may be a tad choppy (threads would be more responsive) but it is very easy to put it in and is often sufficient.
for (int i = 0; i < 100; i++)
{
objTextBox.Text = i.ToString();
Application.DoEvents();
Thread.Sleep(100);
}
Here's my shot at a simple example:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Action countUp = this.CountUp;
countUp.BeginInvoke(null, null);
}
private void CountUp()
{
for (int i = 0; i < 100; i++)
{
this.Invoke(new Action<string>(UpdateTextBox), new object[] { i.ToString() });
Thread.Sleep(100);
}
}
private void UpdateTextBox(string text)
{
this.textBox1.Text = text;
}
}