I want to display text on a ProgressBar, (without all the nonsense of a custom progress bar). This is not hard to do, and does not involve the OnPaint method -- as demonstrated with button1 of the following code. However, this method blocks the UI thread, which is evil.
Unfortunately, my best stab at an asynchronous approach causes the text to flicker, which is very annoying.
Can somebody show me how to update the text asynchronously without the flicker?
(To run the following code, just paste it into a new project
containing a Form with 3 Buttons and 3 ProgressBars).
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Threading.Tasks;
using System.Threading;
namespace ProgBarTest //change namespace in Program.cs accordingly
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
//This method will block the main UI thread
// (thus freezing the other buttons)
StartUpdate(this.progressBar1);
}
private void button2_Click(object sender, EventArgs e)
{
//this method (and the next) don't block the UI, and can be used concurrently,
// but the text flickers
Task t = new Task(() => StartUpdate(this.progressBar2));
t.Start();
}
private void button3_Click(object sender, EventArgs e)
{
Task t = new Task(() => StartUpdate(this.progressBar3));
t.Start();
}
private void StartUpdate( ProgressBar progBar)
{
for (int i = 1; i <= 100; i++)
{
UpdateProgressBar(i, progBar);
Thread.Sleep(100);
}
}
private void UpdateProgressBar(int i, ProgressBar progBar)
{
if (progBar.InvokeRequired) //for use with background Task
{
progBar.Invoke(new Action<int, ProgressBar>(UpdateProgressBar),
new Object[] { i, progBar });
}
else
{
//set progress bar:
progBar.Value = i;
progBar.Refresh();
//set status label:
string percentStr = i.ToString() + "%";
PointF location = new PointF(progBar.Width / 2 - 10, progBar.Height / 2 - 7);
using (Graphics gr = progBar.CreateGraphics())
{
gr.DrawString(percentStr, new Font("Arial",10), Brushes.Red, location );
}
}
}
}
}
A custom ProgressBar would be your best bet.. if you set it up correctly. I know I'm not answering your question, just offering a different solution. I haven't tested this yet, but it would in theory draw the percentage every time the progress bar had to repaint, which would happen every time the value was changed.
Your current problem is that the bar is repainting itself every time you change the value, thus the flickering of the text. If it didn't repaint, you would see percentage you draw starting to overlay on top of each other which is not good either.
This would be better to encapsulate everything within one control from a design standpoint as well.
class CustomProgressBar : ProgressBar {
public CustomProgressBar() : base() {}
protected override void OnPaint(PaintEventArgs e) {
// Call the OnPaint method of the base class.
base.OnPaint(e);
string percentStr = this.Value.ToString() + "%";
PointF location = new PointF(this.Width / 2 - 10, this.Height / 2 - 7);
// Call methods of the System.Drawing.Graphics object.
e.Graphics.DrawString(percentStr, new Font("Arial",10), Brushes.Red, location );
}
}
It's probably because you're drawing the string from another thread. If you can use a Control based text element instead you can use BeginInvoke in the same way as the ProgressBar:
// Label progressLbl
private void UpdateProgressFromAnotherThread(int completed, int total, string progressText)
{
this.progBar.BeginInvoke(new Action(() =>
{
this.progBar.Maximum = total;
this.progBar.Value = completed;
}));
this.progressLbl.BeginInvoke(new Action(() =>
{
this.progressLbl.Text = progressText;
}));
}
Related
I have the following code which read a file and also increment the progress bar while reading it, but I don't see any activity in my progressBar. Why is this?
progressBar1.Minimum = 0;
progressBar1.Maximum = (int)fileStream.Length + 1;
progressBar1.Value = 0;
using (fileStream)
{
fileStreamLength = (int)fileStream.Length + 1;
fileInBytes = new byte[fileStreamLength];
int currbyte = 0, i = 0;
var a = 0;
while (currbyte != -1)
{
currbyte = fileStream.ReadByte();
fileInBytes[i++] = (byte)currbyte;
progressBar1.Value=i;
}
}
It is incrementing but you cannot see it. It is caused by running your loop in UI thread.
Look for BackGroundWorker or async/await pattern.
User Method Invoker to update the UI...
try this...
Do all the your work in a thread and when updating the progressbar use the following lines...
For Windows Forms
this.Invoke((MethodInvoker) delegate
{
progressBar1.value=i;
});
For WPF
Dispatcher.BeginInvoke(new Action(delegate
{
progressBar1.value=i;
}));
your best option will be Background Worker.
drag and drop a BackgroundWorker from toolbox. then you have to implement 2 function: one is doing the background work, another is for reporting to UI.
using System.ComponentModel;
using System.Threading;
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)
{
// begin reading your file here...
// set the progress bar value and report it to the main UI
int i = 0; // value between 0~100
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();
}
}
This question is unlikely to help any future visitors; it is only relevant to a small geographic area, a specific moment in time, or an extraordinarily narrow situation that is not generally applicable to the worldwide audience of the internet. For help making this question more broadly applicable, visit the help center.
Closed 9 years ago.
I'm new to C#, just beginning to write some codes.
I have something in mind but before proceeding to it, need some help on this matter.
How to exit a running process by detecting a mouse click?
I wrote some lines but when compiled and running, the mouse click has no effect at all.
Can someone please take a look and help me?
Here's my lines...
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;
namespace Graphic_test_1
{
public partial class Form1 : Form
{
public static Single lim_x, lim_y; // limits in X & Y
public static int dly = 45;
System.Drawing.Pen myPen = new System.Drawing.Pen(System.Drawing.Color.Blue); // color of the pen
System.Drawing.Graphics Canvas;
public Boolean clik = false; // initialize
public string mystring;
public Form1()
{
InitializeComponent();
}
protected override void OnMouseClick(MouseEventArgs e)
{
base.OnMouseClick(e);
clik = true;
lim_x = e.X;
lim_y = e.Y;
}
private void btgo_Click(object sender, EventArgs e) // Start drawing [GO]
{
Canvas = this.CreateGraphics();
btgo.Enabled = false;
MessageBox.Show("Checking the limits of the canvas.\r\n" +
"Click anywhere to stop and find out X position",
"Watching Coordinates",
MessageBoxButtons.OK, MessageBoxIcon.Information);
btgo.Visible = false;
btend.Enabled = false;
myPen.Color=System.Drawing.Color.Red; // color of the pen
myPen.Width = 2; // pen width
System.Drawing.Font drawfont = new System.Drawing.Font("Arial", 10); // Graphics font
System.Drawing.SolidBrush mybrush = new System.Drawing.SolidBrush(System.Drawing.Color.Black); // color for the font
System.Drawing.Color background;
background = this.BackColor;
lim_x = 0; // initialize
do
{
Canvas.DrawLine(myPen, 0, 200, lim_x, 200);
mystring = "Current X = " + lim_x.ToString();
mybrush.Color = System.Drawing.Color.Black;
Canvas.DrawString(mystring, drawfont, mybrush, 351, 334);
System.Threading.Thread.Sleep(dly);
mybrush.Color = background; // use the background color
Canvas.FillRectangle(mybrush, new Rectangle(350, 333, 500, 353));
if (clik)
{
mybrush.Color = background; // use the background color
Canvas.FillRectangle(mybrush, new Rectangle(350, 333, 500, 353));
mystring = "Current X = " + lim_x.ToString();
mybrush.Color = System.Drawing.Color.Black;
Canvas.DrawString(mystring, drawfont, mybrush, 351, 334);
MessageBox.Show("Final position in X = " + lim_x.ToString(),
"Mouse click detected",
MessageBoxButtons.OK, MessageBoxIcon.Stop);
break;
}
else
lim_x++;
} while (lim_x < 611);
myPen.Dispose(); // release the pen
mybrush.Dispose(); // release the brush
Canvas.Dispose(); // release the canvas
btend.Enabled = true;
btend.Focus();
}
private void btend_Click(object sender, EventArgs e) // quit program [END]
{
Dispose(); // program ends.
}
}
}
If you want to close your form, use this.Close(). if you want to exit your application, you can use Application.Exit().
If I understand well, you are trying to stop the process it are running. (e.g. The btgo click event).
To do this, you should use an individual Thread to execute the process.
Why Thread ?
Your application will run two different process :
The main Thread
The second Thread
So, it will be possible to recognize the "Mouse Click" while you are running the second Thread.
E.g. I have one Button and one Label. I want, on the Button1 click,create and start a Thread. This Thread will loop 10000 time and modify the Label1 text. But when I click on the label, the loop will stop :
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Threading;
public partial class Test : Form
{
Thread thread;
public Test()
{
InitializeComponent();
}
void Button1_Click(object sender, EventArgs e)
{
thread = new Thread(new ThreadStart(StartThread));
thread.Start();
}
private void StartThread()
{
for(int i =0;i<1000000;i++)
{
Thread.Sleep(1000);
Label1.Invoke((MethodInvoker)(() => Label1.Text = i.ToString()));
//See the more information section, I will post a link about this.
}
}
void Label1_Click(object sender, EventArgs e)
{
thread.Abort();
}
}
More information :
You should read about how to properly close a thread here.
Invoke method here.
I have four PictureBoxes (each PictureBox represents one dice) and a Timer that changes every 100ms source pictures (loaded in memory as List<Bitmap> imagesLoadedFromIncludedResources).
Code:
private List<PictureBox> dices = new List<PictureBox>();
private void timer_diceImageChanger_Tick(object sender, EventArgs e)
{
foreach (PictureBox onePictureBox in dices)
{
oneDice.WaitOnLoad = false;
onePictureBox.Image = //... ;
oneDice.Refresh();
}
}
I need to change all the images at once - at this moment, you can see that the images are changing from left to right with a small delay.
I tried variant with one Thread for each PictureBox (using Control.Invoke method from this answer) - it is visually little better but not perfect.
You can try to suspend form's layout logic:
SuspendLayout();
// set images to pictureboxes
ResumeLayout(false);
Parallel.ForEach
(
dices,
new ParallelOptions { MaxDegreeOfParallelism = 4 },
(dice) =>
{
dice.Image = ...;
dice.WaitOnLoad = false;
dice.Refresh();
}
);
The problem is that UI controls can only be accessed from the UI thread. If you want to use this approach, you must create a copy of your PictureBoxes and then replace the UI ones once the operation is done.
Another approach would be creating two PictureBoxes, with the first one just on the top of the other one (hiding the latter)... you change all the images and then, once the processing is complete, you iterate all the ones in the back putting them on the top which would result in a lesser delay.
I'd approach this differently - it's been a while since I've played with WinForms stuff, but I'd probably take more control over the rendering of the images.
In this example I've got the images all in one source bitmap stacked vertically, stored as a resource in assembly:
namespace WindowsFormsApplication1
{
public partial class Form1 : Form
{
private Timer timer;
private Bitmap dice;
private int[] currentValues = new int[6];
private Random random = new Random();
public Form1()
{
InitializeComponent();
this.timer = new Timer();
this.timer.Interval = 500;
this.timer.Tick += TimerOnTick;
this.dice = Properties.Resources.Dice;
}
private void TimerOnTick(object sender, EventArgs eventArgs)
{
for (var i = 0; i < currentValues.Length; i++)
{
this.currentValues[i] = this.random.Next(1, 7);
}
this.panel1.Invalidate();
}
private void button1_Click(object sender, EventArgs e)
{
if (this.timer.Enabled)
{
this.timer.Stop();
}
else
{
this.timer.Start();
}
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
e.Graphics.Clear(Color.White);
if (this.timer.Enabled)
{
for (var i = 0; i < currentValues.Length; i++)
{
e.Graphics.DrawImage(this.dice, new Rectangle(i * 70, 0, 60, 60), 0, (currentValues[i] - 1) * 60, 60, 60, GraphicsUnit.Pixel);
}
}
}
}
}
The source is here if it helps: http://sdrv.ms/Wx2Ets
I am creating some files from xml data in the background using
Task.Factory.StartNew(() => xmlconvert(xx, yy));
Now, the question is how to show the progress of this method using a StatusStrip control with some message and the progress or at least just a scrolling animation for the progress. I don't just have any idea how would it work.
Update:
First of all, this method 'xmlconvert(xx, yy)' has four different forms depends on the condition user selects at runtime.
In the main form of my application user can select from different conditions to process on the data. Then finally when user click on the Button 'Create' all these conditions are being checked and a suitable method will be called within that button click event. I need to show the progress of this method which is being invoked at runtime.
private void btnCreateRelease_Click(object sender, EventArgs e)
{
// Checks set of conditions
if(cond 1)
{
xmlconvert_1();
}
else if (cond2)
{
xmlconvert_2();
}
else if (cond3)
{
xmlconvert_3();
}
else if (cond4)
{
xmlconvert_4();
}
}
I want to show progress of one of these methods which will be invoked at runtime depends on the condition.
Thanks a lot.
You can use the BackgroundWorker for this, and it's pretty simple, too. Here's a sample to get you going:
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
}
void Form1_Shown(object sender, EventArgs e)
{
backgroundWorker1.RunWorkerAsync();
}
void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// Do your work in here.
xmlconvert(xx, yy);
for (int i = 0; i <= 100; i++)
{
backgroundWorker1.ReportProgress(i);
System.Threading.Thread.Sleep(100);
}
}
void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
}
And here's the link to the documentation.
To get it to work in your scenario, I would suggest you add a Progress bar to your StatusStrip control and update it from within the backgroundWorker1_ProgressChanged event.
If you wish just to show, that your app is not hang may help following approach:
public static class ActionExtensions
{
public static void RunWithMargueProgress(this Action action)
{
var progressForm = new ProgressForm();
progressForm.Show();
Task.Factory.StartNew(action)
.ContinueWith(t =>
{
progressForm.Close();
progressForm.Dispose();
}, TaskScheduler.FromCurrentSynchronizationContext());
}
}
Where ProgressForm would be a simple form with ProgressBar, that is set to Marquee style. If you have idea, how it is progressing, it is better to show progress for user and use BackgroundWorker.
As long as it's parameter is Action, it is easily reusable.
Usage would be:
private void button_Click(object sender, EventArgs e)
{
Action action = () => Thread.Sleep(5000);
action.RunWithMargueProgress();
}
If you have control in status strip, that you wish to animate, you can do it like this:
public static void RunWithMargueProgress(this Action action, ToolStripProgressBar progressBar)
{
progressBar.Style = ProgressBarStyle.Marquee;
progressBar.MarqueeAnimationSpeed = 30;
Task.Factory.StartNew(action)
.ContinueWith(t =>
{
progressBar.MarqueeAnimationSpeed = 0;
progressBar.Style = ProgressBarStyle.Continuous;
}, TaskScheduler.FromCurrentSynchronizationContext());
}
Usage would be pretty much the same:
private void button_Click(object sender, EventArgs e)
{
Action action = () => Thread.Sleep(5000);
action.RunWithMargueProgress(ToolStripProgressBar);
}
I have a Winforms app in C# that calls calls a method asynchronously and uses a callback.
I would like to display an animated gif to let the end user know that work is being done.
I would like to have the animated gif hover over the center of the form.
How can I do this?
Update:
Thanks. I guess the step I was missing was to use a Picture Box to hold the gif.
The following seems to be doing the trick of showing the gif and like jmatthews3865 said below I can just set the visible property of the PictureBox to false to hide it.
private ShowAnimatedGif()
{
PictureBox pb = new PictureBox();
this.Controls.Add(pb);
pb.Left = (this.Width / 2) - (pb.Width / 2);
pb.Top = (this.Height / 2) - (pb.Height / 2);
pb.Image = Resources.AnimatedGifHere;
pb.Visible = true;
}
in your form, simply include the image with it's visible property set to false.
from the event which calls the long running async process (button1_click etc.), set the images visibility property to true. event fires, image appears, async process runs and your ui thread should still be responsive.
in your callback event set the images visible property to false to indicate that the process is complete.
Need some code to give an exact answer, but this is fairly trivial, insert the gif before you make the asynchronous call, then remove it in the callback.
This is the answer. I'm using LoadingCircle which is an animated gif component.
public partial class Form1 : Form
{
public delegate void ProcessAnimation(bool show);
ProcessAnimation pa;
public Form1()
{
InitializeComponent();
pa = this.ShowAnimation;
}
private void button2_Click(object sender, EventArgs e)
{
Thread tr = new Thread(FlushToServer);
tr.Start();
}
private void ShowAnimation(bool show)
{
if (show)
{
loadingCircle1.Visible = true;
loadingCircle2.Active = true;
}
else
{
loadingCircle1.Visible = false;
loadingCircle1.Active = false;
}
}
private void FlushToServer()
{
this.Invoke(this.pa,true);
//your long running process
System.Threading.Thread.Sleep(5000);
this.Invoke(this.pa,false);
}
}
i modify the above code a bit and it will not throw error "c# invoke or begininvoke cannot be called on a control until the window handle has been created."
namespace AnimateUI
{
public partial class Form1 : Form
{
public delegate void ProcessAnimation(bool show);
ProcessAnimation pa;
public Form1()
{
InitializeComponent();
pa = this.ShowAnimation;
pictureBox1.Visible = false;
}
private void ShowAnimation(bool show)
{
if (show)
{
pictureBox1.Visible = true;
}
else
{
pictureBox1.Visible = false;
}
}
private void button1_Click(object sender, EventArgs e)
{
Thread tr = new Thread(StartTask);
tr.Start();
}
private void StartTask()
{
if (!this.IsHandleCreated)
this.CreateControl();
this.Invoke(this.pa, true);
System.Threading.Thread.Sleep(15000);
this.Invoke(this.pa, false);
}
}
}