C# Winforms Ultra Slow Update Of Screen Using Drawline method - c#

I've had a look around the web regarding this, but haven't found the exact answer I'm looking for or I've tried what is suggested and it doesn't work!
I'm having issues in that I have a screen which has approximately 72 Checkboxes on it in a matrix which I have connected together using lines the coordinates of which I store in a list.
To draw the lines I use the Drawline method in an override method for OnPaint to iterate through the list as follows :-
protected override void OnPaint(PaintEventArgs e)
{
Pen myPen = new Pen(System.Drawing.Color.Black);
Graphics g = this.CreateGraphics();
myPen.Width = 5;
foreach(ConnectionLine cl in connectionLines)
{
g.DrawLine(myPen, cl.xStart, cl.yStart, cl.xStop, cl.yStop);
}
myPen.Dispose();
g.Dispose();
}
The strange thing about this is that it doesn't appear to be the lines that take the time to draw - it's now the checkboxes, if I remove the line functionality these refresh in the blink of an eye.
Any ideas much appreciated.
Thanks,
Dave

Part of the problem could be that you are recreating the Graphics object each time the control is painted. Instead you should use the e.Graphics object that is provided in PaintEventArgs. You could also try using only one instance of Pen.
private readonly Pen _myPen = new Pen(System.Drawing.Color.Black) {Width = 5};
protected override void OnPaint(PaintEventArgs e)
{
foreach (var cl in connectionLines)
e.Graphics.DrawLine(_myPen, cl.xStart, cl.yStart, cl.xStop, cl.yStop);
}

There is no need to create your own and dispose of a Graphics object. Use what is available with the event handler. ALso you should use using rather than calling Dispose explicitly.
protected override void OnPaint(PaintEventArgs e)
{
using (Pen myPen = new Pen(System.Drawing.Color.Black, 5.0))
{
foreach(ConnectionLine cl in connectionLines)
e.Graphics.DrawLine(myPen, cl.xStart, cl.yStart, cl.xStop, cl.yStop);
}
}
Also, if your lines connect you should get better performance and cleaner code with Graphic's DrawLines method. You would have to change how you store your points or extract them from your conncetionLines collection before calling.

Related

C# How do I draw a line between two objects on a windows form?

I've been trying to draw a line between two objects for a long time now, but it still won't work.
My program is supposed to make two picture boxes (Already made, called PB1 and PB2) and connect them with a line on the form.
I have this:
public void DrawStuff(object sender, PaintEventArgs e)
{
Pen blackPen = new Pen(Color.Black, 3);
Point point1 = new Point(PB[0].Location.X, PB[0].Location.Y);
Point point2 = new Point(PB[1].Location.X, PB[1].Location.Y);
e.Graphics.DrawLine(blackPen, point1, point2);
CreateGraphics();
}
But I can't call the function! Also, the Boxes are being created with a button, so it can't draw from the start, it has to do it after I push that button. If anyone has a working code, please let me know, I'm about to break down.
Do not (read NEVER EVER) call CreateGraphics() explicitly. This is a crime against humanity, except for very rare situations.
Handle Paint event (or override OnPaint()) of your Form. Write your line drawing code in there.
Something like this:
protected override void OnPaint(PaintEventArgs e)
{
base.OnPaint(e);
using(var blackPen = new Pen(Color.Black, 3))
e.Graphics.DrawLine(blackPen, PB[0].Location, PB[1].Location);
}
Whenever you need to refresh screen manually, call this.Invalidate().

C#-WinForms || Multiple instances of Graphics for rotation

im currently working on a school project which is basically a game that involves GDI-Winforms animations.
I chose my game to be something like prison break where you are trying to escape the prison while guards with light-torches are walking around the room.
So, i have the Guard class which represents the guard and has A Path to walk on. Furthermore, the guard can rotate in 90deg. angles. (animated to a certain degree)
When i rotate the guard, i actually rotate the Graphics object that was passed through the Form_Paint event:
void Game_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
ply.Draw(e.Graphics); // Draws the player
grd.Draw(e.Graphics); // Draws the guard
e.Graphics.DrawPolygon(Pens.Red, grd.GetPath()); // Draws the guard's path
}
When i was dealing with only 1 guard, it was working great. Smooth and did the exactly what he was supposed to do.
When i tried to add 1 more guard, they started to freak out. Soon enough i figured out that its because im sending both guards to draw the same instance of Graphics. Which also means, both of them are rotating it.
Lets say one is rotating by -90, the other will too, but his angle class member variable will not be -90, then all hell breaks loose.
Rotation of graphics method (sits inside the guard class):
public Graphics RotateGuard(Graphics g, Point pivot, float angle) // returns the rotated Graphics object
{
if (!float.IsNaN(angle))
{
using (Matrix m = new Matrix())
{
m.RotateAt(angle, pivot);
g.Transform = m;
}
}
return g;
}
The next thing i did was to give each of them this.CreateGraphics().
void Game_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
ply.Draw(e.Graphics); // Draws the player
foreach (Guard grd in this.guards )
{
grd.Draw(this.CreateGraphics()); // Draws the guard
e.Graphics.DrawPolygon(Pens.Red, grd.GetPath()); // Draws the guard's path
}
}
Then everything worked just fine. The only thing is, it seemed like it was really heavy for the GPU to process or something. It drew the guard once about every 5 frames less than he supposed to be drawn.
I googled but couldn't find anything but people saying that "There is no need to clone a Graphics object", but still, i can't think of any better WORKING solution.
How can i solve this problem nicely?
Thanks in advanced.
CreateGraphics() is a really bad idea. You should do all the drawing to the Graphics passed by PaintEventArgs. So your initial code is just fine. However, what you need to do is to ensure that every object that receives a Graphics in its Draw method leaves it unchanged after doing its job. This can be achieved by using Graphics.Save and Graphics.Restore like this
class Guard
{
public void Draw(Graphics g)
{
var state = g.Save();
try
{
// The actual drawing code
}
finally
{
g.Restore(state);
}
}
}
An efficient way is to maintain one overall transform and have a matrix for each thing you wish to draw. Prior to drawing you multiply the current transform with the object's transform. Afterwards you reset the transform before drawing next.
void Game_Paint(object sender, PaintEventArgs e)
{
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
var g = e.Graphics;
g.ResetTransform();
g.MultiplyTransform (playerMatrix);
ply.Draw(e.Graphics); // Draws the player
g.ResetTransform();
foreach (Guard grd in this.guards )
{
g.MultiplyTransform (grd.Matrix);
grd.Draw(this.CreateGraphics()); // Draws the guard
e.Graphics.DrawPolygon(Pens.Red, grd.GetPath()); // Draws the guard's path
g.ResetTransform();
}
}
Such concepts are similar to how things are done in 3D graphics such as Direct3D; XNA; OpenGL and Unity3D.
After drawing with the rotated Graphics tool object simply call e.Graphics.ResetTransform().
Also you may want to look into Graphics.Save() and Graphics.Restore() if you have made a few settings you want to return to.. It can save few states and when done with them bring them back up. Very nice, at least if you keep count of what you are doing.
And, of course you could undo the Translation/Rotation by doing the reverse calls in the reverse order, but the other methods are simpler and made for just your case.
Note that Graphics doesn't contain any graphics, it is a tool used to draw into an associated Bitmap or onto a control's surface..
Finally: Never, never use CreateGraphics !!! Its results are non-persistent, which you want only very rarely..
I figured out what it was, then i used what #Ivan did.
void Game_Paint(object sender, PaintEventArgs e)
{
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
e.Graphics.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;
var saved = e.Graphics.Save();
ply.Draw(e.Graphics); // Draws the player
foreach (Guard grd in this.guards )
{
grd.Draw(e.Graphics); // Draws the guard
e.Graphics.Restore(saved);
e.Graphics.DrawPolygon(Pens.Red, grd.GetPath()); // Draws the guard's path
}
}
All i had to do is instead of using this.DoubleBuffered i used SetStyle(ControlStyles.OptimizedDoubleBuffer, true); which at first i thought both does the same, but evidently it does not.
Then, i saved the current graphics state and re-drew it.
Thanks a lot for everyone!

How to resize a graphic?

I have two pictureBoxes "source" and "dest". Now I would like to resize the "source" image and display it in my "dest"-picturebox.
Question:
How can I resize my "source" image and display it in my "dest"-picturebox?
Here is my code which only display the same image again:
private void pictureBoxZoom_Paint(object sender, PaintEventArgs e)
{
var settings = new Settings();
// Create a local version of the graphics object for the PictureBox.
Graphics Draw = e.Graphics;
Draw.ScaleTransform(2, 2); // rescale by factor 2
IntPtr hDC = Draw.GetHdc(); // Get a handle to pictureBoxZoom.
Draw.ReleaseHdc(hDC); // Release pictureBoxZoom handle.
}
In each case, you have to handle the PictureBox.SizeMode-Property. It determines what the PictureBox does to the image when painting.
If you want to apply a customized Zoom-factor, you can use the Graphics.ScaleTransform-Property. It allows you to scale the complete graphics. The Scaling can be done on the image itself by creating a bitmap (which has a Graphics-object you can use) or by overriding the PictureBox.OnPaint-Method, which requires you to create a class deriving from PictureBox. That's the most flexible attempt because you can control everything by yourself - if you like to.ยด
EDIT:
Sorry for not being clear enough, the way using the Paint-event does not work. You need to make your own PictureBox-class (derive from PictureBox) and override the OnPaint-method. Then you can call ScaleTransform on the Graphics-object the PictureBox uses. I got a tested sample for you:
public class PictureBoxScaleable : PictureBox
{
protected override void OnPaint(PaintEventArgs pe)
{
pe.Graphics.ScaleTransform(.2f, .2f);
base.OnPaint(pe);
}
}

Creating a custom graph in Winforms

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;

C#, double buffer in WinForms?

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.

Categories