Introduction
Admittedly, the Windows Presentation Foundation (WPF) has established itself as a powerful technology for graphics, amongst other things. This does not mean, however, that the beginner cannot benefit from the .NET Framework 2.0’s provision for drawing graphics. Graphics in .NET begins with drawing lines and shapes, and continues on with work on images and formatting text. Drawing begins with the System.Drawing.Graphics
class. To create an instance, you typically call a control’s CreateGraphics
method. Alternatively, you can create a Graphics
object based on an Image
object if you want to save a picture as a file. Once you create the Graphics
object, you have many methods you can use to perform the drawing:
Clear
: clears the entire surface and fills it with color.
DrawEllipse
: draws an ellipse or circle defined by a bounding rectangle specified by a pair of coordinates, a height, and width.
DrawIcon
and DrawUnstretched
: draw the image represented by the specified icon at the specified coordinates, with or without scaling the icon.
DrawImage
: draws the specified Image
object at the specified location.
DrawLine
: draws a line connecting two points by the coordinate pairs.
DrawLines
: draws a series of line segments that connect an array of Point
structures.
DrawPath
: draws a series of connected lines and curves.
DrawPie
: draws a pie shape defined by an ellipse specified by a coordinate pair, a width, a height, and two radial lines.
DrawPolygon
: draws a shape with three or more sides as defined by an array of Point
structures
DrawRectangle
: draws a rectangle or square, specified by a coordinate pair, a width, and a height.
DrawString
: draws the specified text string at the specified location with the specified Brush
and Font
objects.
To use any of these methods, you must provide an instance of the Pen
class. Typically, you specify the Pen
class’ color and width in pixels. For example, the following code draws a 7-pixel wide red line from the upper left corner (1,1) to a point near the middle of the form (100, 100), as shown in figure below:
To run this code, create a Windows Forms application and add the code to a method run during the form’s Paint
event:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
private System.ComponentModel.Container components;
public MainForm()
{
InitializeComponent();
CenterToScreen();
SetStyle(ControlStyles.ResizeRedraw, true);
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
private void InitializeComponent()
{
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Text = "Fun with graphics";
this.Resize += new System.EventHandler(this.Form1_Resize);
this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
}
[STAThread]
static void Main()
{
Application.Run(new MainForm());
}
private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics g = this.CreateGraphics();
Pen p= new Pen(Color.Red, 7);
g.DrawLine(p, 1, 1, 100, 100);
}
private void Form1_Resize(object sender, System.EventArgs e)
{
}
}
Similarly, the following code draws a blue pie shape with a 60 degree angle, as shown below:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
private System.ComponentModel.Container components;
public MainForm()
{
InitializeComponent();
CenterToScreen();
SetStyle(ControlStyles.ResizeRedraw, true);
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
private void InitializeComponent()
{
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Text = "Fun with graphics";
this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
}
[STAThread]
static void Main()
{
Application.Run(new MainForm());
}
private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics g = this.CreateGraphics();
Pen p = new Pen(Color.Blue, 3);
g.DrawPie(p, 1, 1, 100, 100, -30, 60);
}
}
GDI+
GDI stands for Graphical Device Interface, and the GDI+ library contains those classes that allow you to accomplish all types of rendering operations: rendering of lines, curves, gradients, display images… GDI+ also allows you to draw geometric shapes such as Bezier curves. Again, we acquire an instance of the System.Drawing.Graphics
class by calling the CreateGraphics
method of the Control
or Form
class. It is important to call the Dispose( )
method as soon as possible of the Graphics
class obtained by the call to CreateGraphic( )
.
The System.Drawing.Pen class
As we have seen, in the same way as an artist needs a pen to draw a curve, the methods of the Graphics
class are used to draw lines, curves, or the outline of shapes; all we need is an instance of System.Drawing.Pen
. Here is the code that draws a Bezier curve. You can specify the thickness of the stroke using the Width
property of the Pen
class. In addition, you can indicate if you want the stroke to be full, dashed, or dotted using the DashStyle
property of the Pen
class. You can also specify which type of drawing must occur at the ends of the stroke with the StartCap
and EndCap
properties of the Pen
class:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
private System.ComponentModel.Container components;
public MainForm()
{
InitializeComponent();
CenterToScreen();
SetStyle(ControlStyles.ResizeRedraw, true);
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
private void InitializeComponent()
{
this.AutoScaleBaseSize = new System.Drawing.Size(8, 17);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Text = "Fun with graphics";
this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
}
[STAThread]
static void Main()
{
Application.Run(new MainForm());
}
private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
using ( Graphics g = CreateGraphics() )
{
Pen pen = new Pen( Color.Black);
pen.Width = 5;
pen.DashStyle = DashStyle.Dash;
pen.StartCap = LineCap.RoundAnchor;
pen.EndCap = LineCap.ArrowAnchor;
g.DrawBezier( pen, new Point(10, 30), new Point(30, 200),
new Point(50, -100), new Point(70, 100));
}
}
}
Working with Images
The Image and Bitmap Classes
The System.Drawing.Image
abstract class enables you to create, load, modify, and save images such as BMP files, JPG files, and TIFF files. The Image
class is abstract
, but you can create instances of the class using Image.FromFile
(which accepts a path to the image file as a parameter) and Image.FromStream
(which accepts a System.IO.Stream
object as a parameter). To display an image that is saved to the disk in a form (or as a background for a form or control), use the Graphics.DrawImage( )
method. Here is an example of an image that is saved to my desktop (on a Vista terminal, so the path is c:\users\dave\desktop\image.jpg):
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
private System.ComponentModel.Container components;
public MainForm()
{
InitializeComponent();
CenterToScreen();
SetStyle(ControlStyles.ResizeRedraw, true);
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
private void InitializeComponent()
{
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Text = "Fun with graphics";
this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
}
[STAThread]
static void Main()
{
Application.Run(new MainForm());
}
private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Bitmap bm = new Bitmap(@"C:\users\dave\desktop\imageA.jpg");
Graphics g = this.CreateGraphics();
g.DrawImage(bm, 1, 1, this.Width, this.Height);
}
}
How to Create and Save Pictures
To create a new, blank picture, create an instance of the Bitmap
class with one of the constructors that does not require an existing image. You can then edit it using the Bitmap.SetPixel
method, or you can call Graphics.FromImage
and edit the image using the Graphics
drawing methods. To save the picture, call Bitmap.Save
. The code shown below creates a blank 600 by 600 Bitmap
, creates a Graphics
object based on the Bitmap
, uses the Graphics.FillPolygon
and Graphics.DrawPolygon
methods to draw a shape in the Bitmap
, and then saves it to a file called bm.jpg in the current directory (the .NET Framework 2.0 directory). Like the code above, while many parts are repetitive, it will be run on the console command line. It requires the System.Drawing2D
and System.Drawing.Imaging
namespaces:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
private System.ComponentModel.Container components;
public MainForm()
{
InitializeComponent();
CenterToScreen();
SetStyle(ControlStyles.ResizeRedraw, true);
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
private void InitializeComponent()
{
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Text = "Fun with graphics";
this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
}
[STAThread]
static void Main()
{
Application.Run(new MainForm());
}
private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Bitmap bm = new Bitmap(600, 600);
Graphics g = Graphics.FromImage(bm);
Brush b = new LinearGradientBrush(new Point(1,1),
new Point(600, 600), Color.White, Color.Red);
Point[] points = new Point[] { new Point(77, 500), new Point(590, 100),
new Point(250, 590), new Point(300, 410)};
g.FillPolygon(b, points);
bm.Save("bm.jpg", ImageFormat.Jpeg);
}
}
The way this code is written, it will compile and then execute to output a plain form. However, if you type “bm.jpg”, you will find that you have created a file that is actually saved as a JPEG file. The size shown below has been reduced:
How to Use Icons
Icons are transparent bitmaps of specific sizes that are used by Windows to convey status. The .NET Framework provides standard 40 by 40 system icons as properties of the SystemIcons
class. The simplest way to add an icon is to call the Graphics.DrawIcon
method:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
private System.ComponentModel.Container components;
public MainForm()
{
InitializeComponent();
CenterToScreen();
SetStyle(ControlStyles.ResizeRedraw, true);
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
private void InitializeComponent()
{
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Text = "Fun with graphics";
this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
}
[STAThread]
static void Main()
{
Application.Run(new MainForm());
}
private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics g = this.CreateGraphics();
g.DrawIcon(SystemIcons.Question, 40, 40);
}
}
Things to do in Graphics Land when You’ve had Enough
Graphics applications can have functionality, but this requires taking a look at some of the core events of the Control
class:
Click
, DoubleClick
: the Control
class defines numerous events triggered in response to mouse input.
MouseEnter
, MouseLeave
, MouseDown
, MouseUp
, MouseMove
, MouseHover
, MouseWheel
.
KeyPress
, KeyUp
, KeyDown
: the Control
class defines numerous events triggered in response to keyboard input.
This application is meant to show how Windows Forms responds to standard input. Run it on the command line. It give a pop-up box for every key pressed, and gives the coordinates of every mouse location. It will also output a message box for every type of mouse click or keypress. We first start by building a new Form
class (which we have done – MainForm
), set the initial size of the form to some arbitrary dimensions, and override the Dispose()
method. For the coordinates to be displayed on top of the form, the boundaries of the Rectangle
have to be established:
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
private System.ComponentModel.Container components;
public MainForm()
{
Top = 100;
Left = 75;
Height = 100;
Width = 500;
MessageBox.Show(Bounds.ToString(), "Current rect");
this.MouseUp += new MouseEventHandler(OnMouseUp);
this.MouseMove += new MouseEventHandler(OnMouseMove);
this.KeyUp += new KeyEventHandler(OnKeyUp);
InitializeComponent();
CenterToScreen();
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
MessageBox.Show("Disposing this Form");
}
private void InitializeComponent()
{
this.components = new System.ComponentModel.Container();
this.Size = new System.Drawing.Size(300,300);
this.Text = "Form1";
}
[STAThread]
static void Main()
{
Application.Run(new MainForm());
}
protected void OnMouseUp(object sender, MouseEventArgs e)
{
if(e.Button == MouseButtons.Left)
MessageBox.Show("Left click!");
else if(e.Button == MouseButtons.Right)
MessageBox.Show("Right click!");
else if(e.Button == MouseButtons.Middle)
MessageBox.Show("Middle click!");
}
protected void OnMouseMove(object sender, MouseEventArgs e)
{
this.Text = "Current Pos: (" + e.X + ", " + e.Y + ")";
}
public void OnKeyUp(object sender, KeyEventArgs e)
{
MessageBox.Show(e.KeyCode.ToString(), "Key Pressed!");
}
}
Run this code on the command line. Type ‘type con > ControlApp.cs’, then paste the code to the console, and then press Ctrl-Z. Use the /target:winexe flag and provide a /reference:System.dll. Once the code is run, we are now in a position to see how we can use graphics. More to the point, we can now see how graphical applications can respond to standard input. For example, assume that you have to render some image outside of the scope of a standard Paint
event handler – that you want to draw a small circle at the (x, y) position where the mouse was clicked. The first thing to do is to obtain a valid Graphics
object, which can be obtained using the static Graphics.FromHwnd( )
method. Notice that we are passing our current handle as the sole parameter, and note that the Handle
property is inherited from the Control
class. This should make sense, but wouldn’t it work better with the MouseUp logic and add a new point to an internal collection of Point
objects, followed by a call to Invalidate( )
(close the window and erase the point that was drawn):
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class MainForm : System.Windows.Forms.Form
{
private ArrayList myPts = new ArrayList();
public MainForm()
{
InitializeComponent();
CenterToScreen();
this.Text = "Basic Paint Form (click on me)";
}
public void MyPaintHandler(object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
}
private void InitializeComponent()
{
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Text = "Form1";
this.MouseDown += new System.Windows.Forms.MouseEventHandler(this.MainForm_MouseDown);
this.Paint += new System.Windows.Forms.PaintEventHandler(this.MainForm_Paint);
}
[STAThread]
static void Main()
{
Application.Run(new MainForm());
}
private void MainForm_MouseDown(object sender, System.Windows.Forms.MouseEventArgs e)
{
Graphics g = Graphics.FromHwnd(this.Handle);
myPts.Add(new Point(e.X, e.Y));
Invalidate();
}
private void MainForm_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics g = e.Graphics;
g.DrawString("Hello GDI+", new Font("Times New Roman", 20),
new SolidBrush(Color.Black), 0, 0);
foreach(Point p in myPts)
g.DrawEllipse(new Pen(Color.Green), p.X, p.Y, 10, 10);
}
}
The circles shown are the result of the mouse clicks on those specified locations. So with that, assume that we have three JPEG images. We want to click one and have it identified on the top of the form. Consider this code:
using System;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
public class Form1 : System.Windows.Forms.Form
{
private System.ComponentModel.Container components;
private Image bMapImageA;
private Image bMapImageB;
private Image bMapImageC;
private Rectangle rectA = new Rectangle(10, 10, 90, 90);
private Rectangle rectB = new Rectangle(10, 110, 90, 90);
private Rectangle rectC = new Rectangle(10, 210, 90, 90);
GraphicsPath myPath = new GraphicsPath();
private bool isImageClicked = false;
private int imageClicked;
public Form1()
{
InitializeComponent();
bMapImageA = new Bitmap(@"c:\users\dave\desktop\imageA.jpg");
bMapImageB = new Bitmap(@"c:\users\dave\desktop\imageB.jpg");
bMapImageC = new Bitmap(@"c:\users\dave\desktop\imageC.jpg");
myPath.StartFigure();
myPath.AddLine(new Point(150, 10), new Point(120, 150));
myPath.AddArc(200, 200, 100, 100, 0, 90);
Point point1 = new Point(250, 250);
Point point2 = new Point(350, 275);
Point point3 = new Point(350, 325);
Point point4 = new Point(250, 350);
Point[] points = {point1, point2, point3, point4};
myPath.AddCurve(points);
myPath.CloseFigure();
CenterToScreen();
}
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
private void InitializeComponent()
{
this.AutoScaleBaseSize = new System.Drawing.Size(5, 11);
this.ClientSize = new System.Drawing.Size(292, 273);
this.Text = "Form1";
this.MouseUp += new System.Windows.Forms.MouseEventHandler(this.Form1_MouseUp);
this.Paint += new System.Windows.Forms.PaintEventHandler(this.Form1_Paint);
}
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
private void Form1_MouseUp(object sender, System.Windows.Forms.MouseEventArgs e)
{
Point mousePt = new Point(e.X, e.Y);
if(rectA.Contains(mousePt))
{
isImageClicked = true;
imageClicked = 0;
this.Text = "You clicked image A";
}
else if(rectB.Contains(mousePt))
{
isImageClicked = true;
imageClicked = 1;
this.Text = "You clicked image B";
}
else if(rectC.Contains(mousePt))
{
isImageClicked = true;
imageClicked = 2;
this.Text = "You clicked image C";
}
else if(myPath.IsVisible(mousePt))
{
isImageClicked = true;
imageClicked = 3;
this.Text = "You clicked the strange shape...";
}
else
{
isImageClicked = false;
this.Text = "Images";
}
Invalidate();
}
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
Graphics g = e.Graphics;
g.DrawImage(bMapImageA, rectA);
g.DrawImage(bMapImageB, rectB);
g.DrawImage(bMapImageC, rectC);
g.FillPath(Brushes.AliceBlue, myPath);
if(isImageClicked == true)
{
Pen outline = new Pen(Color.Red, 5);
switch(imageClicked)
{
case 0:
g.DrawRectangle(outline, rectA);
break;
case 1:
g.DrawRectangle(outline, rectB);
break;
case 2:
g.DrawRectangle(outline, rectC);
break;
case 3:
g.DrawPath(outline, myPath);
break;
default:
break;
}
}
}
}
So, what do we get?
Click the image, and have it identified on the top of the form. Not a lot of functionality, but definitely can be expanded on. Look back at the code and notice the array of Point
structures, as well as the file paths for the images.
Animation and Double Buffering
To create an animation by displaying different frames at the rate of several dozen times per second, use an instance of the System.Windows.Forms.Timer
class which will take care of triggering a regular call to a method by the thread of the window. The following code shows how to create an animation representing a square which rotates in the center of a window:
using System;
using System.Drawing;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
public partial class AnimForm : Form {
private float angle;
private Timer timer = new Timer();
private BufferedGraphics bufferedGraphics;
public AnimForm() {
BufferedGraphicsContext context = BufferedGraphicsManager.Current;
context.MaximumBuffer = new Size( this.Width + 1, this.Height + 1 );
bufferedGraphics = context.Allocate( this.CreateGraphics(),
new Rectangle( 0, 0, this.Width, this.Height) );
timer.Enabled = true;
timer.Tick += OnTimer;
timer.Interval = 20;
timer.Start();
}
private void OnTimer( object sender, System.EventArgs e ) {
angle ++;
if (angle > 359)
angle = 0;
Graphics g = bufferedGraphics.Graphics;
g.Clear( Color.Black );
Matrix matrix = new Matrix();
matrix.Rotate( angle, MatrixOrder.Append );
matrix.Translate( this.ClientSize.Width / 2,
this.ClientSize.Height/ 2, MatrixOrder.Append );
g.Transform = matrix;
g.FillRectangle( Brushes.Azure, -100, -100, 200, 200 );
bufferedGraphics.Render( Graphics.FromHwnd( this.Handle ) );
}
[System.STAThread]
public static void Main() {
Application.Run( new AnimForm() );
}
}