my code for my game had a paint system where is draws then redraws a car (or a box) every 50 ticks. i have really bad flickering when running the code (even when not moving) anything else you need i can grab :)
i have tried:
-Double buffering
-Putting everything into a panel and refreshing that
//My Paint Code
Bitmap greenCar = new
e.Graphics.FillRectangle(car.getDrawingbrushColor(),
car.getCarposition());
e.Graphics.DrawImage(greenCar,car2.getCarposition());
}
my timer code
scoreTimer.Tick += new EventHandler(Tick2);
scoreTimer.Interval = 100;
scoreTimer.Start();
DoubleBuffered = true;
My Refresh Code
{
this.Counter.Text = Scorecounter.ToString();
Scorecounter += 1;
panel1.Refresh();
}
i just dont want it to flicker, i is really holding me back.
Related
In my application, there are 2 windows and both contain a PictureBox. The first (pb1) allows interaction and the image can be changed through click- and mouseMove-events. These events call pb1.Invalidate(); which works fine.
I want the second PictureBox (pb2) to redraw as well so I call pb2.Invalidate() from the paint-event of pb1. [Just for context, the second PictureBox shows nearly the same Image but on a bigger scale and some parts of the drawing will be left out in the future so I use the same Method in both paint events which decides what to draw and what not]
It works but it's "laggy" and I want it to be as smooth as the paint on the first PictureBox. I reduced the paint event just to a grid for test purposes.
Both windows are double buffered.
I tried replacing the picture boxes with SKGLControls from SkiaSharp (which should have better performance). The example code still uses the SkiaEvents so don't be confused if the problem occurs with both controls.
I tried to use .Update() or .Refresh() instead of .Invalidate() but i guess its to much to handle, the application just crashes..
Here is the method that is called by both OnPaint events
public void Update(SKPaintGLSurfaceEventArgs e, bool bigscreen)
{
SKCanvas canvas = e.Surface.Canvas;
canvas.Clear(SKColors.Beige);
//Zoom to specified area
SKMatrix matrix = SKMatrix.Identity;
if (!bigscreen)
{
matrix = matrix.PostConcat(SKMatrix.CreateScale(canvasSize / (float)zoomArea.Width, canvasSize / (float)zoomArea.Height));
}
else
{
matrix = matrix.PostConcat(SKMatrix.CreateScale(bigCanvasSize / (float)zoomArea.Width, bigCanvasSize / (float)zoomArea.Height));
}
matrix = matrix.PreConcat(SKMatrix.CreateTranslation(-zoomArea.X, -zoomArea.Y));
canvas.SetMatrix(matrix);
DrawGrid(canvas);
}
and the grid-draw method
private void DrawGrid(SKCanvas canvas)
{
using (SKPaint paint = new SKPaint() { IsAntialias = true,Color=SKColors.LightGray,StrokeWidth = 1})
{
canvas.DrawLine(0, 0, 0, gridCanvas.Height, paint); //Size gridCanvas is always the same at the moment and defines the space where the grid is drawn
canvas.DrawLine(0, 0, gridCanvas.Width, 0, paint);
for (int i = 0; i <= (gridCanvas.Width - gridoffsetX) / pxPerSquare; i++)
{
canvas.DrawLine(i * pxPerSquare + gridoffsetX, 0, i * pxPerSquare + gridoffsetX, gridCanvas.Height, paint);
}
for (int i = 0; i <= (gridCanvas.Height - gridoffsetY) / pxPerSquare; i++)
{
canvas.DrawLine(0, i * pxPerSquare + gridoffsetY, gridCanvas.Width, i * pxPerSquare + gridoffsetY, paint);
}
}
}
and finally the original Paint Event
private void Pb1_PaintSurface(object sender, SKPaintGLSurfaceEventArgs e)
{
win2.UpdateDrawing(); //Just calls .Invalidate() on pb2
painter.Update(e, false);
}
examplePicture
So my question is: Is there a way to make both controls draw at nearly the same time without delay, although I don't understand why the first PictureBox draws in real time and the second doesn't...
Thanks!
after searching for day i found this page right after posting, which helped me:
Onpaint events (invalidated) changing execution order after a period normal operation (runtime)
rightT is a timer which is supposed to change the image of the object (which inherits from PictureBox)
when the code runs (after I turned on the timer) the image changes only once and never changes again
anyone knows why it happens?
rightT.Tick += (EventHandler)delegate
{
this.Location = new Point(this.Location.X + _movementSize,
this.Location.Y); // moves the character
this.Image = this.rightState++ % 2 == 0 ? Properties.Resources.MarioAnimation2 : Properties.Resources.MarioAnimation1; // changes the photo
this.Refresh();
};
I think Application.DoEvents(); is better suited for forcing the gui to update. you can place it anywhere you like and as much as you like.
When a specific condition is met I disable my picturebox and the image freeze's, whenever it runs again the gif starts all over again not from where it was stopped is there anyway to resume it ?
Stopping picturebox : http://prntscr.com/b6gg7a
"Resuming" picturebox : http://prntscr.com/b6gglb
I believe indeed this is a rather tedious/unlikely to perform with an animated gif.
What you could/should do IMO is the following:
Instead of making the background a gif, break it into multiple frames(pictures).
Set up a timer that triggers the background to change to the next image.
You can pause and resume that timer the same as you would enable and disable the picturebox.
Your background change function could look something like this:
int bgCount = 8; //Amount of frames your background uses
int bgCurrent = 0;
string[] bgImg = {
"bg1.png",
"bg2.png",
"bg3.png",
"bg4.png",
"bg5.png",
"bg6.png",
"bg7.png",
"bg8.png",
};
void changeBG()
{
if(bgCurrent > bgCount-1) //If not displaying the last background image go to the next one
{
bgCurrent++;
imgBox.image = bgImg[bgCurrent];
}else //If displaying the last background image -> Start with the first one
{
bgCurrent = 0;
imgBox.image = bgImg[bgCurrent];
}
}
}
And you just let the timer trigger this event. And you simply start and stop the timer.
Sidenote:
1* You might experiment with, instead of changing the source, just make 8 imageboxed and call/display them as they are needed.
One problem was solved, another followed: In a C#-program I use following method to set a labels color to green, then playing a mp3-file and finally setting the color back to black.
The problem is that the sound seems to be played in an extra thread, thus the time between the change of the two colors is too short (in fact, it should have the green color while the file is played).
private void playSound()
{
label1.ForeColor = Color.LimeGreen;
Application.DoEvents();
WMPLib.WindowsMediaPlayer wmp = new WMPLib.WindowsMediaPlayer();
wmp.URL = #"C:\examplesound.mp3"; // duration about 30s
wmp.controls.play();
label1.ForeColor = Color.Black;
}
Is there anything can be done to force the label to keep the green color whilst the mp3-file is played?
Don't set the colour back to black straight away as the playback is in another thread.
When the current track ends WMPLib sends out a PlayStateChange event.
So add a handler:
wmp.PlayStateChange += this.Player_PlayStateChange;
private void Player_PlayStateChange(int newState)
{
if ((WMPLib.WMPPlayState)newState == WMPLib.WMPPlayState.wmppsStopped)
{
label1.ForeColor = Color.Black;
}
}
The page for playState has a list of values:
8 - MediaEnded - Media item has completed playback.
You'll need to make sure this is done on the UI thread.
Try hooking the PlayStateChanged event and put the label1.ForeColor = Color.Black; in there.
At the moment there's nothing in your code saying that it should only change to black when it finishes, only after it has started to play.
comrades) I've found some interesting behavior of Invalidate method in multithreaded applications. I hope you could help me with a problem...
I experience troubles while trying to invalidate different controls at one time: while they're identical, one succesfully repaints itself, but another - not.
Here is an example: I have a form (MysticForm) with two panels (SlowRenderPanel) on it. Each panel has a timer and with a period of 50ms Invalidate() method is called. In OnPaint method I draw number of current OnPaint call in the centre of panel. But notice that in OnPaint method System.Threading.Thread.Sleep(50) is called to simulate long time draw procedure.
So the problem is that the panel added first repaints itself much more often than another one.
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Runtime.InteropServices;
namespace WindowsFormsApplication1 {
static class Program {
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MysticForm());
}
}
public class MysticForm : Form {
public SlowRenderPanel panel1;
public SlowRenderPanel panel2;
public MysticForm() {
// add 2 panels to the form
Controls.Add(new SlowRenderPanel() { Dock = DockStyle.Left, BackColor = Color.Red, Width = ClientRectangle.Width / 2 });
Controls.Add(new SlowRenderPanel() { Dock = DockStyle.Right, BackColor = Color.Blue, Width = ClientRectangle.Width / 2 });
}
}
public class SlowRenderPanel : Panel {
// synchronized timer
private System.Windows.Forms.Timer timerSafe = null;
// simple timer
private System.Threading.Timer timerUnsafe = null;
// OnPaint call counter
private int counter = 0;
// allows to use one of the above timers
bool useUnsafeTimer = true;
protected override void Dispose(bool disposing) {
// active timer disposal
(useUnsafeTimer ? timerUnsafe as IDisposable : timerSafe as IDisposable).Dispose();
base.Dispose(disposing);
}
public SlowRenderPanel() {
// anti-blink
DoubleBuffered = true;
// large font
Font = new Font(Font.FontFamily, 36);
if (useUnsafeTimer) {
// simple timer. starts in a second. calls Invalidate() with period = 50ms
timerUnsafe = new System.Threading.Timer(state => { Invalidate(); }, null, 1000, 50);
} else {
// safe timer. calls Invalidate() with period = 50ms
timerSafe = new System.Windows.Forms.Timer() { Interval = 50, Enabled = true };
timerSafe.Tick += (sender, e) => { Invalidate(); };
}
}
protected override void OnPaint(PaintEventArgs e) {
string text = counter++.ToString();
// simulate large bitmap drawing
System.Threading.Thread.Sleep(50);
SizeF size = e.Graphics.MeasureString(text, Font);
e.Graphics.DrawString(text, Font, Brushes.Black, new PointF(Width / 2f - size.Width / 2f, Height / 2f - size.Height / 2f));
base.OnPaint(e);
}
}
}
Debug info:
1) Each panel has a bool field useUnsafeTime (set to true by default) which allows using System.Windows.Forms.Timer (false) insted of System.Threading.Timer (true). In the first case (System.Windows.Forms.Timer) everything works fine. Removing System.Threading.Sleep call in OnPaint also makes execution fine.
2) Setting timer interval to 25ms or less prevents second panel repainting at all (while user doesn't resize the form).
3) Using System.Windows.Forms.Timer leads to speed increasement
4) Forcing control to enter synchronization context (Invoke) doesn't make sense. I mean that Invalidate(invalidateChildren = false) is "thread-safe" and could possibly have different behavior in diffenent contexts
5) Nothing interesting found in IL comparison of these two timers... They just use different WinAPI functions to set and remove timers (AddTimerNative, DeleteTimerNative for Threading.Timer; SetTimer, KillTimer for Windows.Forms.Timer), and Windows.Forms.Timer uses NativeWindow's WndProc method for rising Tick event
I use a similar code snippet in my application and unfortunately there is no way of using System.Windows.Forms.Timer) I use long-time multithreaded image rendering of two panels and Invalidate method is called after rendering is completed on each panel...
That would be great if someone could help me to understand what's different happening behind the scenes and how to solve the problem.
P.S. Interesting behavior isn't it?=)
Nice demonstration of what goes wrong when you use members of a control or form on a background thread. Winforms usually catches this but there's a bug in the Invalidate() method code. Change it like this:
timerUnsafe = new System.Threading.Timer(state => { Invalidate(true); }, null, 1000, 50);
to trip the exception.
The other panel is slower because lots of its Invalidate() calls are getting canceled by the paint event. Which is just slow enough to do so. Classic threading race. You cannot call Invalidate() from a worker thread, the synchronous timer is an obvious solution.
Invalidate() invalidates the client area or rectangle ( InvalidateRect() ) and "tells" Windows that next time Windows paints; refresh me, paint me. But it does not cause or invoke a paint message. To force a paint event, you must force windows to paint after an Invalidate call. This is not always needed, but sometimes it's what has to be done.
To force a paint you have to use the Update() function. "Causes the control to redraw the invalidated regions within its client area."
You have to use both in this case.
Edit: A common technique to avoid these kinds of problems is keep all your paint routines and anything related in a single (generally main) thread or timer. The logic can run elsewhere but where the actual paint calls are made should all be in one thread or timer.
This is done in games and 3D simulations.
HTH