How to control frame rate in WPF by using dispatcher timer accurately? - c#

I have encountered a problem when I tried to control frame rate in WPF by using System.Windows.Threading.DispatcherTimer instance.
In order to try effectivity of DispatcherTimer, I created a simple WPF Demo with a single window which has a textbox and a button. When the button is clicked, a DispatcherTimer instance begins to tick according to the double number in the textbox as the interval and a StopWatch starts at the same time, a counter variable increases by 1 on every DispatcherTimer tick. When StopWatch.ElaspedMilliSeconds > 1000 (more than 1 second passed), the timer stops ticking and the stopwatch resets as well, a message box pops up to show value of the counter.
So the counter value is supposed to be around 30 if I input 33.3(1000/30). But the result turns out to be around 20. I ask for help whether there is anyone can help check what seems to be the problem in my source code below. Thanks in advance.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Navigation;
using System.Windows.Shapes;
namespace TimerDemo
{
public partial class MainWindow : Window
{
private System.Windows.Threading.DispatcherTimer _timer = new System.Windows.Threading.DispatcherTimer();
public MainWindow()
{
InitializeComponent();
double ticks = 0L;
System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch();
int frameCount = 0;
//There is a textbox "txtTicks" which accepts a millisecond value
//And a button "btn", by clicking the button the dispatchertimer &
//stopwatcher are started.
_timer.Tick += (sender, e) =>
{
frameCount++;
System.Diagnostics.Debug.WriteLine(watch.ElapsedMilliseconds);
if (watch.ElapsedMilliseconds > 1000)
{
_timer.Stop();
watch.Reset();
MessageBox.Show(string.Format("Already 1 second! FrameCount: {0}", frameCount));
frameCount = 0;
}
};
this.btn.Click += (sender, e) =>
{
double.TryParse(this.txtTicks.Text, out ticks);
if (ticks != 0.0)
{
_timer.Interval = TimeSpan.FromMilliseconds(ticks);
}
_timer.Start();
watch.Start();
};
}
}
}
Running result as below(Rookie of Stackoverflow, cannot upload image yet):
http://i.imgur.com/Bkp1uam.png

Since WPF (and most other rendering engines) does not take an equal time amount to render every frame, and since different computations can further delay the interval between frames, you are right in using CompositionTarget.Rendering to "clock" the rendering intervals, but you can still use a "wall clock" timer to update your game at regular, rigorously controlled intervals.
In the excellent book "Game Programming Patterns" (freely available online) you can find the very useful Game Loop Pattern.
In its full form, this is what you would put inside a handler for the CompositionTarget.Rendering event (in pseudo-code):
double current = getCurrentTime();
double elapsed = current - previous;
previous = current;
lag += elapsed;
processInput();
while (lag >= MS_PER_UPDATE)
{
update();
lag -= MS_PER_UPDATE;
}
render();
In C#, you could implement getCurrentTime() either by calling DateTime.Now or by using Stopwatch.Elapsed, for example.

Related

DateTime class as Stopwatch

my school project is to make a c# aplication with one button start/stop that starts to count the time and by clicking again stops count the time.
When i use this 3 lines
txtVrijeme.Text = Convert.ToString(sada.AddSeconds(i).ToString("HH:mm:ss"));
Thread.Sleep(1000);
i++;
without loop (while,for,goto etc.) in forms apk. it works (display 00:00:00, and if i click again 00:00:01) but when I use loop it does not display anything. Why ? btw. console aplication works with this code without any problem(loop in console)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace BradičićManuelŠtoperica
{
public partial class Form1 : Form
{
DateTime sada = new DateTime(2009, 6, 22, 0, 0, 0);
int i = 0;
bool x = true;
public Form1()
{
InitializeComponent();
}
void execute(bool y)
{
while (y == true)
{
txtVrijeme.Text = Convert.ToString(sada.AddSeconds(i).ToString("HH:mm:ss"));
Thread.Sleep(1000);
i++;
}
}
private void btnStartStop_Click(object sender, EventArgs e)
{
if (x == false)
x = true;
else
x = false;
execute(x);
}
private void textBox1_TextChanged(object sender, EventArgs e)
{
}
}
}
excuse my English :)
Changing a textbox property does not directly draw anything to the screen.
Say it again:
Changing a textbox property does not directly draw anything to the screen.
What does happen is the Textbox control will invalidate it's graphics area, which will cause a WM_PAINT message to be generated.
The program needs to process this WM_PAINT message before the screen can be updated. The thread that receives and processes your paint events is the same thread where your execute() method is running. It's also the same thread that would handle your btnStartStop_Click() method.
Unfortunately, the execute() method never finishes. It never lets execution on that thread fall back to your program's main message loop, so your program can't repaint the textbox and it can't handle the button click that might finally let the execute() method stop. Even when it pauses, it puts the whole thread to sleep.
What you need to do instead is use a Timer component, and handle the Timer's Tick event.
Additionally, timer events are not perfectly precise. The slight variance for each Tick event can add up over time, so rather than incrementing the current value for each event your program should remember the start time and compute the difference from the current time.
As others have said, part of the problem is that you're trying to execute a hard loop inside the same thread that's responsible for updating the UI. Instead of using a loop like this, it's better to use a Timer, because the timer will run in a different thread so your UI will remain responsive.
The Timer has an Interval property that defines how often it executes a task. In our case it will be updating the textbox text. I've set this value to 100 milliseconds in the code below. The timer also has a Tick event that will execute every time the Interval elapses. Inside this event is where we put the code to update the textbox text.
We should should use a Stopwatch to measure elapsed time, because that's what it was built for. The DateTime object is great for holding dates, but it's not as accurate for measuring time.
A stopwatch has a Start and a Stop method that we can call from the button click event, so inside that event all we have to do is first check if the stopwatch is running. If it is, then we Stop it. If it isn't, then we Start it.
Putting these ideas together, your code could look something like this:
// At the top of your file, you'll need this to access the Timer:
using System.Windows.Forms;
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private static Stopwatch stopwatch = new Stopwatch();
private static readonly Timer Timer = new Timer { Interval = 100 };
private void Form1_Load(object sender, EventArgs e)
{
btnStartStop.Text = "Start";
txtVrijeme.Text = stopwatch.Elapsed.ToString("hh\\:mm\\:ss\\.ff");
Timer.Tick += Timer_Tick; // This hooks up an event handler for the Tick event
}
// This code executes every {Timer.Interval} millisecond when the timer is running
private void Timer_Tick(object sender, EventArgs e)
{
txtVrijeme.Text = stopwatch.Elapsed.ToString("hh\\:mm\\:ss\\.ff");
}
// This method handles the button click. It changes the button text and starts
// or stops the stopwatch, depending on whether the stopwatch is running
private void btnStartStop_Click(object sender, EventArgs e)
{
if (stopwatch.IsRunning)
{
stopwatch.Stop();
Timer.Stop();
btnStartStop.Text = "Start";
}
else
{
Timer.Start();
stopwatch.Start();
btnStartStop.Text = "Stop";
}
}
}
Don't use DateTime for this. Instead, create a Stopwatch (in the System.Diagnostics namespace). You won't need to increment it yourself - just start it when the button is clicked, then evaluate its Elapsed property to get the duration elapsed (which is a TimeSpan type).
Yes, you'll also want a timer to periodically update the display. That timer can fire as often as you like, and can be imprecise - because it's not responsible for updating the stopwatch, just for writing the current value of the stopwatch to the display.
In summary:
DateTime is for representing a date and time of day.
DateTime.Now, DateTime.UtcNow, etc. give a mechanism to get the current date and time of day.
TimeSpan is for representing durations of elapsed time.
Stopwatch gives a mechanism for measuring elapsed time.
Separate the updating of the display from measurement of time. They are separate concerns.
Your project won't work because you never leave while.
You should try to use Timer.
Read about it here.
I think this video must help you.

Timer ticks with wrong values

I am developing a game in Windows Phone 8 SDK
and i need a countdown timer.
i implemented a Dispatcher timer , on the first time CLICK
The timer decrease with no errors !
but if i press RESET (Which it should reset to 60 SECONDS and start countdown)
it Resets to 60 BUT it Decreases "2 Seconds" every second !
and if i press one more time RESET , it Decreases by 3 Seconds every second
Sample code i wrote with the same idea of my app: (and same wrong
results)
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Navigation;
using Microsoft.Phone.Controls;
using Microsoft.Phone.Shell;
using PhoneApp3.Resources;
using System.Windows.Threading;
namespace PhoneApp3
{
public partial class MainPage : PhoneApplicationPage
{
private DispatcherTimer time = new DispatcherTimer(); // DISPATCHER TIMER
private int left;
// Constructor
public MainPage()
{
InitializeComponent();
}
//Starting Countdown
private void Start_Click_1(object sender, RoutedEventArgs e)
{
left = 60; // time left
time.Interval = TimeSpan.FromSeconds(1);
time.Tick += time_Tick;
time.Start();
}
void time_Tick(object sender, EventArgs e)
{
left--; // decrease
txt.Text = Convert.ToString(left); // update text
}
private void reset_Click(object sender, RoutedEventArgs e)
{
time.Stop();
Start_Click_1(null, null); // RE - START
}
}
}
Every time you press reset, and Start_Click_1 runs again, you're subscribing to time_Tick again:
time.Tick += time_Tick;
So after pressing Reset 3 times, you're subscribed 3 times, and the following line of code is running 3 times every time the tick event fires:
left--;
Move the subscription to your constructor:
public MainPage()
{
InitializeComponent();
time.Tick += time_Tick;
}
//Starting Countdown
private void Start_Click_1(object sender, RoutedEventArgs e)
{
left = 60; // time left
time.Interval = TimeSpan.FromSeconds(1);
time.Start();
}
As Hans said in the comments, you are incorrectly adding the event handler every time the button is clicked.
You should call this code
time.Interval = TimeSpan.FromSeconds(1);
time.Tick += time_Tick;
in the constructor, instead of the event handler.

Show the new form after Progress Bar percentage completed c# project

I am working on a project using Visual Studio(c#). I want to create a startup form when i install my application with a progress bar. And after progress bar completed this form should be hide and a new form should be open. can u help me about this problem?
Edit:
I've just made a sample application trying to use exactly the code that you've specified. It worked fine besides just one tweak:
Form1().Show(); should be new Form1().Show();
The only way this code does not execute is if you forgot to set timer1 to enabled state in design view which causes the code to never fire up.
Are you sure the code is firing up? have you done a break-point on this piece of code?
On a sidenote: timer1 is not on a separate thread so you don't need to use Invoke (you can see if you actually need it by looking InvokeRequired property of a control)
Suggested improvement: if you are not going to use Form2 again and judging from your code, it is likely you won't; perhaps you should call Close() on Form2 instead of Hide() and release the resources. I've had times when my application kept running in background because I hid the form but never closed it and application was on "exit when last window closes" which never happened.
So to be sure, here is the final code that does work on my machine:
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 WindowsFormsApplication1
{
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
//enable timer1 here or in designer
timer1.Enabled = true;
}
private void timer1_Tick(object sender, EventArgs e)
{
//disable timer1 first thing, otherwise it can end up ticking
//multiple times before you've had a chance to disable it
//if the timespan is really short
timer1.Enabled = false;
int d;
for (d = 0; d <= 100; d++)
progressBar1.Value = d;
Hide();
//create a new Form1 and then show it
new Form1().Show();
}
}
}
Create your form and add your progress bar
Set up event handlers on the parts of the form that should effect the progress bar
Update the progree bar to reflect the amount of work that is done
When the form is complete close it
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 WindowsFormsApplication1
{
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
private void timer1_Tick(object sender, EventArgs e)
{
int d;
for (d = 0; d <= 100; d++)
progressBar1.Value = d;
this.Hide();
Form1().Show();
timer1.Enabled = false;
}
}
}

How to delay program for a certain number of milliseconds, or until a key is pressed?

I need to delay my program's execution for a specified number of milliseconds, but also want the user to be able to escape the wait when a key is pressed. If no key is pressed the program should wait for the specified number of milliseconds.
I have been using Thread.Sleep to halt the program (which in the context of my program I think is ok as the UI is set to minimise during the execution of the main method).
I have thought about doing something like this:
while(GetAsyncKeyState(System.Windows.Forms.Keys.Escape) == 0 || waitTime > totalWait)
{
Thread.Sleep(100);
waitTime += 100;
}
As Thread.Sleep will wait until at least the time specified before waking the thread up, there will obviously be a large unwanted extra delay as it is scaled up in the while loop.
Is there some sort of method that will sleep for a specified amount of time but only while a condition holds true? Or is the above example above the "correct" way to do it but to use a more accurate Sleep method? If so what method can I use?
Thanks in advance for your help.
Edit ---- Possible Idea...
DateTime timeAtStart = DateTime.Now;
int maxWaitTime = 15000;
while (true)
{
if (GetAsyncKeyState(System.Windows.Forms.Keys.Escape) != 0)
{
break;
}
if ((DateTime.Now - timeAtStart).TotalMilliseconds >= maxWaitTime)
{
break;
}
}
This doesn't use any sort of timer but looks like it could work, any suggestions?
Edit 2: The above works for me and now allows me to break the wait when escape is pressed. I have noticed the delay is more accurate than using Thread.Sleep too!
Consider reversing the concepts... instead of delaying it for a certain time, think about starting execution in a certain time, or when a key is pressed.
Start a Windows Forms timer with a tick handler which will kick off whatever you want to happen, and also a key event handler which will start it and stop the timer.
First sample is using Timer, ManuelResetEvent and Global Keyboard hook:
I did not include keyboard hook code because it's too large. You can find it here.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Threading;
namespace WindowsFormsApplication1
{
static class Program
{
private static System.Threading.Timer _timer;
private static ManualResetEvent _signal;
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
_signal = new ManualResetEvent(false);
_timer = new System.Threading.Timer(Timer_Signaled, null, 15000, 0);
_signal.WaitOne();
_signal.Reset();
Application.Run(new Form1());
}
private static void Timer_Signaled(object state)
{
_signal.Set();
}
}
}
When you hook to keyboard and ESC is pressed, simply call: _signal.Set(). This first sample is just to give you an idea.
Second sample:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace WindowsFormsApplication1
{
static class Program
{
[DllImport("user32.dll")]
static extern short GetAsyncKeyState(System.Windows.Forms.Keys vKey);
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
int maxWaitTime = 15000;
int tc = System.Environment.TickCount;
while (true)
{
System.Threading.Thread.Sleep(1000);
if (System.Environment.TickCount - tc > maxWaitTime)
{
break;
}
if (GetAsyncKeyState(Keys.Escape) > 0)
{
break;
}
}
Application.Run(new Form1());
}
}
}
EDITED:
First sample is more reliable as keyboard hook use callback to inform which key was pressed. Second sample works like 'Pull' and it can happen not every key press will be collected.
Looking at your code, I'm assuming you're using Windows Forms for the UI.
There are many ways to solve your issue, but given the framework the easiest that comes to my mind is:
Blocking your UI (i.e. : this.Enabled = false)
Setting up a timer (i.e.: timer.Tick += ContinueUITimer();)
Setting up a keyboard handler (i.e.: this.KeyDown += ContinueUIKeyboard)
With the ContinueUI functions like this:
void ContinueUIXxx(...)
{
timer.Tick -= ContinueUITimer;
this.KeyDown -= ContinueUIKeyboard;
this.Enabled = true;
... whatever else in the continuation;
}

Windows Forms Opacity After Shown- C#

I am tryig to fade-in a windows form using c# but it doesnt seem to work after I have shown the form. Is it possible to change the forms opacity after Ive shown it?
Code:
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.Timers;
namespace ToolStrip
{
public partial class Form1 : Form
{
Form ToolForm = new ToolForm();
Form PropForm = new PropertyGrid();
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
ToolForm.FormBorderStyle = FormBorderStyle.FixedToolWindow;
ToolForm.Owner = this;
ToolForm.Show();
ToolForm.Location = new Point(50, 50);
}
private void button2_Click(object sender, EventArgs e)
{
PropForm.FormBorderStyle = FormBorderStyle.FixedToolWindow;
PropForm.Owner = this;
PropForm.Show();
PropForm.Location = new Point(50, 50);
System.Timers.Timer aTimer = new System.Timers.Timer(10000);
aTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
aTimer.Interval = 2000;
aTimer.Enabled = true;
Console.WriteLine("Press the Enter key to exit the program.");
Console.ReadLine();
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
PropForm.Opacity = PropForm.Opacity - 0.25;
Console.WriteLine(PropForm.Opacity);
}
}
}
because you r using System.Timers.Timer which is a multithread timer, in it's OnTimedEvent() it calls control created by another thread, which cause exception.
If you use System.Windows.Forms.Timer, it will work. i tested.
Using your code (and creating the other necessary Form classes), I get a cross-threading exception the first time the timer fires and the event handler is called, as Benny suggests.
Making changes to your code to check InvokeRequired in the timer event handler, and use Invoke if necessary to change PropForm.Opacity, results in the opacity changing after the form is shown, as required.
Note that you probably want to start with an Opacity of 0, and increase it gradually - otherwise your form will start off completely solid and gradually fade out
I will mention in passing that Opacity will have no effect on some versions of Windows, though you say you have Opacity effects working elsewhere, so it shouldn't be that in this case.
Ive gotten it to work without timers:
int Loop = 0;
for (Loop = 100; Loop >= 5; Loop -= 10)
{
this.PropForm.Opacity = Loop / 95.0;
this.PropForm .Refresh();
System.Threading.Thread.Sleep(100);
}
but i cant seem to change this example to fade-in instead of out.

Categories