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
Related
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.
I want to make an arrow in c#, which goes from A position to B position in 5 seconds for example. I want to put a map image in the form and when i click on a button i want to draw an arrow from A position to B position in an interval of seconds. i have made an arrow when it is in a horizontal position, but when i try to make it oblique it draws me a triangle instead of an arrow and i don't know how to fix it.
here i made an arrow from a position 12 with a width of 300
and i try to make the same with an oblique arrow but when i put different positions it draws me a triangle not an arrow.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
namespace WindowsFormsApp4
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(400, 273);
this.Text = "";
this.Resize += new System.EventHandler(this.Form1_Resize);
this.Paint += new System.Windows.Forms.PaintEventHandler(this.Form1_Paint);
}
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
g.FillRectangle(Brushes.White, this.ClientRectangle);
Pen p = new Pen(Color.Black, 5);
p.StartCap = LineCap.Round;
for(int i=1; i<=300;i++)
{
System.Threading.Thread.Sleep(2);
g.DrawLine(p, 12, 30, i, 30);
Cursor.Current = Cursors.Default;
}
p.EndCap = LineCap.ArrowAnchor;
g.DrawLine(p, 12, 30, 310, 30);
p.Dispose();
}
private void Form1_Resize(object sender, System.EventArgs e)
{
Invalidate();
}
}
}
The fundamental problem with your code is that you are doing the entire animation loop inside the Paint event handler. This means that the window is never clear out between each line you draw, so you get all of the copies of the line you're drawing, start to finish, laid on top of each other in the same view.
It is not clear from your question exactly what you expect to see on the screen. However, another potential problem with your code is that the moving end point of the line does not start at the start point of the line, but rather at a point with the same Y coordinate where you want the line to end. This means that the arrow end of the line traverses a horizontal line leading to the final end point, rather than gradually extending from the start point of the line.
There is also the minor point that you seem to be confused about what the DrawLine() method does. You state that the width of your line is 300, but in fact the second argument of the DrawLine() method is just another point. The width of the line is defined by the Pen you use to draw the line. The width of the box containing the line is defined by the start and end point, but in this case is not 300, but rather (at the final length of the line) the difference between your start X coordinate and end X coordinate (i.e. 288).
The fundamental problem described above can be addressed by running a loop outside of the Paint event handler, which updates values that describe the line, and then call Invalidate() so that the Paint event handler can be called to draw just the current state of the animation.
On the assumption that what you really wanted was for a line to extend out from the start point, rather than traverse a horizontal line, the example I show below implements the animation that way as well, in addition to fixing the fundamental issue. I did nothing to change the length or width of the line.
public Form1()
{
InitializeComponent();
this.DoubleBuffered = true;
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(400, 273);
this.Resize += new System.EventHandler(this.Form1_Resize);
this.Paint += new System.Windows.Forms.PaintEventHandler(this.Form1_Paint);
var task = AnimateLine();
}
private readonly Point _lineStart = new Point(12, 30);
private readonly Point _lineFinalEnd = new Point(300, 60);
private const int _animateSteps = 300;
private Point _lineCurrentEnd;
private bool _drawArrow;
private async Task AnimateLine()
{
Size size = new Size(_lineFinalEnd) - new Size(_lineStart);
for (int i = 1; i <= _animateSteps; i++)
{
await Task.Delay(2);
Size currentSize = new Size(
size.Width * i / _animateSteps, size.Height * i / _animateSteps);
_lineCurrentEnd = _lineStart + currentSize;
Invalidate();
}
_drawArrow = true;
Invalidate();
}
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics g = e.Graphics;
g.SmoothingMode = SmoothingMode.AntiAlias;
using (Pen p = new Pen(Color.Black, 5))
{
p.StartCap = LineCap.Round;
p.EndCap = _drawArrow ? LineCap.ArrowAnchor : p.EndCap;
g.DrawLine(p, _lineStart, _lineCurrentEnd);
}
}
Note that the repeated erasing and redrawing of the window would make the window flicker. This is a basic issue with any sort of animation, and the fix is to enable double-buffering for the window, hence the this.DoubleBuffered = true; statement added to the constructor.
Some other points worth mentioning:
The await Task.Delay() call is used so that the loop can yield the UI thread with each iteration of the loop, which allows the UI thread to raise the Paint event, as well as allows any other UI activity to still work during the animation. You can find lots more information about that C# feature in the How and When to use async and await article, and of course by reading the documentation.
Whether you use Thread.Sleep() or Task.Delay(), specifying a delay of 2 ms isn't very useful. The Windows thread scheduler does not schedule threads to that degree of precision. A thread that sleeps for 2 ms could be woken up as much as 50 ms later in the normal case, and even later if the CPU is under heavy load. Nor does a 2 ms delay provide a useful animation frame rate; that would be a 500 Hz refresh rate, which is easily 10x or more faster than the human brain needs in order to perceive a smooth animation.My example above does nothing to try to address this issue, but you should explore implementing the loop slightly differently, such that instead of the number of animation steps, you specify a reasonable animation interval (say, every 50 or 100 ms), make an attempt to delay that interval, but then use a Stopwatch to actually measure what the real delay was and compute the progress within the animation based on the actual time elapsed. This will allow you to have precise control over the total duration of the animation, as well as somewhat precise control over the refresh rate used for the animation.
I am trying to make a simple graph for my application which shows real-time data for every 100 ms. So I thought I could draw the graph line using the DrawCurve method and started with the following code:
class BufferedPanel : Panel
{
public BufferedPanel()
{
this.DoubleBuffered = true; //to avoid flickering of the panel
}
}
class Form2: Form
{
BufferedPanel panel1 = new BufferedPanel();
List<Point> graphPoints = new List<System.Drawing.Point>();
private void Form2_Load(object sender, EventArgs e)
{
this.panel1.Paint += new System.Windows.Forms.PaintEventHandler(this.panel1_Paint);
}
private void panel1_Paint(object sender, PaintEventArgs e)
{
using (Graphics g = e.Graphics)
{
Point[] points = graphPoints.ToArray();
g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.HighQuality;
if (points.Length > 1)
g.DrawCurve(graphPen, points);
}
}
private void worker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
graphPoints.Add(new System.Drawing.Point(counter * steps, (int)(float)e.UserState)); //x - regular interval decided by a constant; y - simulated by background worker
panel1.Refresh();
counter++;
}
}
As of now, I am simulating the values of graphPoints from a background worker thread. My problem is that, I could not get the graph lines visible when I doublebuffer my panel. It works well when I set doublebuffering to false. I am new to drawing using Graphics. So I am not very sure of how it works. Please help me in this.
Also, I would like to achieve AutoScrolling when the graphlines reach to end of the panel. Could you suggest an idea on how to do it?
This is an image of my working graph:
using (Graphics g = e.Graphics)
That's bad. That destroys the Graphics object that was passed to your Paint event handler. Nothing can be done with that object when your event handler returns, it is a dead parrot. So don't expect anything to work afterwards, including what needs to happen when you turn on double-buffering, the buffer needs to be drawn to the screen to be visible.
There's a simple rule to using the using statement or the Dispose() method correctly. If you create an object then you own it and it is yours to destroy it. Hands off it you didn't create it.
Some evidence that you are also getting it wrong with the "graphPen" variable. Pens are definitely an object that you create and destroy in a Paint event handler. Don't store one, that just needlessly occupies space in the GDI heap, that isn't worth the few microseconds needed to create one. You'd definitely use the using statement for the pen.
So the quick fix is:
var g = e.Graphics;
private void button3_Click(object sender, EventArgs e)
{
this.DoubleBuffered = true;
for (int i = 0; i < 350; i++)
{
using (Graphics g = this.CreateGraphics() )
{
g.Clear(Color.CadetBlue);
g.DrawImage(Properties.Resources._256, 100, 100, i-150, i-150);
}
}
}
Yet thought I have the DoubleBuffered set to true, the image still flickers. Any ideas what am I doing wrong?
Thanks!
As Neil noted, you don't need to (and shouldn't) create a new Graphics object in each iteration of the loop. These are relatively expensive resources and should not be created willy nilly.
Also, you shouldn't be painting like that inside of a button Click handler by calling CreateGraphics. It can lead to problems, most notably your drawing being "undone" when the paint handler is invoked (i.e., every time the window is receives a WM_PAINT message and is refreshed). You should do all of your painting by overriding OnPaint and simply call Invalidate() when you need to update your form.
As for the flickering, setting DoubleBuffered to true will usually take care of it, but rolling your own double buffering is trivial. Give it a try. Also realize that drawing in a loop like that probably isn't what you want to do. Use a timer if you want to update once per some interval. Your code is being executed as fast as the loop can execute, which is not usually desirable.
private void someTimer_Tick( ... )
{
Invalidate();
}
protected override void OnPaint( PaintEventArgs e )
{
using( var tempBmp = new Bitmap( ... ) )
using( var g = Graphics.FromImage( tempBmp ) )
{
// draw to tempBmp
e.Graphics.DrawImage( tempBmp, new Point( 0, 0 ) );
}
}
The problem is you are creating a new graphics object on each iteration of the loop
Move the for statement within the using statement, and you should see a dramatic performance increase:
using (Graphics g = this.CreateGraphics() )
{
for (int i = 0; i < 350; i++)
{
g.Clear(Color.CadetBlue);
g.DrawImage(Properties.Resources._256, 100, 100, i-150, i-150);
}
}
That, and it may also be a good idea to move the Resource file you are loading into a local variable.
Double-buffering is only enabled for the Paint event. You are directly drawing to the screen with CreateGraphics(), the g.Clear() call is very noticeable since it instantly erases the drawn image. Not drawing in the Paint event or OnPaint method is almost always a mistake.
I am using Cairo in a GTK# application for drawing. When another window covers part of the drawn content, the overlapped part of the drawn content is lost. Is there a way to make it permanent?
Here is my simplified method for drawing the content:
void UpdateConnectionLines ()
{
GdkWindow myWindow = GetGdkWindow();
myWindow.Clear ();
using (Context g = Gdk.CairoHelper.Create (myWindow))
{
g.Save ();
g.MoveTo (0, 20);
g.LineTo (100, 20);
g.Restore ();
g.Color = new Color (0, 0, 0);
g.LineWidth = 1;
g.Stroke();
}
}
If you are drawing directly on the form, then you need to do it in the Form's paint event, to ensure it is there every time the form get's painted (i.e. when another window covers it and then moves, when it is resized, ...)
Evaluating John Koerner's answer, I have found a solution, that works for every GTK# widget. I use the generic WidgetEvent ExposeEvent (thanks, ptomato) and redraw.
I append my event handler with
this.ExposeEvent += new global::Gtk.ExposeEventHandler (this.Handle_ExposeEvent);
and then the handler just calls my method:
protected virtual void Handle_ExposeEvent (object o, Gtk.ExposeEventArgs args)
{
UpdateConnectionLines();
}
EDIT:
Actually, I have not RTFM correctly, as it explicitely states:
The best place to create and use the
Context is the ExposeEvent for the
given widget. Usually you'll want to
use the Gtk.DrawingArea for this task.
An example implementation of the
Expose event method: