Home

GDI Tools: Pens

 

The Fundamentals of a Pen

In the previous lesson, we mentioned that, in order to draw, two primary objects are needed: a platform and a tool. So far, we were using the platform, called a device context. We introduced the main device context represented by, or accessed with, the TCanvas class. To draw, we have been using a pointer to TCanvas. A TCanvas variable does not just give us access to the device context, it also initializes it.

A pen is a tool used to draw lines and curves on a device context. In graphics programming, a pen is also used to draw the borders of a geometric closed shape such as a rectangle or a polygon. To make it an efficient tool, a pen must produce some characteristics on the lines it is asked to draw. These characteristics can range from the width of the line drawn to their colors, from the pattern applied to the level of visibility of the lines. To manage these properties, Microsoft Windows considers two types of pens: cosmetic and geometric.

  • A pen is referred to as cosmetic when it can be used to draw only simple lines of a fixed width, less than or equal to 1 pixel
  • A pen is geometric when it can assume different widths and various ends.

Creating and Selecting a Pen

As mentioned already, the device context is a combination of the platform on which the drawing is performed and the necessary tools to draw on it. To make drawing quick, when selecting a device context, which is already done for all objects that have a TCanvas member variable, the device context is initialized with some default values. One of these is a pen. This is why we have been able to draw shapes so far, without realizing that the device context was already equipped with a pen for us. A pen is like any other device context tool. It is equipped with characters that define its behavior and control its role on the canvas.

The VCL supports pens through a class called TPen. When an object that has a TCanvas member variable comes up, it is equipped with a pen already and you can use it as you see fit. To use that pen, simply access the Pen member variable of the TCanvas class. If you want to explicitly create a pen, you can declare a TPen variable using the new operator. Whether using the TCanvas::Pen member variable or a TPen variable, once you have a pen, you can change its characteristics through its own member variables.

Win32 Support of Pens

The Win32 library defines a pen as HPEN, a handle to a pen. The Win32 library supports pens through various functions. To make sure an HPEN can be used on a canvas, the TCanvas class of the VCL is equipped with a Handle member variable. This variable is used to get a handle to a Win32 HPEN and make it available to a VCL object.

To create a pen in Win32, you can call the CreatePen() function. Its syntax is:

HPEN CreatePen(int fnPenStyle, int nWidth, COLORREF crColor);

After calling this function, make sure you retrieve its return value so you can assign it to the Handle member variable of TCanvas::Pen.

The Win32 API also provides the LOGPEN structure that you can use to individually specify each characteristics of a logical pen. The LOGPEN structure is created as follows:

typedef struct tagLOGPEN { 
	UINT nStyle; 
	POINT nWidth; 
	COLORREF nColor; 
} LOGPEN, *PLOGPEN;

To use this structure, declare a variable of LOGPEN type or a pointer. Then initialize each member of the structure. If you do not, its default values would be used and the line may not be visible. After initializing the LOGPEN variable, call the CreatePenIndirect() function to create a pen. The syntax of the CreatePenIndirect() function is:

HPEN CreatePenIndirect(CONST LOGPEN *lplgpn);

The LOGPEN value is passed to this method as a pointer. After this call, the new pen is available and can be selected into a device context variable for use.

Characteristics of a Pen

The color of a pen is one of its most visual characteristics. To support colors, the TPen class provides a Color property. Its value follows the same rules we reviewed for a color object. Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	Canvas->Pen->Color = clRed;
	Canvas->MoveTo(20, 15);
	Canvas->LineTo(255, 82);
}
//---------------------------------------------------------------------------
Pens

Although most lines are drawn continuously, the VCL and the Win32 library support non-continuous lines. This characteristic is controlled by the style of the pen. For a TPen object, this would be the Style property. For the CreatePen() function or the LOGPEN structure, this would be the fnPenStyle argument or member variable. The possible values of the style are:

 

TPen::Style CreatePen() and
LOGPEN
Illustration Description
psSolid PS_SOLID A continuous solid line
psDash PS_DASH A continuous line with dashed interruptions
psDot PS_DOT A line with a dot interruption at every other pixel
psDashDot PS_DASHDOT A combination of alternating dashed and dotted points
psDashDotDot PS_DASHDOTDOT A combination of dash and double dotted interruptions
psClear PS_NULL   No visible line
psInsideFrame PS_INSIDEFRAME   A line drawn just inside of the border of a closed shape
 

The Width or nWidth of a pen specifies how wide its line(s) would be. Its value should be greater than or equal to 1. If you specify a width less than 1, it would be restored to 1. The value of the width cannot be applied to all types of pens. This property is directly influenced by the style. A cosmetic pen can have a width of only 1 pixel. If you specify a higher width, it would be ignored. A geometric pen can have a width of 1 or more pixels but the line can only be solid or null. This means that, if you specify the style as psDash, PS_DASH, psDot, PS_DOT, psDashDot, PS_DASHDOT, psDashDotDot, or PS_DASHDOTDOT but set a width higher than 1, the line would be drawn as PS_SOLID.

If you are using the CreatePen() function, to specify the type of pen you are creating, as cosmetic or geometric, use the bitwise OR operator to combine one of the above styles with one of the following:

  • PS_COSMETIC: used to create a cosmetic pen

  • PS_GEOMTERIC: used to create a geometric pen

If you are creating a cosmetic pen, you can also add (bitwise OR) the PS_ALTERNATE style to set the pen at every other pixel.

Here is an example:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	Canvas->Pen->Style = psDashDotDot;
	Canvas->Pen->Width = 1;
	Canvas->Pen->Color = TColor(RGB(255, 25, 5));

	Canvas->Rectangle(20, 22, 250, 125);
}
//---------------------------------------------------------------------------
Pen

Once a pen has been selected, any drawing performed and that uses a pen would use the currently selected pen. If you want to use a different pen, you can either create a new pen or change the characteristics of the current pen.

Here is an example that uses the HPEN:

//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
	HPEN NewPen;
	LOGPEN LogPen;

	LogPen.lopnStyle = PS_SOLID;
	LogPen.lopnWidth = Point(1, 105);
	LogPen.lopnColor = RGB(235, 115, 5);

	NewPen = CreatePenIndirect(&LogPen);
	Canvas->Pen->Handle = NewPen;

	Canvas->Ellipse(60, 40, 82, 80);
	Canvas->Ellipse(80, 20, 160, 125);
	Canvas->Ellipse(158, 40, 180, 80);

	Canvas->Ellipse(100, 60, 110, 70);
	Canvas->Ellipse(130, 60, 140, 70);
	Canvas->Ellipse(100, 90, 140, 110);
}
//---------------------------------------------------------------------------
 

Retrieving a Pen

If you want to know the currently selected pen used on a device context, simple declare a pointer to TPen an initialize it with the TCanvas::Pen of the current Canvas.

Home Copyright 2005-2007 FunctionX, Inc.