Using GDI To Draw Graphics

VisualStudio.UsingGDIToDrawGraphics History

Hide minor edits - Show changes to output

January 18, 2006, at 12:17 AM by 207.236.235.30 -
Changed line 4 from:
(:cell:) [+ '''Download: ''' [[(Attach:)GDIDraw.zip]] - Demonstrates drawing, antialiasing, and repainting. +] \\
to:
(:cell:) [+ '''Download: ''' [[(Attach:)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: ''' [[(Attach:)GDIAnimation.zip]] - Demonstrates double buffering, color blending, animation, and factory drawing. +] \\
to:
[+ '''Download: ''' [[(Attach:)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: ''' [[(Attach:)GDIDraw.zip]] +] \\
[+ '''Download: ''' [[(Attach:)GDIAnimation
.zip]] +] \\
to:
(:cell:) [+ '''Download: ''' [[(Attach:)GDIDraw.zip]] - Demonstrates drawing, antialiasing, and repainting. +] \\
[+ '''Download: ''' [[(Attach:)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: ''' [[(Attach:)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: ''' [[(Attach:)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.