C# - Inconsistent timing with Progress Bars and Timers - c#

I'm trying to make a smooth progress bar that "loads" from 0 to 100 based on a time value (in seconds).
So far, I've got a method that sets up the progress bar's maximum value to 100 * loadTime. Additionally, I've got a timer in my form, and its increment is 10 milliseconds. The idea is that the progress bar's maximum, divided by the timer's increment, will equal the number of seconds the progress bar needs to load.
Unfortunately, there are some inconsistencies with my timer. For instance, if I set my timer's increment to 1000 milliseconds and the bar's maximum to loadTime, it will be somewhat consistent, but it doesn't do anything for the first second. It's also very jittery. At 100 milliseconds and 10 * loadTime, it's slightly more consistent, but still very jittery. 10 milliseconds seems to be the sweet spot in terms of smoothness, however, if for instance, loadTime is equal to 5, it will load the progress bar in approximately 7 or 8 seconds.
I have also tried setting the timer's increment to 1, and my bar's maximum to 1000 * loadTime, however, this just makes it slower, and results in times of 10-13 seconds, when it should be 5 for instance.
Why's this the case? Can anything be done about it?
DisplayTimeProgressBar.cs (credit to Crispy's answer)
[DesignerCategory("code")]
public class DisplayTimeProgressBar : ProgressBar
{
public DisplayTimeProgressBar()
{
this.SetStyle(ControlStyles.UserPaint, true);
}
protected override void OnPaint(PaintEventArgs e)
{
Rectangle rec = e.ClipRectangle;
rec.Width = (int)(rec.Width * ((double)Value / Maximum));
rec.Height = rec.Height;
e.Graphics.FillRectangle(Brushes.Aquamarine, 0, 0, rec.Width, rec.Height);
}
}
Form.Designer.cs
private System.Windows.Forms.Timer progressBarTimer = new System.Windows.Forms.Timer(this.components);
private DisplayTimeProgressBar displayTimeProgressBar = new DisplayTimeProgressBar();
Form.cs
private void loadBar(int timeToDisplay)
{
this.displayTimeProgressBar.Visible = true;
this.displayTimeProgressBar.Maximum = timeToDisplay * 100;
this.progressBarTimer.Start();
}
private void progressBarTimer_Tick(object sender, EventArgs e)
{
if (this.displayTimeProgressBar.Value >= this.displayTimeProgressBar.Maximum)
{
this.displayTimeProgressBar.Value = 0;
this.displayTimeProgressBar.Visible = false;
this.progressBarTimer.Stop();
}
else
{
this.displayTimeProgressBar.Value++;
}
}
The following is a visual demonstration of my issue. The parameter loadTime has been set to 5 (5 seconds). In this demo, the timer's increment is 10, and the bar's maximum is 500.

If you want a continous animation over a specifc duration, wouldn't you have to base it on frames per second (i.e. 40 fps), as well as taking into consideration the maximum width in pixels of the monitor, because the Value corresponds to the number of pixels filled. In other words, using 100 for the Maximum value doesn't make sense. So then sleep 25 millis, calculate the true elapsed time (current time minus start time), divide the elapsed time by the total duration. Now you have a percentage. Multiply that percentage by the Maximum value, and that is the current Value. Also, setting pb.Style = ProgressBarStyle.Continuous; seems to look better, but it may not apply to your situation since you are using a custom paint. Example:
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Form f5 = new Form();
ProgressBar pb = new ProgressBar { Dock = DockStyle.Top, Minimum = 0, Maximum = 2000 };
pb.Style = ProgressBarStyle.Continuous;
double durationSeconds = 5;
Button btnStart = new Button { Text = "Start", Dock = DockStyle.Bottom };
double fps = 40; // refreshes per second
btnStart.Click += delegate {
int maxValue = pb.Maximum;
Thread t = new Thread(() => {
DateTime utcNow = DateTime.UtcNow;
int sleepMillis = (int) (1000 / fps);
int progress = 0;
while (true) {
Thread.Sleep(sleepMillis);
DateTime utcNow2 = DateTime.UtcNow;
double elapsedSeconds = (utcNow2 - utcNow).TotalSeconds;
int newProgress = (int) Math.Round(maxValue * elapsedSeconds / durationSeconds);
if (newProgress >= maxValue) {
pb.BeginInvoke((Action) delegate {
pb.Value = maxValue;
});
break;
}
else if (newProgress > progress) {
pb.BeginInvoke((Action) delegate {
pb.Value = newProgress;
});
}
progress = newProgress;
}
});
t.IsBackground = true;
t.Start();
};
f5.Controls.Add(pb);
f5.Controls.Add(btnStart);
Application.Run(f5);

Related

Using a timer and milliseconds to fade a form away but the duration is doubled

I am making my own little custom message box, essentially its just a small box showing up with a message for X amount of time, and then fades away for Y duration.
What is happening tho is that the fade away takes double as long as its supposed to and I cannot figure out why. Can someone look at my code and spot why its taking double the amount of time to fade the form away than it is expected?
//Initiate and set up the message bubble.
public static void InitiateBubble(String displayText, Double showTime = 1000, Double fadeTime = 2000) {
Bubble bubble = new Bubble(displayText);
bubble.showTime = showTime;
bubble.fadeTime = fadeTime;
bubble.Show();
bubble.showTimer = new Timer();
bubble.showTimer.Interval = (int)bubble.showTime;
bubble.showTimer.Tick += bubble.startFadeAway;
bubble.showTimer.Start();
}
//Leaves some time on screen before starting to fade away
private void startFadeAway(object sender, EventArgs e) {
showTimer.Stop();
fadeAwayTimer = new Timer();
fadeAwayTimer.Interval = 10;
fadeAwayTimer.Tick += fadeAway;
fadeAwayTimer.Start();
}
//slowly fades the contorle away until it disapears.
private void fadeAway(object sender, EventArgs e) {
double opacity = Opacity;
opacity -= (10 / fadeTime);
if (opacity < 0) {
Close();
}
else {
Opacity = opacity;
}
}
If the user sets the fade interval to 1 second (1000 milliseconds), and we set the timer interval to 1/10th of a second (100 milliseconds), then we need to fade the opacity by 10% every interval (since the interval is triggered 10 times per second). So we would set Opacity -= .1 on each iteration.
If the user sets the fade interval to 2 seconds (2000 milliseconds), and we still have the timer interval set to 1/10th of a second, then we need to fade the opacity by only 5% every interval, so we would set Opacity -= .05 on each iteration.
Seeing this relationship, we can discover that:
var amountToReduceOpacity = 1.0 / fadeTime * interval;
Note: as γηράσκω δ' αεί πολλά διδασκόμε mentioned above, the resolution on the winform timer is around 17 milliseconds, so if we set it to less than this, the fade will slow down dramatically because we will have calculated the rate for a very fast timer (meaning that we won't fade very much each iteration), but it will execute more slowly. On my machine, setting it to 50 looks just fine.
Now we can use this formula to always fade the form by the correct amount each interval. Here's a sample Form that does basically what you're doing above (note that I dropped a label and two timers on the form, and named them: lblDisplay, showTimer, and fadeTimer):
public partial class Bubble : Form
{
private readonly double amountToReduceOpacity;
private readonly int fadeInterval = 50;
// Custom form constructor takes in all three required settings
public Bubble(string displayText, int showTime, int fadeTime)
{
InitializeComponent();
lblDisplay.AutoSize = true;
lblDisplay.Text = displayText;
lblDisplay.Left = ClientRectangle.Width / 2 - lblDisplay.Width / 2;
lblDisplay.Top = ClientRectangle.Height / 2 - lblDisplay.Height / 2;
showTimer.Interval = showTime;
fadeTimer.Interval = fadeInterval;
amountToReduceOpacity = 1.0 / fadeTime * fadeInterval;
}
// The Shown event starts the first timer
private void Bubble_Shown(object sender, EventArgs e)
{
showTimer.Start();
}
// The shownTimer starts the fadeTimer
private void showTimer_Tick(object sender, EventArgs e)
{
showTimer.Stop();
BackColor = Color.Red; // Just so we see when the fade starts
fadeTimer.Start();
}
// The fade timer reduces opacity on each iteration until it's zero
private void fadeTimer_Tick(object sender, EventArgs e)
{
Opacity -= amountToReduceOpacity;
if (Opacity <= 0) Close();
}
}
Then on the client side we can just do something like:
private void button1_Click(object sender, EventArgs e)
{
Bubble bubble = new Bubble("Help me, I'm Fading!", 1000, 2000);
bubble.Show();
}

C# rolling numbers Windows forms

I would like to create "rolling numbers" on a windows form for about 10 seconds.
I've tried with loops but i got problems with refresh (the forms freezes and updates when the loop is done) updating a text label.
It would be nice when it looks like this https://youtu.be/Q7JmiCAAqu0 (made on console)
Sorry for bad english ^^
Drag one Label on your Form and one Timer.
Insert the following code after 'InitializeComponent();
const int maximum = 100;
int actual = 0;
timer1.Interval = 100;
timer1.Enabled = true;
timer1.Tick += (sender, args) =>
{
label1.Text = (actual++ % maximum).ToString();
};
Explanation:
Label is used to show the rolling numbers
Timer is used to perform an operation every N miliseconds (in our case every 100ms = 0.1s)
const int maximum = 100; // defines the maximum number we want to show
int actual = 0; // represents the actual number we are showing at a time
timer1.Interval = 100; // interval after which the timer1.Tick is called (in our case 100ms)
timer1.Enabled = true; // enables the timer, without it the Tick would not get called
timer1.Tick += (sender, args) =>
{
label1.Text = (actual++ % maximum).ToString(); // set the text to: actual + 1 modulo 100
};
EDIT: Information about modulo: https://en.wikipedia.org/wiki/Modulo_operation

Why the progressBar never reaches 100%?

What i wanted to do is that if i check if counter == 10 then the progressBar will jump by 10's untill 100.
If i will make if counter == 20 then the progressBar should jump by 20's untill 100.
private void NewsUpdate()
{
counter += 1;
progressBar1.Value = (int)Math.Round((counter / 10f) * 100);
label9.Text = counter.ToString();
label9.Visible = true;
if (counter == 10)
{
Extractions();
counter = 0;
progressBar1.Value = 0;
}
}
Im calling this method in a timer tick event the timer1 interval is set to 1000ms
What happen now is that the progrsssBar1 move by 10's getting to 90% after 9 times it's moving back to 0 to value 0. Wht it's not getting to 100% to the end ?
Do this instead:
PprogressBar1.Maximum = YourMaximumValue; // like for example 1,000,000
and then increment by 1
Counter += 1;
ProgressBar1.Value = Counter;
Your formula in your question results in a decimal which is always less than 100 (Like 98.55).
When the counter equals 10, the progress is 100%. However, you're setting the progressBar1.Value = 0; so it never reaches 100% before the UI can update.

How do I make the progressBar1 to progress each time by the time I set to timer interval to?

I have this method that i call it from a timer tick:
private void NewsUpdate()
{
counter += 1;
progressBar1.Value = counter * 10;
progressBar1.Value = counter;
label9.Text = counter.ToString();
label9.Visible = true;
if (counter == 10)
{
client.Encoding = System.Text.Encoding.GetEncoding(1255);
page = client.DownloadString("http://rotter.net/scoopscache.html");
TextExtractor.ExtractDateTime(page, newText, dateTime);
StreamWriter w = new StreamWriter(#"d:\rotterhtml\rotterscoops.html");
w.Write(page);
w.Close();
TextExtractor.ExtractText(#"d:\rotterhtml\rotterscoops.html", newText, dateTime);
combindedString = string.Join(Environment.NewLine, newText);
counter = 0;
}
}
The progressBar1 is set from 0 to 100.
I want that if I make:
if (counter == 10)
Then the progressBar1 will move to the end after 10 seconds.
If I set it to 50 then it's 50 seconds so the progressBar1 should move 50 seconds until the end.
The timer1 in the designer is set to 1000ms
For example now the way it is:
counter += 1;
progressBar1.Value = counter * 10;
progressBar1.Value = counter;
label9.Text = counter.ToString();
label9.Visible = true;
if (counter == 10)
It will count to 10 the progressBar1 will move by 10 and then will return to the beginning.
I want to that if I change it to 10 50 or 33 the progressBar1 will know to move until the end according to the seconds counted.
If its 33 then count 33 and move the progressBar1 to the end after 33 seconds.
What i want to do is that if i set the IF == 10 then the progressBar will Increment 10 seconds * 10 steps Increment so in 10 seconds the progressBar will get to 100 to the end. Then over again ( this timer is update it should keep running all the time i don't want to stop it i just check if 10 seconds passed make update. And i also want that the progressBar will Increment according to the limit i set in this case 10 untill the end. If its == 10 then the progressBar should Increment each second by 10. And if i set it to == 50 for example then the progressBar should Increment by 2 each s
You want a counter with n steps, where n is the number of seconds to completion.
There are two ways to approach this:
(Preferred) Set the Maximum property of the ProgressBar. This will make everything automatic!
counter++;
progressBar1.Maximum = 33; //This could be set in the designer, or on init
progressBar1.Value = counter;
Calculate the interval yourself. Do this by dividing the counter by the total number of seconds, then multiplying by the range (100 in this case).
counter++;
progressBar1.Value = (int)Math.Round((counter / 33f) * 100);
All of your other code sounds fine based on your comment.

Is that 60 frames per second?

public PbsWheel(AnimatedPictureBox.AnimatedPictureBoxs[] pbs, AnimatedPictureBox.AnimatedPictureBoxs pb, int delta,Label label2)
{
for (int i = 0; i < pbs.Length; i++)
{
if (delta > 0)
{
pbs[i].AnimateRate += 1/60 * 1000;
1/60 * 1000 is 60 frames per second ?
This is how i animate the pictureBoxes the images inside. Im using timer for each picturebox:
public class AnimatedPictureBoxs : PictureBox
{
public static bool images;
List<string> imageFilenames;
Timer t = new Timer();
public AnimatedPictureBoxs()
{
images = false;
AnimateRate = 100; //It's up to you, the smaller, the faster.
t.Tick += Tick_Animate;
}
public int AnimateRate
{
get { return t.Interval; }
set { t.Interval = value; }
}
public void Animate(List<string> imageFilenames)
{
this.imageFilenames = imageFilenames;
t.Start();
}
public void StopAnimate()
{
t.Stop();
i = 0;
}
int i;
private void Tick_Animate(object sender, EventArgs e)
{
if (images == true)
{
imageFilenames = null;
}
if (imageFilenames == null)
{
return;
}
else
{
try
{
if (i >= imageFilenames.Count)
{
i = 0;
}
else
{
Load(imageFilenames[i]);
i = (i + 1) % imageFilenames.Count;
}
}
catch (Exception err)
{
}
}
}
The rate is set to 100 what i want to do is to display and when i move the mouse wheel up down to change the speed of the images animate by frames per second.
pbs is array of pictureBoxes.
pbs[i].AnimateRate += 1/60 * 1000;
Now, AnimateRate is an integer property. It is very badly named. It is not a rate. It is a timer interval. In mathematical terms it is a period. Naming it rate makes it sound as though it will be a rate, or a frequency.
The mathematical relationship between period T and frequency f is:
T = 1/f
So, here's what you should do:
Rename the property as AnimationInterval.
When you need to convert a frequency (i.e. frame rate) to an interval use the formula above.
Note that you need to account for the fact that your frequencies are measured in frames per second, but your intervals are measured in milli-seconds. So your code should be:
pbs[i].AnimationInterval += 1000/60;
That looks very similar to what you had but there is a subtle difference. In mathematics, the formulae are identical. But in C#, the behaviour of the / operator depends on the types of its operands. You supply two integers and so / is integer division. And the result of 1/60 is zero. So your code does not modify the property.
I do think that you will need to modify your logic a little. As it stands, your raw data is an interval. But actually what you wish to control if frame rate. So I believe that you should maintain a variable that holds the frame rate. If you want to modify it, then make the modifications to the frame rate variable. And then set the interval like this:
pbs[i].AnimationInterval = 1000/frameRate;

Categories