|
If
you have programmed under Windows, you would definitely be
familiar with the term GDI (Graphical Device Interface). GDI
simplifies drawing by providing an interface to the hardware
devices like the screen or printer, such that the programmers
dont need to bother about hardware details and their
differences. The same program can work on different display
adapters, printers, keyboards, etc without any modifications
to the code.
.NET uses GDI+, an extension to GDI, which further simplifies
drawing. GDI+ has added several new features like graphics
paths, support to image file formats, image transformation,
etc. GDI+ has also introduced fundamental changes in the programming
model used by GDI.
Changes in programming model
GDI uses the concept of Device Context (DC). Device Context
is a structure that stores all the drawing related information,
namely, features of the display device and attributes that
decide the appearance of the drawing. Every device context
is associated with a window. To draw on a window, one must
first obtain a device context of that window. To change any
attribute, say, pen colour, it first has to be selected in
the device context by calling the SelectObject( ) method.
Once selected, all the drawing is done using this pen, until
such time as another pen is selected in device context.
GDI+ works with graphics context that plays a
similar role as device context. The graphics context is also
associated with a particular window and contains information
specifying how a drawing would be displayed. However, unlike
device context, it does not contain information about pen,
brush, font, etc. In order to draw with a new pen we simply
have to pass an object of Pen class to the DrawLine( ) method
(this method draws a line on window). We can pass different
Pen objects in each call to DrawLine( ) method to draw the
lines in different colours. Thus GDI uses a stateful model,
whereas GDI+ uses a stateless model. The Graphics class encapsulates
the graphics context. Not surprisingly, most of the drawing
is done by calling methods of the Graphics class.
Working with GDI+
So lets learn how to draw text and graphics using GDI+
by writing a small program. Create a Windows Application.
Windows programmers know that a window receives WM_PAINT message
when it is to be painted. We need to handle this message if
we want to do any painting in the window. In .NET we can do
this either by overriding the virtual method OnPaint( ) of
the Form class or by writing a handler for the Paint event.
The base class implementation of OnPaint( ) invokes the Paint
event handler through delegate. Hence we should write our
code in the Paint event handler.
Add the Paint handler to the form. The Form1_Paint( ) handler
would look like this.
private void Form1_Paint (object sender, PaintEventArgs e)
{
}
The first parameter passed to the Form1_Paint( ) handler contains
the reference to the object of a control that sends the event.
The second parameter contains more information about the Paint
event. We would first see how to display a string on the form.
To display the string we would use DrawString( ) method of
the Graphics class.
private void Form1_Paint ( object sender, PaintEventArgs e )
{
Graphics g = e.Graphics;
Font myfont = new Font (Times New Roman, 60); StringFormat f = new StringFormat();
f.Alignment = StringAlignment.Center;
f.LineAlignment = StringAlignment.Center;
g.DrawString (Hello!,myfont,Brushes.Blue,ClientRectangle,f);
}
The Graphics property of the PaintEventArgs class contains
reference to the Graphics object. We can use this reference
for drawing. In a handler other than Paint event handler we
can obtain the Graphics reference using the CreateGraphics(
) method of the Form class. The DrawString() method has several
overloaded versions. We used one that allows us to display
centrally aligned text in desired font and colour. The first
parameter passed to the DrawString( ) method is the string
we wish to display. The second parameter is the font in which
text would get displayed. We have created a font by passing
the font name and font size to the constructor of the Font
class. The text gets filled with the brush colour specified
as the third parameter. The fourth parameter specifies the
surrounding rectangle. We have passed ClientRectangle property
of Form class that contains a rectangle representing the client
area of the form. To centrally align the text we have used
the StringFormat class. The Alignment and LineAlignment properties
of this class contain horizontal and vertical alignment of
text respectively.
The Graphics class contains various methods to draw different
shapes. This includes drawing rectangle, line, arc, bezier,
curve, pie, etc. We would add the code in Form1_Paint( ) handler
that draws rectangles in different pens and brushes. You would
be able to draw other shapes on similar lines.
The following code draws a rectangle using green coloured
pen having line thickness of 3.
Pen p=new Pen(Color.Green, 3);
g.DrawRectangle(p,20,20,150,100);
The Pen class encapsulates various styles of pens like solid,
dash, dash-dot, etc. We can change the style of pen using
the DashStyle property of the Pen class. This is shown in
the following statement.
p.DashStyle = DashStyle.Dash;
If we want, we can specify custom pen style by using the DashPattern
property. There are several other properties of the Pen class
that allow us to specify the pen type (hatch fill, gradient
fill, solid color, etc), cap style, join style, etc.
Unlike GDI, GDI+ provides separate methods for rectangle and
filled rectangle. To fill the rectangle we need to pass a
Brush object. This is shown below.
HatchBrush
hb=new HatchBrush(HatchStyle.BackwardDiagonal,
Color.Red, Color.Black);
g.FillRectangle (hb,200,20,150,100);
We have used hatch brush to fill the rectangle. The hatch
brush is created using the HatchBrush class. We have mentioned
the hatch style as BackwardDiagonal. The rectangle will get
filled with the hatch brush in a red and black colour combination.
Like the HatchBrush class there are several other classes
used to fill the shapes with, namely, SolidBrush, TextureBrush,
and LinearGradientBrush. Gradient brush is something that
was not available in GDI. Let us see how to use it.
LinearGradientBrush gb=new LinearGradientBrush(ClientRectangle, Color.BlanchedAlmond,Color.Aquamarine,90); g.FillRectangle(gb,ClientRectangle);
Here, we have created an object of the LinearGradientBrush
class and passed to its constructor the rectangle to be filled,
and two colours that form the gradient pattern. The last parameter
specifies the angle from which we wish to draw. Specifying
90 would fill the window vertically.
Coordinates and Transformations
In the Graphics methods we specify coordinates in a two-dimensional
coordinate system. The system has the origin at the top-left
corner and x and y axes point to the right and down respectively.
All the methods take coordinates in pixels. The coordinates
passed to Graphics methods are world coordinates. When we
pass world coordinates to a method, they firstly get translated
into page coordinates (logical coordinates) and then into
device coordinates (physical coordinates). Ultimately, the
shape gets drawn in device coordinates. In both the page and
device coordinate system the measure of unit is the samepixels.
The coordinate system can be customised by shifting the origin
to some other place in the client area and by setting a different
measure of unit. Let us see how this can be achieved. We would
first draw a horizontal line having 1 inch of width. Here
is the code to do this.
private void Form1_Paint(object sender, PaintEventArgs e)
{
Graphics g=e.Graphics;
g.PageUnit=GraphicsUnit.Inch;
Pen p=new Pen (Color.Green, 1/g.DpiX);
g.DrawLine (p,0,0,1,0);
}
Here, firstly we have set the PageUnit property to GraphicsUnit.Inch
specifying that the unit of measure is an inch. We have created
a Pen object and set its width to 1 / g.Dpix. The Dpix property
of the Graphics class indicates a value, in dots per inch,
for the horizontal resolution supported by this Graphics object.
Note that this is necessary because now Pen object also assumes
1 unit = 1 inch. So, if we dont set the pen width like
this, a line with 1 inch pen width would get drawn. Next we
drew a line having one unit measure, which happens to be an
inch.
Let us now shift the origin to the centre of the client area
and draw the line again.
private void Form1_Paint (object sender, PaintEventArgs e)
{
Graphics g = e.Graphics;
g.PageUnit = GraphicsUnit.Inch;
g.TranslateTransform ( ( ClientRectangle.Width/g.DpiX )/ 2,
(ClientRectangle.Height/g.DpiY)/2);
Pen p = new Pen ( Color.Green, 1/g.DpiX);
g.DrawLine (p,0,0,1,0);
}
Here, after setting the unit to an inch using the PageUnit
property, we have called the TranslateTransform( ) method
to shift the origin to the centre of the client area. This
method maps the world coordinates to page coordinates and
so the transformation is called world transformation. The
x and y values we have passed to the TranslateTransform( )
method get added to every x and y values we pass to the Graphics
methods. Finally, we created a pen having proper width and
drew the line.
GDI+
also allows us to orient the x and y axes direction
to the specified angle. For this, it provides the RotateTransform(
) method. For example, if we call the RotateTransform( ) method
before drawing the line as shown below,
g.RotateTransform(30);
then line would get displayed slanting downwards, 30 degrees
below the base line. We can use this functionality of the
RotateTransform( ) method to create an application like an
analogue clock.
Disposing
graphics objects
Whenever we open a file, we close it after we have finished
working with the file. This is because a handle is associated
with the file and it remains open if we dont close it
explicitly. Similarly, GDI+ resources like pens, brushes and
fonts need to be disposed of because they encapsulate GDI+
handles in them. To release the GDI+ resources, we can call
the Dispose( ) method on every object that is to be released.
For example, the following statement would release the pen
object represented by penobject using the Dispose( ) method.
penobject.Dispose();
We must also release the Graphics object obtained by calling
the CreateGraphics() method.
 |
Yashavant
Kanetkar, one of the first Express Computer columnists,
is an established software expert, speaker and author
with several best-sellers to his credit, including titles
like “Let Us C” and the “Fundas” series. Contact him at
kanet@nagpur.dot.net.in |
|