Programming GDI+ in Vulcan.Net – part 2
Im SDN-magazine 94 appeared the 1st part of this article about programming GDI+, the successor to the old GDI (Graphics Programming Interface), which is responsible for drawing and painting on the screen and the printer. We’ll finish this mini-serie of 2 papers with the things left, like drawing text and images, performing transformations and printing.
PS: In this 2nd part there will be references to ‘before’ and the like; you can find the referenced info in the 1st part.
Drawing Text
Drawing text is very easy in GDI+, using the DrawString() method. The following overload draws a string with the specified Font at the location specified by oPoint, and paints it with a Brush object (again any of the Brush types mentioned above):
METHOD DrawString(cText AS STRING, oBrush AS Brush, ;
oFont AS Font, oPoint AS Point) AS VOID
Font class
A Font object can be instantiated in many ways. The simplest Font class constructor requires a font name and a font size (as a REAL4 number):
CONSTRUCTOR (cFontName AS STRING, fSize AS REAL4)
An example on creating a font:
oFont := Font{ ‘Arial’ , 10.0 }
// create an “Arial” font with a 10 point size
Now let’s see how we can draw a simple string on our form:
PROTECTED METHOD OnPaint(e AS PaintEventArgs) AS VOID
LOCAL oGraphics AS Graphics
LOCAL oBrush AS SolidBrush
LOCAL oFont AS Font
oGraphics := e:Graphics
oBrush := SolidBrush{ Color.Blue }
oFont:=Font {'Arial',15.0}
oGraphics:DrawString ('Drawing some text', oFont, ;
oBrush, Point{50,50} )
RETURN
FontStyle
There are many situations when we need to print text in Bold, with Italic style, or underlined. In order to make use of such text drawing styles, we need to use another Font class constructor:
CONSTRUCTOR (cFontName AS STRING, fSize AS REAL4,;
eStyle AS FontStyle) CLASS Font
FontStyle is an enumeration with the following members:
- Regular (the default)
- Bold
- Italic
- Underline
- Strikeout
Font styles can be mixed together to define more complex styles:
oFont := Font{ ‘Courier New’ , 12 , ;
FontStyle.Bold + FontStyle.Italic }
// create a “Courier New” font with a 12 point
// size and Bold,Italic style
The following sample draws text on our form with different font types, colors and styles:
PROTECTED METHOD OnPaint(e AS PaintEventArgs) AS VOID
LOCAL oGraphics AS Graphics
LOCAL oBrush AS SolidBrush
LOCAL oFont AS Font
oGraphics := e:Graphics
oBrush := SolidBrush{ Color.Green }
oFont:=Font {'Arial',30.0}
oGraphics:DrawString ('Arial', oFont, oBrush,;
Point{10,50} )
oFont:=Font {'Arial',30.0, FontStyle.Bold}
oGraphics:DrawString ('Arial BOLD', oFont, oBrush,;
Point{10,100} )
oFont:=Font {'Arial',30.0, FontStyle.Italic}
oGraphics:DrawString ('Arial Italic', oFont, oBrush,;
Point{10,150} )
oBrush := SolidBrush{ Color.Red }
oFont:=Font {'Courier New',20.0}
oGraphics:DrawString ('Courier New', oFont, oBrush,;
Point{10,250} )
oFont:=Font {'Courier New',20.0, FontStyle.Strikeout}
oGraphics:DrawString ('Courier New Strikeout', oFont,;
oBrush , Point{10,300} )
oFont:=Font {'Courier New',20.0, FontStyle.Underline}
oGraphics:DrawString ('Courier New Underline', oFont,;
oBrush , Point{10,350} )
RETURN

Important note: In the code snippets we have seen so far, we have used local variables declared in the OnPaint() method to hold our GDI+ objects (fonts, brushes, pens etc). This is helpful for keeping the samples small and easy to follow, but it is not good programming practice; every time OnPaint() is called, those objects are created and destroyed when they get out of scope, resulting to resource and performance issues. In a real-life application, GDI+ objects should be generally declared at the class level (EXPORTs, PROTECTs etc) and created only once.
Drawing text inside a rectangle
Text may also be drawn inside a virtual rectangle, by using another overload of the DrawString() method:
METHOD DrawString(cText AS STRING, oBrush AS Brush, ;
oFont AS Font, oRect AS Rectangle) AS VOID
This version requires a Rectangle object instead of a Point. It draws text starting from the upper-left corner of the rectangle and wraps text when it reaches its right side. The next sample showcases this functionality; it creates a Rectangle object that is used to draw a rectangle on the form and then draws a long string inside the same rectangle. Just in order to make the result a bit fancier, text is drawn using a LinearGradientbrush:
PROTECTED METHOD OnPaint(e AS PaintEventArgs) AS VOID
LOCAL oGraphics AS Graphics
LOCAL oRectangle AS Rectangle
LOCAL oPen AS Pen
LOCAL oBrush AS LinearGradientBrush
LOCAL oFont AS Font
LOCAL cText AS STRING
oGraphics := e:Graphics
cText := "Let's draw a very very long string "+;
"inside this rectangle"
oBrush := LinearGradientBrush{ Point {0, 0} ,;
Point {400, 100}, Color.Blue, Color.Red }
oPen := Pen{ Color.DarkGray}
oPen:Width := 3
oPen:DashStyle := DashStyle.DashDotDot
oRectangle := Rectangle{ 50, 50, 300, 300 }
oGraphics:DrawRectangle( oPen, oRectangle )
oFont:=Font{ 'Arial', 28.0, FontStyle.Bold }
oGraphics:DrawString(cText, oFont, oBrush, oRectangle)
RETURN
Drawing Images
The Graphics class includes a very powerful method for drawing images, named, as one would expect, DrawImage(). This method has literally dozens of overloads and in its simplest version it requires an Image object and a (x,y) coordinate pair, specifying the position where the image should be drawn (in its original size):
METHOD DrawImage(oImage AS Image, x AS INT, y AS INT)
Bitmap class
Image is an abstract class, which again means that it cannot be instantiated directly; instead we will use its most important subclass, Bitmap, which encapsulates a bitmap image. We usually instantiate a Bitmap object by loading a bitmap file from disc, using the following constructor method:
CONSTRUCTOR (cFileName AS STRING)
The cFileName parameter must contain the (fully or partially qualified) filename of a bitmap file. GDI+ supports all commonly used file formats, like .bmp, .tiff, .jpg, .png etc:
oBitmap := Bitmap{ ‘C:\PICTURES\MyPhoto.bmp’}
// fully specified filename
oBitmap := Bitmap{ ‘MyPhoto.jpg’}
// bitmap file is loaded from the current folder
Putting the pieces together, let’s see a sample OnPaint() method, drawing an Image on the form:
PROTECTED METHOD OnPaint(e AS PaintEventArgs) AS VOID
LOCAL oBitmap AS Bitmap
oBitmap := Bitmap{'image.jpg'}
e:Graphics:DrawImage( oBitmap, 10 , 10)
RETURN
This simply draws a JPG image loaded from disc, in its original size. The Image can also be drawn in a different size (scaled or stretched), using another DrawImage() overload:
METHOD DrawImage(oImage AS Image, x AS INT, y AS INT,;
width AS INT, height AS INT)
The following full sample draws three times the same Image on the form; once in its original size and two times scaled in different sizes. It also demonstrates using a class variable for holding the image, since loading it from disc every time OnPaint() is called (in our previous sample) was obviously very bad practice:
USING System.Windows.Forms
USING System.Drawing
FUNCTION Start() AS VOID
LOCAL oForm AS SampleForm
oForm:=SampleForm{}
oForm:Show()
Application.Run(oForm)
RETURN
CLASS SampleForm INHERIT Form
PROTECT oBitmap AS Bitmap
CONSTRUCTOR()
SUPER()
SELF:Text := 'GDI Sample'
SELF:oBitmap := Bitmap{'image.jpg'}
SELF:ClientSize := Size{400,400}
RETURN
PROTECTED METHOD OnPaint(e AS PaintEventArgs) AS VOID
e:Graphics:DrawImage(Self:oBitmap,10, 10)
e:Graphics:DrawImage(Self:oBitmap,10, 250, 130, 130)
e:Graphics:DrawImage(Self:oBitmap,150, 250, 230, 130)
RETURN
END CLASS

Scaling and Rotating
GDI+ offers a very powerful and easy to use mechanism for moving, scaling and rotating anything that can be drawn using a Graphics object. This functionality is implemented mainly in the following three Graphics class methods:
- TranslateTransform()
- ScaleTransform()
- RotateTransform()
Each of the above methods affects the way all graphics are drawn after the transformation, until another transformation takes place (using another of the above methods).
TranslateTransform
METHOD TranslateTransform( x AS REAL4, y AS REAL4 )
By default, the origin of the graphics coordinate system for all drawing operations is located at point {0,0} (upper left corner for a form). The TranslateTransform() method changes the origin by moving it x pixels on the x-axis and y pixels on the y-axis. Everything the user draws after a TranslateTransform operation, will be drawn displaced by (x,y) pixels.
ScaleTransform
METHOD ScaleTransform( rx AS REAL4, ry AS REAL4 )
By using the ScaleTransform() method, every graphics object drawn following it will be drawn scaled by a factor of rx for the x-axis and ry for the y-axis.
RotateTransform
METHOD RotateTransform( rAngle AS REAL4 )
RotateTransform() rotates subsequent objects drawn by rAngle degrees. The central point of the rotation is the current origin of the coordination system, and objects are rotated clockwise. Again, this operation includes all kind of objects like lines, text, images etc.
The following sample is similar to a previous one, drawing text inside a rectangle. Only this time a few transformations take place before any drawing operation: At first, the coordinate origin is moved to the center of our form; next a scaling takes place only in the x-axis, followed by a 30-degree rotation. Lastly, the coordinates’ origin is moved back to point (0,0).
PROTECTED METHOD OnPaint(e AS PaintEventArgs) AS VOID
LOCAL oGraphics AS Graphics
LOCAL oRectangle AS Rectangle
LOCAL oPen AS Pen
LOCAL oBrush AS LinearGradientBrush
LOCAL oFont AS Font
LOCAL cText AS STRING
oGraphics := e:Graphics
oGraphics:TranslateTransform(200.0, 200.0)
oGraphics:ScaleTransform(1.5, 0.8)
oGraphics:RotateTransform(30.0)
oGraphics:TranslateTransform(-200.0, -200.0)
cText := "Let's draw a very very long " +;
"string inside this rectangle"
oBrush := LinearGradientBrush{Point {0,0}, ;
Point {400,100}, Color.Blue , Color.Red }
oPen := Pen{ Color.DarkGray}
oPen:Width := 3
oPen:DashStyle := DashStyle.DashDotDot
oRectangle := Rectangle{ 50, 50, 300, 300 }
oGraphics:DrawRectangle( oPen, oRectangle )
oFont:=Font{ 'Arial', 28.0, FontStyle.Bold }
oGraphics:DrawString( cText, oFont, oBrush, ;
oRectangle )
RETURN
The output looks quite impressive:
Printing
Everything that can be possibly drawn on the screen using a Graphics object can also be sent for printing to a printer. GDI+ makes this (yes, again!) extremely easy and even offers a ready-to-use Print preview dialog! Printing is achieved using a PrintDocument object that supports any printing functionality we might need.
Note: All classes that have to do with printing reside in System.Drawing.Printing namespace.
Everything that can be possibly drawn on the screen using a Graphics object can also be sent for printing to a printer
PrintDocument class
The most important method belonging to the PrintDocument class is Print(), which we need to call in order to start the printing operation. In addition, PrintDocument class defines a number of Events, which are raised while printing progresses:
- BeginPrint, occurs when printing begins
- PrintPage, occurs when a page is being printed
- EndPrint, occurs when printing operation ends
PrintPage event
In this article we will use the PrintPage event. This event is raised whenever a page needs to be printed and by responding to it, the user can supply the data to be printed. Catching this event (or wiring, in .Net terms) can be done using the += operator, as in the following code:
LOCAL oPrintDoc AS PrintDocument
oPrintDoc := PrintDocument{}
oPrintDoc:PrintPage += PrintPageEventHandler{ NULL ,;
@PrintPageFunction()}
FUNCTION PrintPageFunction(o AS OBJECT,;
e AS PrintPageEventArgs) AS VOID
// Do some drawing
// …
RETURN
The function PrintPageFunction() will be automatically called every time the PrintPage event is raised (i.e., when a page needs to be printed). Its second parameter is an instance object of the PrintPageEventArgs class, which in turn supplies a Graphics object which we must use for drawing the page, the same way we’ve used it for drawing on the screen:
FUNCTION PrintPageFunction(o AS OBJECT,;
e AS PrintPageEventArgs) AS VOID
LOCAL oGraphics AS Graphics
oGraphics := e:Graphics
// Do some drawing using oGraphics object
// …
RETURN
PrintPreviewDialog
PrintPreviewDialog class encapsulates a print preview window. Its usage is extremely easy; we only need to assign a PrintDocument object to its Document property and show it on screen using the Show() method (or ShowDialog() if we want to display it as a modal window) :
LOCAL oPreviewDlg AS PrintPreviewDialog
oPreviewDlg := PrintPreviewDialog{}
oPreviewDlg:Document := oPrintDoc // defined earlier
oPreviewDlg:Show()
Printing sample
Let’s summarize the above with a working sample. The following code creates a PrintDocument object, wires its PrintPage event to a printing function and uses a PrintPreviewDialog to let the user preview the document:
USING System.Windows.Forms
USING System.Drawing
USING System.Drawing.Printing
USING System.Drawing.Drawing2D
FUNCTION Start() AS VOID
LOCAL oPrintDoc AS PrintDocument
LOCAL oPreviewDlg AS PrintPreviewDialog
oPrintDoc := PrintDocument{}
oPrintDoc:PrintPage += PrintPageEventHandler{NULL, ;
@PrintPageFunction()}
oPreviewDlg:=PrintPreviewDialog{}
oPreviewDlg:Document := oPrintDoc
oPreviewDlg:ShowDialog()
RETURN
FUNCTION PrintPageFunction(o AS OBJECT, ;
e AS PrintPageEventArgs) AS VOID
LOCAL oGraphics AS Graphics
LOCAL oLBrush AS LinearGradientBrush
LOCAL oSbrush AS SolidBrush
LOCAL oFont AS Font
oGraphics := e:Graphics
oLBrush := LinearGradientBrush { Point{0,0}, ;
Point{800,1100}, Color.Yellow, Color.Blue}
oGraphics:FillRectangle(oLBrush, 60.0, 50.0, ;
700.0, 1050.0)
oFont := Font{'Arial',100,FontStyle.Bold}
oSbrush := SolidBrush{Color.FromArgb(128,255,255,255)}
oGraphics:DrawString('This',oFont,oSbrush,100,100)
oGraphics:DrawString('Is',oFont,oSbrush,110,250)
oGraphics:DrawString('A',oFont,oSbrush,100,400)
oGraphics:DrawString('Print',oFont,oSbrush,100,550)
oGraphics:DrawString('Preview',oFont,oSbrush,100,700)
oGraphics:DrawString('Dialog',oFont,oSbrush,100,850)
RETURN
This is the produced print preview dialog:

Conclusion
The 2 papers have presented the most basic GDI+ features and demonstrated how to use them in Vulcan.NET. However, there is still a huge part that has been left uncovered. Hopefully this paper will achieve its goal, acting as a staring point for you to continue exploring the amazing world of GDI+.