Wednesday, April 16, 2008

GDI+ painting basics

The core of GDI+ is the Graphics class which draws all the needed lines, curves, shapes, text and images. The Graphics class encapsulates a GDI+ drawing surface, which can be a window or print document. You paint on this drawing surface using a combination of the painting methods the Graphics class provides. If you want to read more about the GDI+ please read this article: GDI+ Windows rendering API

GDI+: Getting a Graphics instance using PaintEventArgs

There are two ways to get access on a Graphics instance.

The simplest and safest approach is to perform your painting inside the dedicated Paint event handler. You can access the current Graphics instance through the PaintEventArgs parameter.

// This code handles the Form.Paint event.

privatevoid Form_Paint(object sender, PaintEventArgs e)

{

Pen myPen = newPen(Color.Blue, 5);

e.Graphics.DrawLine(myPen, 10, 10, 20, 20);

myPen.Dispose();

}

If you create a owner-drown control, you can access the Graphics instance by overriding the OnPaint() method of that control.

// This code overrides the base Form.OnPaint() method.

protectedoverridevoid OnPaint(PaintEventArgs e)

{

Pen myPen = newPen(Color.Blue, 5);

e.Graphics.DrawLine(myPen, 10, 10, 20, 20);

myPen.Dispose();

// Call the base class implementation

// (which raises the Paint event).

this.OnPaint(e);

}

Using any of these approaches, you don’t have to call Dispose() for the used Graphics instance because the .NET Framework acquires and disposes of the Graphics object for you. Both approaches will ensure your code is executed each time the Windows paints your control or form. For example, when the user moves or resizes the control or form, Windows will repaint it.

GDI+: Creating a Graphics object

The second approach to get a Graphics object to paint on is to call the CreateGraphics() method for a specific control or form. In this case, you should make sure to call Dispose() when you have finished the drawing because the Graphics object uses unmanaged system resources.

privatevoid button_Click(object sender, EventArgs e)

{

Pen myPen = newPen(Color.Blue, 5);

Graphics gdi = this.CreateGraphics();

gdi.DrawLine(myPen, 10, 10, 20, 20);

myPen.Dispose();

// Dispose the Graphics object.

gdi.Dispose();

}

Using this approach, the painting code is not executed each time Windows repaints the control or form. You have to take care of this by yourself. If the control or form is resized, a new Graphics object must be created by calling CreateGraphics() method because of the new width and height values that must be considered.

The GDI+ Coordinate Systems

GDI+ defines three distinct coordinate systems, which are used by the run-time to determine the location and size of the content to be painted. The origin is at the upper-left corner with the x-axis increasing to the right and the y-axis increasing downward.

World coordinates. World coordinates represent an abstraction of the size of agiven GDI+ type, irrespective of the unit of measurement. When you are drawing with GDI+, you will typically think in terms of world coordinates, which are the baseline used to determine the size and location of a GDI+ drawing type. To paint in world coordinates requires no special coding actions:

void MainForm_Paint(object sender, PaintEventArgs e)

{

// Render a rectangle in world coordinates.

Graphics g = e.Graphics;

g.DrawRectangle(Pens.Black, 10, 10, 100, 100);

}

Page coordinates. Page coordinates represent an offset applied to the original world coordinates. This is helpful in that you are not the one in charge of manually applying offsets in your code (should you need them).

privatevoid MainForm_Paint(object sender, PaintEventArgs e)

{

// Specify page coordinate offsets (5 * 5).

Graphics g = e.Graphics;

g.TranslateTransform(5, 5);

g.DrawRectangle(10, 10, 100, 100);

}

Device coordinates. Device coordinates represent the result of applying page coordinates to the original world coordinates. This coordinate system is used to determine exactly where the GDI+ type will be paint.

GDI+: Paint and repaint

Windows doesn’t store the graphical representation of a control or form in memory. This is true for all Windows versions, up to Windows XP but not for Windows Vista.

Storing an image representation of every open window could quickly consume tens of megabytes and cripple a computer. Instead, Windows automatically discards the contents of a window as soon as it is minimized or hidden by another window. When the program window is restored, Windows sends a message to the application, asking it to repaint itself.

In a .NET application, this means that both the Control.OnPaint() and Control.OnPaintBackground() methods are called and the Control.Paint event fires. Similarly, if part of a window is obscured, only controls that are affected fire Paint event when they reappear on the screen.

GDI+: Paint Flickering

Repainting a control frequently cause flicker. The flicker is caused because, with each paint event, the image is erased and then redrawn, object by object. The flash you see is the blank background that precedes the redrawn content. Bellow you can find some useful GDI+ tips to reduce the flickering effect.

GDI+: OnPaint and OnPaintBackground

Windows Forms and the underlying Win32 architecture expose two layers of painting for any control: background and foreground. All the background elements (like background images or colors including the control's shape) are painted by the Control.OnPaintBackground() call while all the details (foreground images, decorations and text) are painted by the Control.OnPaint() call. Because the OnPaint() is drawing all the details, it is slower than OnPaintBackground() call.

The OnPaint() method does not implement any drawing functionality. It just invokes the event delegates registerd for the Paint event. When override the OnPaint() in a custom control, the base class's OnPaint() method should be called so that registered delegates receive the Paint event. Like the Paint event, the OnPaint() has a PaintEventArgs argument.

The OnPaintBackground() method has a PaintEventArgs argument too, but it is not a true event method. There is no PaintBackground event and OnPaintBackground() call does not invoke event delegates. For overriding the OnPaintBackground(), there is no required base class's OnPaintBackground() to be called. Actually you can reduce flickering by overriding the OnPaintBackground() to not invoke the base class's.

protectedoverridevoid OnPaintBackground(PaintEventArgs e)

{

// Do nothing.

}

You can achieve the same effect by setting the parent form to ignore all messages asking it to repaint its background, by calling this:

this.SetStyle(ControlStyles.AllPaintingInWmPaint, true);

Of course you can do this only if the entire drawing is done by the OnPaint() call.

Optimize GDI+ Painting

If your Windows Forms application is drawing-intensive, it could benefit from enhanced painting performance, so here are some useful performance tips:

Minimize the work that is done on every Paint event. For example, if you need to paint text based on your current ClientSize, you can create the appropriately sized Font object in a Resize event handler instead of on every Paint event.

Recycle drawing objects instead of recreate them. All the GDI+ resources are not managed and they must be disposed as soon as we don't need them anymore. Anyway, sometimes is a good idea to recycle instead of recreat them for each OnPaint() call.

Provide an area parameter when calling the Invalidate() method. Doing so you will avoid repainting the entire control and only the affected areas are redrown.

Set ControlStyles.Opaque to true for controls without background graphics. If your control is not using the background image or background color or it is completely covered by other controls and its background doesn't need to be painted, set ControlStyles.Opaque to true and the OnPaintBackground() will not be called. This will reduce flickering too.

If you are filling a form or a control with a solid background color, you should always suppress the OnPaintBackground() call. It can improve performance dramatically by reducing the flickering effect. This can be done by overriding the OnPaintBackground() to do nothing (not even call the base OnPaintBackground() method), or using this code: