Using GDI To Draw Graphics

VisualStudio.UsingGDIToDrawGraphics History

Hide minor edits - Show changes to markup

January 18, 2006, at 12:17 AM by 207.236.235.30 -
Changed line 4 from:

(:cell:) Download: GDIDraw.zip - Demonstrates drawing, antialiasing, and repainting. \\

to:

(:cell:) Download: GDIDraw.zip - Demonstrates drawing, antialiasing, coordinate translation, and repainting. \\

January 18, 2006, at 12:16 AM by 207.236.235.30 -
Changed lines 5-6 from:

Download: GDIAnimation.zip - Demonstrates double buffering, color blending, animation, and factory drawing.

to:

Download: GDIAnimation.zip - Demonstrates double buffering, animation, and factory drawing.

January 18, 2006, at 12:16 AM by 207.236.235.30 -
Added line 6:
January 18, 2006, at 12:16 AM by 207.236.235.30 -
Changed lines 4-5 from:

(:cell:) Download: GDIDraw.zip
Download: GDIAnimation.zip \\

to:

(:cell:) Download: GDIDraw.zip - Demonstrates drawing, antialiasing, and repainting.
Download: GDIAnimation.zip - Demonstrates double buffering, color blending, animation, and factory drawing. \\

January 18, 2006, at 12:14 AM by 207.236.235.30 -
Added line 5:

Download: GDIAnimation.zip \\

January 17, 2006, at 10:31 PM by 207.236.235.30 -
Added lines 3-7:

(:table border=1 cellpadding=17 cellspacing=0 align=entre bgcolor=#eeeeee :) (:cell:) Download: GDIDraw.zip
Unzip all files (executables plus dependencies) into a single folder (:tableend:)

January 16, 2006, at 11:42 PM by 207.236.235.30 -
Changed lines 143-144 from:

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, expecially 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.

to:

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.

January 16, 2006, at 11:42 PM by 207.236.235.30 -
Changed line 142 from:

Renderring en Masse

to:

Drawing en Masse

January 16, 2006, at 11:41 PM by 207.236.235.30 -
Changed line 149 from:

public interface Drawable

to:

public interface IDrawable

Changed line 154 from:

public class Ellipse : Drawable

to:

public class Ellipse : IDrawable

Added lines 181-198:

=]

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.

(:source lang=csharp tabwidth=2 :) [= 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 );

}

January 16, 2006, at 11:36 PM by 207.236.235.30 -
Added lines 141-181:

Renderring 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, expecially 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:

(:source lang=csharp tabwidth=2 :) // Our interface for easy drawing public interface Drawable { void Draw( Graphics g ); } public class Ellipse : Drawable { 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 ); } } }

January 16, 2006, at 11:11 PM by 207.236.235.30 -
Added lines 123-140:

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: (:source lang=csharp tabwidth=2 :) 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:

(:source lang=csharp tabwidth=2 :) 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.

January 16, 2006, at 10:58 PM by 207.236.235.30 -
Changed lines 48-53 from:
to:

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: (:source lang=csharp tabwidth=2 :) g.SmoothingMode = System.Drawing.Drawing2D.SmoothingMode.AntiAlias;

January 16, 2006, at 10:54 PM by 207.236.235.30 -
Changed line 37 from:
to:

OR

Added lines 42-48:

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. (:source lang=csharp tabwidth=2 :) g.Clear( Color.Black ); g.Clear( Control.BackColor );

January 16, 2006, at 10:52 PM by 207.236.235.30 -
Changed lines 94-95 from:
  • A System.Drawing.StringFormat object, which contains the style for horizontal and vertical alignment, text direction, clipping, line spacing, etc..
to:
  • OPTIONALLY: A System.Drawing.StringFormat object, which contains the style for horizontal and vertical alignment, text direction, clipping, line spacing, etc..
January 16, 2006, at 10:50 PM by 207.236.235.30 -
Changed lines 45-46 from:
  • For lines/outlines, the Pen object stores a Color and thickness in pixels for the line.
  • For solids/fills, the SolidBrush object represent a solid Color.
to:
  • For lines/outlines, the Pen object stores a Color and thickness in pixels for the line. You can also use the Pens enumeration which gives a 1 pixel thick pen in any color from the Color enumeration.
  • For solids/fills, the SolidBrush object represent a solid Color. You can also use the Brushes enumeration which gives a SolidBrush in any color from the Color enumeration.
Added lines 73-87:

(:source lang=csharp tabwidth=2 :) 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 );

Added lines 95-110:

(:source lang=csharp tabwidth=2 :) 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: (:source lang=csharp tabwidth=2 :) int stringwidth = g.MeasureString( "Hello world!", new Font("Times", 12, FontStyle.Regular) );

January 16, 2006, at 10:33 PM by 207.236.235.30 -
Added lines 56-79:

Drawing Primitives

The Graphics object has methods for drawing operations.

  • The Graphics.DrawLine Function draws a line from:
    • A Pen, a starting (X,Y) and ending (X,Y).
    • A Pen, a starting Point, and an ending Point.
  • The Graphics.DrawRectangle Function draws the outline of a rectangle from:
    • A Pen and X, Y, Width, Height values.
    • A Pen, a Point, and a Size.
    • A Pen and a Rectangle
  • The Graphics.FillRectangle Function draws a solid rectangle from:
    • A Brush and X, Y, Width, Height values.
    • A Brush , a Point, and a Size.
    • A Brush and a Rectangle
  • The Graphics.DrawEllipse Function draws the outline of an ellipse. Note that the (X,Y) coordinates indicate the top left anchor for the ellipse, NOT the center point as you would probably expect. This is a minor annoyance that you must deal with. This primitive is drawn using the same parameters as from DrawRectangle.
  • The Graphics.FillEllipse Function draws a solid ellipse. Once again the (X,Y) coordinates indicate the top left anchor for the ellipse, NOT the center point. This primitive is drawn using the same parameters as from FillRectangle.

Drawing Text

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

  • The String of text that you wish to draw.
  • A Font object.
  • A Brush.
  • A PointF object indicating the top lefthand corner of the text bounds OR a RectangleF representing a rectangular clipping area for the text.
  • A System.Drawing.StringFormat object, which contains the style for horizontal and vertical alignment, text direction, clipping, line spacing, etc..
January 16, 2006, at 10:16 PM by 207.236.235.30 -
Changed lines 47-48 from:
to:
  • For text, the Font object represents the Font Face, Point Size, and optional font styles such as Bold and Italic. Styles are applied using a boolean OR (| operator) on the FontStyles enumeration.
Added line 54:

Font f = new Font("Times", 12, FontStyle.Bold | FontStyle.Italic | FontStyle.Underline);

January 16, 2006, at 10:12 PM by 207.236.235.30 -
Added lines 48-53:

(:source lang=csharp tabwidth=2 :) 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 );

January 16, 2006, at 10:09 PM by 207.236.235.30 -
Changed lines 40-47 from:

=]

to:

=]

Drawing Styles

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

  • The Color enumeration has a list of named web colors. This can be nice for quick access to colors whose names that you know (Red, Green, Blue, Yellow, Purple...), but you can create your own color from RGB values using the Color.FromArgb( r, g, b );, where r, g, and b are integers between 0 (lowest intensity) and 255 (highest intensity).
  • For lines/outlines, the Pen object stores a Color and thickness in pixels for the line.
  • For solids/fills, the SolidBrush object represent a solid Color.
January 16, 2006, at 10:00 PM by 207.236.235.30 -
Changed lines 35-39 from:

Graphics g = Control.CreateGraphics();

to:

Graphics g = this.CreateGraphics(); =]

(:source lang=csharp tabwidth=2 :) [= Graphics g = MyControl.CreateGraphics();

January 16, 2006, at 09:59 PM by 207.236.235.30 -
Added lines 34-36:

(:source lang=csharp tabwidth=2 :) Graphics g = ''Control''.CreateGraphics();

January 16, 2006, at 09:58 PM by 207.236.235.30 -
Changed lines 24-33 from:
  • The PointToClient function takes a Point object (assumed relative to the screen) and convertis it to a coordinate relative to the control, returning another Point object.
to:
  • The PointToClient function takes a Point object (assumed relative to the screen) and convertis it to a coordinate relative to the control, returning another Point object.

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.

  • You can force a repaint event by calling the control's Refresh() function.
  • Repainting of the current control and all sub-controls can be suspended by calling the SuspendLayout() function, and resumed again by calling ResumeLayout().

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.

January 16, 2006, at 09:49 PM by 207.236.235.30 -
Deleted lines 7-11:

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.

  • These coordinates are relative to the client area of the form or control, so the top lefthand corner is (0, 0). * X coordinates increase from Left to Right, Y coordinates increase from Top to Bottom.
  • You can use negative X and Y coordinates to place something beyond the top or left boundaries of the panel.
  • The furthest right and bottom coordinate can be found by accessing the panel's Width and Height properties respectively.
Changed lines 16-24 from:

C# provides methods for translating Control Coordinates to Screen Coordinates.

to:

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.

  • These coordinates are relative to the client area of the form or control, so the top lefthand corner is (0, 0). * X coordinates increase from Left to Right, Y coordinates increase from Top to Bottom.
  • You can use negative X and Y coordinates to place something beyond the top or left boundaries of the panel.
  • The furthest right and bottom coordinate can be found by accessing the panel's Width and Height properties respectively.
  • A control's Bounds property returns a Rectangle object representing the area of the control.

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 PointToScreen function takes a Point object (assumed relative to the control) and converts it to absolute coordinates on the screen, returning another Point object.
  • The PointToClient function takes a Point object (assumed relative to the screen) and convertis it to a coordinate relative to the control, returning another Point object.
January 16, 2006, at 09:42 PM by 207.236.235.30 -
Changed lines 8-21 from:

Every Form or Control has a main panel called a Client Area, where sub-controls are placed, or the control itself is drawn.

to:

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.

  • These coordinates are relative to the client area of the form or control, so the top lefthand corner is (0, 0). * X coordinates increase from Left to Right, Y coordinates increase from Top to Bottom.
  • You can use negative X and Y coordinates to place something beyond the top or left boundaries of the panel.
  • The furthest right and bottom coordinate can be found by accessing the panel's Width and Height properties respectively.

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

  • The Point object stores an integer X and Y coordinate.
  • The PointF object stores a floating-point X and Y coordinate.
  • The Size object stores an integer Width and Height value.
  • The SizeF object stores a floating-point Width and Height value.
  • The Rectangle object is an aggregation of a Point (X, Y) and a Size (Width, Height) to give a sense of area.
  • The RectangleF object is an aggregation of a PointF (X, Y) and SizeF (Width, Height) to give a sense of area.

C# provides methods for translating Control Coordinates to Screen Coordinates.

January 16, 2006, at 09:32 PM by 207.236.235.30 -
Added lines 1-8:

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

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.

Every Form or Control has a main panel called a Client Area, where sub-controls are placed, or the control itself is drawn.