The Graphics Object
All drawing in GDI+ takes place upon the Graphics object and there are several contexts in which you'll find one. During a Paint cycle the Graphics object will be provided in a PaintEventArgs object that is handed to your code in the OnPaint and OnPaintBackground methods of a Windows Forms control. This same event argument is passed to handlers that service the Paint event raised by the OnPaint methods. When printing, the PrintPageEventArgs provided in a PrintPage event will contain a Graphics object for the printer and you can obtain a Graphics object for certain types of image so that you can paint directly on an image in memory as if it were the screen.
Obtaining the Graphics object.
When you're writing programs that place graphics on screen the graphics object will be handed to you wrapped up in a PaintEventArgs object. There are two ways in which your code can get hold of the Graphics object. You can override the protected OnPaint or OnPaintBackground methods or you can add a handler to the Paint event. In all these cases the Graphics object is passed in a PaintEventArgs object. Listing 1 shows the various methods.
Listing 1
protected override void OnPaint(PaintEventArgs e)
{
//get the Graphics object from the PaintEventArgs
Graphics g=e.Graphics;
g.DrawLine(....);
//or use it directly
e.Graphics.DrawLine(....);
//Remember to call the base class or the Paint event won't fire
base.OnPaint (e);
}
//This is the Paint event handler
private void Form1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
//get the Graphics object from the PaintEventArgs
Graphics g=e.Graphics;
g.DrawLine(....);
//or use it directly
e.Graphics.DrawLine(....);
}
Protected Overrides Sub OnPaint(e As PaintEventArgs)
'get the Graphics object from the PaintEventArgs
Dim g As Graphics = e.Graphics
g.DrawLine(....)
'or use it directly
e.Graphics.DrawLine(....)
'Remember to call the base class or the Paint event won't fire
MyBase.OnPaint(e)
End Sub 'OnPaint
'This is the Paint event handler
Private Sub Form1_Paint(sender As Object, e As System.Windows.Forms.PaintEventArgs) Handles MyBase.Paint
'get the Graphics object from the PaintEventArgs
Dim g As Graphics = e.Graphics
g.DrawLine(....)
'or use it directly
e.Graphics.DrawLine(....)
End Sub 'Form1_Paint
You can see in Listing 1 that you can get a reference to the Graphics object and save that for use in the code or use it directly from the PaintEventArgs. Remember not to save the Graphics object outside of the scope of the method.
What to do with it when you have it.
When your code has access to a Graphics object there are a number of things you can do. They fall into five general categories.
-
Stroke a shape. Shapes such as rectangles, ellipses and lines are drawn using a Pen object. The pen can have different thickness or colour and have many other attributes which you will see in another article.
-
Fill a shape. Shapes can be filled with a Brush object. Brushes have many complex settings in GDI+ but they all basically fill an area with colour.
-
Draw a string. Text can be placed on the Graphics surface using the DrawString method.
-
Draw an image. Images can be drawn in any scale using one of the DrawImage methods.
-
Modify the Graphics object. There are many methods that change the way the Graphics object performs. You can change the quality of graphics and have high-quality graphics at the expense of speed. You can change the way the graphics are output to create rotation, zooming or distortion effects.
To demonstrate the use of the Graphics object, the following listing shows the paint handler from a form that demonstrates stroking and filling of shapes. Figure 1 shows this simple application at work.
Figure 1. Stroking and filling.
protected override void OnPaint(PaintEventArgs e)
{
//Get the Graphics object
Graphics g=e.Graphics;
//Draw a line
g.DrawLine(Pens.Red,10,5,110,15);
//Draw an ellipse
g.DrawEllipse(Pens.Blue,10,20,110,45);
//Draw a rectangle
g.DrawRectangle(Pens.Green,10,70,110,45);
//Fill an ellipse
g.FillEllipse(Brushes.Blue,130,20,110,45);
//Fill a rectangle
g.FillRectangle(Brushes.Green,130,70,110,45);
base.OnPaint (e);
}
Protected Overrides Sub OnPaint(ByVal e As PaintEventArgs)
'Get the Graphics object
Dim g As Graphics = e.Graphics
'Draw a line
g.DrawLine(Pens.Red, 10, 5, 110, 15)
'Draw an ellipse
g.DrawEllipse(Pens.Blue, 10, 20, 110, 45)
'Draw a rectangle
g.DrawRectangle(Pens.Green, 10, 70, 110, 45)
'Fill an ellipse
g.FillEllipse(Brushes.Blue, 130, 20, 110, 45)
'Fill a rectangle
g.FillRectangle(Brushes.Green, 130, 70, 110, 45)
MyBase.OnPaint(e)
End Sub 'OnPaint
Things to remember.
Generally, you should only ever use the Graphics object handed to you by the system.
GDI+ is an "Immediate Mode" system. This is to say that the Graphics object has no knowledge of objects, only areas of colour. Shapes and lines are only remembered until they are painted over by a different colour.
You should not store a Graphics object outside of the scope of the method that its given to. When drawing is done, the Graphics object is destroyed until another is needed.
Coordinate Systems
|
|
As with most graphics systems, the position of items on a Graphics surface is controlled by X and Y coordinates. A single coordinate is a pair of numbers that represent the distance across the surface from left to right and the distance down the surface from top to bottom. For example, a line may be drawn from place to place using the code shown in listing 1.
The coordinates are given in the last four parameters of the line drawing command as 0,0,100,200 and represent a line drawn from position 0,0 to position 100,200 as shown in figure 1.
Figure 1.
In the default mode, the coordinates used in GDI+ refer to pixel positions but GDI+ is a Resolution Independent drawing system. This means that you can represent abstract drawing units such as pixels or real-world drawing units such as inches and millimeters in GDI+.
Goodbye to integers
Many graphics systems and particularly old Windows GDI used integer values for the coordinates. This meant that you could not represent an inch and a half by the number 1.5. In GDI+ this is a thing of the past because the coordinates used to place colour on the drawing surface are floating point numbers. This means that if you wish to create a CAD drawing program and show designs in accurate inch or millimeter sizes then you can with GDI+.
Coordinate spaces
There are three distinct coordinate spaces in GDI+. These are;
-
World coordinate space. This is where you put the coordinates that define lines, shapes and points in the 2 dimensional space of the graphics system. World coordinates are abstract values expressed as floating point numbers. Essentially, whenever you draw something it goes into this coordinate space.
-
Page Coordinate Space. The Page space is where the world coordinates are transformed into some real-world value. You can make the Page Space represent pixels, inches millimeters and so-on. This is what makes GDI+ a resolution independent system. You control how the page space interprets the world space by telling the Graphics object what PageUnit is being used and adjusting the PageScale.
-
Device Coordinate Space. This space is controlled by the system and enables the real-world values in the Page Space to be translated to your screen or printer. Device space ensures that a 1 inch long line looks an inch long on the screen and on the printer even though the two devices may have very different pixel resolutions. You have no direct control over this space.
The correct term for this type of system, where coordinates are transformed from one system to another over several steps, is a Graphics Pipeline. The GDI+ graphics pipeline takes the values you use and transforms them from an abstract floating point number to a real-world value and then to the hardware world of the monitor or printer.
Real-world values.
The actual real-world standards available to you for the Page Space are;
-
Pixel. Each unit in world space represents one pixel on the screen or printer. This is the default value for the page units. Artifacts drawn with this setting will be of different sizes on different devices such as screens and printers.
-
Millimeter. Each unit in world space represents one millimeter. This page unit setting will look the same on the printer as it does on the screen.
-
Inch. Each unit in world space represents one inch. This page unit setting will look the same on the printer as it does on the screen.
-
Point. Each unit in world space represents one printers point which is 1/72nd of an inch. Points are the preferred unit of measure for type-intensive applications. This page unit setting will look the same on the printer as it does on the screen.
-
Display. A world space unit will represent 1/75th of an inch. This is a holdover from the days when common CRT dot-pitch was 75 dots-per-inch (DPI). This page unit setting will look the same on the printer as it does on the screen.
-
Document. World space units will represent 1/300th of an inch. This value is a holdover from when laser-printers commonly had 300 DPI resolutions
-
World. Supposedly, this should work the same way a the Pixel system but in practice it often causes an error. Generally, you don't need to worry about this setting.
The following demonstration application shows how the real-world values can be used in your code. The application draws several rectangles that are created to inch, millimeter and pixel sizes. The application will print too so that you can check the sizes against a printed copy and on screen. If you have a plug and play monitor you'll see that the printed sizes and the screen sizes of the real-world values are identical but the pixel values aren't. Figure 1 shows the application at work.
Figure 1. Real-world drawing systems.
The bit that does the drawing of the rectangles is shown in the following listing.
private void DrawRectangles(Graphics g)
{
g.PageUnit=GraphicsUnit.Pixel;
Pen p=new Pen(Color.Black, 3); //this pen will be 3 pixels wide
g.DrawRectangle(p,10,10,200,100); //draw a rectangle in Pixel mode (the default)
p.Dispose();
g.PageUnit=GraphicsUnit.Inch;
p=new Pen(Color.Blue,0.05f); //this pen will be 1/20th of an inch wide
g.DrawRectangle(p,0.1f,1.5f,4f,1f); // draw a rectangle 4" by 1"
p.Dispose();
g.PageUnit=GraphicsUnit.Millimeter;
p=new Pen(Color.Green,1f); //this pen will be 1 millimeter wide
g.DrawRectangle(p,4f,80f,80f,60f); // draw a rectangle 80 by 60 mm
p.Dispose();
}
Private Sub DrawRectangles(ByVal g As Graphics)
g.PageUnit = GraphicsUnit.Pixel
Dim p As New Pen(Color.Black, 3) 'this pen will be 3 pixels wide
g.DrawRectangle(p, 10, 10, 200, 100) 'draw a rectangle in Pixel mode (the default)
p.Dispose()
g.PageUnit = GraphicsUnit.Inch
p = New Pen(Color.Blue, 0.05F) 'this pen will be 1/20th of an inch wide
g.DrawRectangle(p, 0.1F, 1.5F, 4.0F, 1.0F) ' draw a rectangle 4" by 1"
p.Dispose()
g.PageUnit = GraphicsUnit.Millimeter
p = New Pen(Color.Green, 1.0F) 'this pen will be 1 millimeter wide
g.DrawRectangle(p, 4.0F, 80.0F, 80.0F, 60.0F) ' draw a rectangle 80 by 60 mm
p.Dispose()
End Sub 'DrawRectangles
The same code, in DrawRectangles, is used for both printing and screen drawing. Note how, especially for the inch based drawing, floating point values with fractional numbers are used.
The full listing of the code, shown in listing 2, includes the button click handler to print a single page with the rectangles drawn on it. Unless by some strange quirk of fate your printer resolution is identical to that of your screen you'll see two very different versions of the same code.
using System;
using System.Drawing;
using System.Collections;
using System.ComponentModel;
using System.Windows.Forms;
using System.Data;
using System.Drawing.Printing;
namespace RealWorld
{
/// <summary>
/// Summary description for Form1.
/// </summary>
public class Form1 : System.Windows.Forms.Form
{
private System.Windows.Forms.Panel panel1;
private System.Windows.Forms.Button button1;
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.Container components = null;
public Form1()
{
//
// Required for Windows Form Designer support
//
InitializeComponent();
//
// TODO: Add any constructor code after InitializeComponent call
//
}
/// <summary>
/// Clean up any resources being used.
/// </summary>
protected override void Dispose( bool disposing )
{
if( disposing )
{
if (components != null)
{
components.Dispose();
}
}
base.Dispose( disposing );
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
this.panel1 = new System.Windows.Forms.Panel();
this.button1 = new System.Windows.Forms.Button();
this.SuspendLayout();
//
// panel1
//
this.panel1.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom)
| System.Windows.Forms.AnchorStyles.Left)
| System.Windows.Forms.AnchorStyles.Right)));
this.panel1.BackColor = System.Drawing.Color.White;
this.panel1.Location = new System.Drawing.Point(8, 8);
this.panel1.Name = "panel1";
this.panel1.Size = new System.Drawing.Size(312, 320);
this.panel1.TabIndex = 0;
this.panel1.Paint += new System.Windows.Forms.PaintEventHandler(this.panel1_Paint);
//
// button1
//
this.button1.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Right)));
this.button1.Location = new System.Drawing.Point(336, 40);
this.button1.Name = "button1";
this.button1.TabIndex = 1;
this.button1.Text = "Print";
this.button1.Click += new System.EventHandler(this.button1_Click);
//
// Form1
//
this.AutoScaleBaseSize = new System.Drawing.Size(5, 13);
this.ClientSize = new System.Drawing.Size(416, 334);
this.Controls.Add(this.button1);
this.Controls.Add(this.panel1);
this.Name = "Form1";
this.Text = "Form1";
this.ResumeLayout(false);
}
#endregion
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.Run(new Form1());
}
private void DrawRectangles(Graphics g)
{
g.PageUnit=GraphicsUnit.Pixel;
Pen p=new Pen(Color.Black, 3); //this pen will be 3 pixels wide
g.DrawRectangle(p,10,10,200,100); //draw a rectangle in Pixel mode (the default)
p.Dispose();
g.PageUnit=GraphicsUnit.Inch;
p=new Pen(Color.Blue,0.05f); //this pen will be 1/20th of an inch wide
g.DrawRectangle(p,0.1f,1.5f,4f,1f); // draw a rectangle 4" by 1"
p.Dispose();
g.PageUnit=GraphicsUnit.Millimeter;
p=new Pen(Color.Green,1f); //this pen will be 1 millimeter wide
g.DrawRectangle(p,4f,80f,80f,60f); // draw a rectangle 80 by 60 mm
p.Dispose();
}
private void panel1_Paint(object sender, System.Windows.Forms.PaintEventArgs e)
{
DrawRectangles(e.Graphics);
}
private void button1_Click(object sender, System.EventArgs e)
{
PrintDocument pd=new PrintDocument();
pd.PrintPage+=new PrintPageEventHandler(pd_PrintPage);
pd.Print();
}
private void pd_PrintPage(object sender, PrintPageEventArgs e)
{
DrawRectangles(e.Graphics);
e.HasMorePages=false;
}
}
}
Imports System
Imports System.Drawing
Imports System.Collections
Imports System.ComponentModel
Imports System.Windows.Forms
Imports System.Data
Imports System.Drawing.Printing
Namespace RealWorld
'/ <summary>
'/ Summary description for Form1.
'/ </summary>
Public Class Form1
Inherits System.Windows.Forms.Form
Private WithEvents panel1 As System.Windows.Forms.Panel
Private WithEvents button1 As System.Windows.Forms.Button
'/ <summary>
'/ Required designer variable.
'/ </summary>
Private components As System.ComponentModel.Container = Nothing
Public Sub New()
'
' Required for Windows Form Designer support
'
InitializeComponent()
End Sub 'New
'
' TODO: Add any constructor code after InitializeComponent call
'
'/ <summary>
'/ Clean up any resources being used.
'/ </summary>
Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
If disposing Then
If Not (components Is Nothing) Then
components.Dispose()
End If
End If
MyBase.Dispose(disposing)
End Sub 'Dispose
#Region "Windows Form Designer generated code"
'/ <summary>
'/ Required method for Designer support - do not modify
'/ the contents of this method with the code editor.
'/ </summary>
Private Sub InitializeComponent()
Me.panel1 = New System.Windows.Forms.Panel
Me.button1 = New System.Windows.Forms.Button
Me.SuspendLayout()
'
' panel1
'
Me.panel1.Anchor = CType(System.Windows.Forms.AnchorStyles.Top Or System.Windows.Forms.AnchorStyles.Bottom Or System.Windows.Forms.AnchorStyles.Left Or System.Windows.Forms.AnchorStyles.Right, System.Windows.Forms.AnchorStyles)
Me.panel1.BackColor = System.Drawing.Color.White
Me.panel1.Location = New System.Drawing.Point(8, 8)
Me.panel1.Name = "panel1"
Me.panel1.Size = New System.Drawing.Size(312, 320)
Me.panel1.TabIndex = 0
'
' button1
'
Me.button1.Anchor = CType(System.Windows.Forms.AnchorStyles.Top Or System.Windows.Forms.AnchorStyles.Right, System.Windows.Forms.AnchorStyles)
Me.button1.Location = New System.Drawing.Point(336, 40)
Me.button1.Name = "button1"
Me.button1.TabIndex = 1
Me.button1.Text = "Print"
'
' Form1
'
Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
Me.ClientSize = New System.Drawing.Size(416, 334)
Me.Controls.Add(button1)
Me.Controls.Add(panel1)
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)
End Sub 'InitializeComponent
#End Region
'/ <summary>
'/ The main entry point for the application.
'/ </summary>
<STAThread()> _
Shared Sub Main()
Application.Run(New Form1)
End Sub 'Main
Private Sub DrawRectangles(ByVal g As Graphics)
g.PageUnit = GraphicsUnit.Pixel
Dim p As New Pen(Color.Black, 3) 'this pen will be 3 pixels wide
g.DrawRectangle(p, 10, 10, 200, 100) 'draw a rectangle in Pixel mode (the default)
p.Dispose()
g.PageUnit = GraphicsUnit.Inch
p = New Pen(Color.Blue, 0.05F) 'this pen will be 1/20th of an inch wide
g.DrawRectangle(p, 0.1F, 1.5F, 4.0F, 1.0F) ' draw a rectangle 4" by 1"
p.Dispose()
g.PageUnit = GraphicsUnit.Millimeter
p = New Pen(Color.Green, 1.0F) 'this pen will be 1 millimeter wide
g.DrawRectangle(p, 4.0F, 80.0F, 80.0F, 60.0F) ' draw a rectangle 80 by 60 mm
p.Dispose()
End Sub 'DrawRectangles
Private Sub panel1_Paint(ByVal sender As Object, ByVal e As System.Windows.Forms.PaintEventArgs) Handles panel1.Paint
DrawRectangles(e.Graphics)
End Sub 'panel1_Paint
Private Sub button1_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles button1.Click
Dim pd As New PrintDocument
AddHandler pd.PrintPage, AddressOf pd_PrintPage
pd.Print()
End Sub 'button1_Click
Private Sub pd_PrintPage(ByVal sender As Object, ByVal e As PrintPageEventArgs)
DrawRectangles(e.Graphics)
e.HasMorePages = False
End Sub 'pd_PrintPage
End Class 'Form1
End Namespace 'RealWorld
评论