From iLab Cookbook

VisualStudio: UsingGDIToDrawGraphics

This tutorial takes you through the basics of using GDI+ to draw your own graphics to a form or control.

Download: GDIDraw.zip - Demonstrates drawing, antialiasing, coordinate translation, and repainting.
Download: GDIAnimation.zip - Demonstrates double buffering, animation, and factory drawing.

Unzip all files (executables plus dependencies) into a single folder

Frequently there just isn't a widget that does what you need. Using GDI+ to draw your own user interface components helps to open up the possibilities for user interaction in your application. GDI+ requires a basic understanding of how to use primitives (lines, ellipses, and rectangles) in a coordinate system to compose your desired output.

The GDI+ Coordinate System

If you've created visual C# applications you've already used the GDI+ coordinate system. A component's Location property is the X and Y coordinate relative to the parent form.

Several data structures exist in the System.Drawing namespace to store coordinates, dimensions, and areas.

Every Form or Control has a main panel called a Client Area, where sub-controls are placed, or the control itself is drawn. Think of this panel as a blank canvas for you to paint on. In order to specify the location of primitives, you must specify an X and Y coordinate.

C# provides methods for translating Control Coordinates to Screen Coordinates. Screen Coordinates are absolute coordinates on the screen where the top lefthand corner is (0, 0). For systems that utilize more than one monitor, the screen area is assumed to be a continuous rectangular area that spans both monitors.

The Paint Event

Forms and Controls have a Paint event which is called every time the control needs to be redrawn. For instance, when part of a control is occluded by some other object on the screen, the area that has been occluded needs to be redrawn when the occluding object is removed - thus the control is asked to repaint itself. The Paint event is called whenever this repaint is requested, thus it is the place where you should place your code to draw the control.

The Graphics Interface

Forms and controls each have a CreateGraphics() function, which returns an instance of System.Drawing.Graphics, an object used to conduct drawing to the client area of the control.

Graphics g = this.CreateGraphics();

OR

Graphics g = MyControl.CreateGraphics();

The Graphics object also has a handy function for clearing the entire area of the canvas with a given color. You may wish to specify your own color, or use the default control color.

g.Clear( Color.Black );
g.Clear( Control.BackColor );

By default, the Graphics object draws without antialiasing - it is computationally cheaper, but results in jagged edges on drawn shapes. To turn on antialiasing, do the following function call before any drawing operations:

g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

Drawing Styles

Several objects exist to control color for different types of drawing:

Color c1 = Color.NavyBlue;
Color c2 = Color.FromArgb( 64, 64, 255 )// A darkish blue-gray
Pen p = new Pen( c1, 2 );
Brush b = new SolidBrush( c2 );
Font f = new Font("Times", 12, FontStyle.Bold | FontStyle.Italic | FontStyle.Underline);

Drawing Primitives

The Graphics object has methods for drawing operations.

g.DrawLine( new Pen( Color.Blue, 3 ), 0, 0, 100, 100 );
g.DrawLine( new Pen( Color.Blue, 3 ), new Point(0, 0), new Point(100, 100) );

g.DrawRectangle( Pens.Red, 10, 10, 20, 20 )// Note the bottom corner is at (30,30).
g.DrawRectangle( Pens.Red, new Point(10, 10), new Size(20, 20) );
g.DrawRectangle( Pens.Red, new Rectangle(10, 10, 20, 20) );

g.FillRectangle( new SolidBrush( Color.Lime ), 50, 50, 20, 20 );

g.DrawEllipse( new Pen( Color.Purple, 10 ), 100, 100, 100, 100 );

g.FillEllipse( Brushes.Yellow, 10, 100, 20, 30 );

Drawing Text

The Graphics.DrawString Function draws text to the canvas using:

g.DrawString( "Hello world!", new Font("Times", 12, FontStyle.Regular), new SolidBrush( Color.Black ), new PointF(0, 0) );

StringFormat sf = new StringFormat();
sf.Alignment = StringAlignment.Center; // Horizontally center.  Near = left align, Far = right align.
sf.LineAlignment = StringAlignment.Center; // Vertically center.  Near = top align, Far = bottom align.

g.DrawRectangle( Pens.Red );
g.DrawString( "Hello world!", new Font("Times", 12, FontStyle.Regular), new SolidBrush( Color.Black ), new RectangleF(0, 0, 100, 100), sf ); // Text will wrap to horizontal bounds, clip to vertical bounds

You can measure the width of a graphically rendered string in pixels as follows:

int stringwidth = g.MeasureString( "Hello world!", new Font("Times", 12, FontStyle.Regular) );

Using Images

When you want to use an image in your control rather than drawing it manually, GDI+ lets you easily paste images to the canvas.

First load the desired image into your application as follows:

Image myimage = Image.FromFile("mypicture.jpg");

For simple image drawing, take a look at the Graphics.DrawImageUnscaled function. For more advanced options, such as image scaling, use Graphics.DrawImage. There are many overloads for both of these functions, so I leave it to the reader to investigate which overload satisfies their desired outcome.

GDI+ also lets you draw to images as if they were a canvas. To do this, you must create a Graphics object from the Image as follows:

Graphics gi = Graphics.FromImage( myimage );

You can use all the same GDI+ drawing functions as mentioned above - it functions exactly the same as drawing to a control's client area except your drawings are stored to an off-screen buffer. HINT: You can use Images as off-screen buffers for double-buffering your controls. Double buffering can be used to reduce flickering when drawing.

Drawing en Masse

Hard-coding GDI+ commands can only take you so far. Say you have a control that draws a 10x10 grid of circles - you're not going to enjoy writing out 100 DrawEllipse commands, especially when you have to manually enter coordinates for each one. Naturally you can use a nested loop to iterate over X and Y positions, but what if they don't conform to some pattern.

It's time to dredge up the old Software Engineering Factory Design pattern. Let's use an object oriented approach to store properties such as location, color, size etc. for primitives. We can have each one implement an interface so it is a simple matter to call on the object to render itself. Take a look at the following code:

// Our interface for easy drawing
public interface IDrawable
{
  void Draw( Graphics g );
}

public class Ellipse : IDrawable
{
  public Color c = Color.Black;
  public Point l = Point(0, 0);
  public Size s = Size(0, 0);
  public int stroke = 0;

  public Ellipse( Color c, Point l, Size s, int stroke )
  {
    this.c = c;
    this.l = l;
    this.s = s;
    this.stroke = stroke;
  }

  public void Draw( Graphics g )
  {
    if ( stroke == 0 )
    {
      g.FillEllipse( SolidBrush( c ), l, s );
    }
    else
    {
      g.DrawEllipse( new Pen( c, stroke ), l, s );
    }
  }
}

We can create similar objects that implement the Drawable interface for rectangles and lines. We can then store an ArrayList of these objects, and draw them whenever the Paint event is triggered.

private ArrayList alDrawingObjects = new ArrayList();
...

alDrawingObjects.Add( new Ellipse( Color.Red, new Point(100, 100), new Size(10, 10), 5 ) );
alDrawingObjects.Add( new Ellipse( Color.Blue, new Point(50, 50), new Size(20, 20), 0 ) );
...

Graphics g = this.CreateGraphis();

foreach ( IDrawable d in alDrawingObjects )
{
  d.Draw( g );
}
Retrieved from http://grouplab.cpsc.ucalgary.ca/cookbook/index.php/VisualStudio/UsingGDIToDrawGraphics
Page last modified on January 18, 2006, at 12:17 AM