GDI Accessories and Tools: Pens

 

Introduction to Pens

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 class as the CDC class. To draw, we have been using a pointer to CDC. Now, we need to realize that, declaring a CDC variable does not just give us access to the device context, it also initializes it.

The device context is a combination of the platform on which the drawing is performed and the necessary tools to draw on it. As such, when declaring a CDC variable, it also creates and selects a black pen. This is why we have been able to draw lines and other shapes so far.

The Fundamentals of a Pen

A pen is a tool used to draw lines and curves on a device context. In the 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

When you declare a CDC variable, it creates and selects a pen that can draw a 1-pixel width black line. If you want a more refined pen, the MFC provides the CPen class. Therefore, the first step in creating a pen is to declare a variable of CPen type, which can be done using the default constructor as follows:

CPen NewPen;

To create a pen, you must specify the desired characteristics. This can be done with another CPen constructor declared as follows:

CPen(int nPenStyle, int nWidth, COLORREF crColor);

Alternatively, if you want to use a variable declared using the default constructor, you can then call the CPen::CreatePen() method. Its syntax is:

BOOL CreatePen(int nPenStyle, int nWidth, COLORREF crColor);

The arguments of the second constructor and the CreatePen() method are used to specify the properties that the pen should have:

The Style: This characteristic is passed as the nPenStyle argument. The possible values of this argument are:

Value Illustration Description
PS_SOLID Solid A continuous solid line
PS_DASH Dash A continuous line with dashed interruptions
PS_DOT Dot A line with a dot interruption at every other pixel
PS_DASHDOT Dash Dot A combination of alternating dashed and dotted points
PS_DASHDOTDOT Dash Dot Dot A combination of dash and double dotted interruptions
PS_NULL No visible line
PS_INSIDEFRAME Inside Frame A line drawn just inside of the border of a closed shape

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 to set the pen at every other pixel.

The Width: The nWidth argument is the width used to draw the lines or borders of a closed shape. 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 PS_DASH, PS_DOT, PS_DASHDOT, or PS_DASHDOTDOT but set a width higher than 1, the line would be drawn as PS_SOLID.

The Color: The default color of pen on the device context is black. If you want to control the color, specify the desired value for the crColor argument.

Based this, using the second constructor, you can declare and initialize a CPen variable as follows:

CPen NewPen(PS_DASHDOTDOT, 1, RGB(255, 25, 5));

The same pen can be created using the CreatePen() method as follows:

CPen NewPen;

NewPen.CreatePen(PS_DASHDOTDOT, 1, RGB(255, 25, 5));

After creating a pen, you can select it into the desired device context variable and then use it as you see fit, such as drawing a rectangle. Here is an example:

void CExoView::OnDraw(CDC* pDC)
{
	CExoDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);

	CPen NewPen;

	NewPen.CreatePen(PS_DASHDOTDOT, 1, RGB(255, 25, 5));

	pDC->SelectObject(&NewPen);
	
	pDC->Rectangle(20, 22, 250, 125);
}

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.

After using a pen, between exiting the function or event that created it, you should get rid of it and restore the pen that was selected previously. Here is an example:

void CExoView::OnDraw(CDC* pDC)
{
	CExoDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);

	CPen NewPen;

	NewPen.CreatePen(PS_DASHDOTDOT, 6, RGB(255, 25, 5));

	CPen* pPen = pDC->SelectObject(&NewPen);
	
	pDC->Rectangle(20, 22, 250, 125);

	// Restore the previous pen
	pDC->SelectObject(pPen);
}
Create Pen

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

typedef struct tagLOGPEN { 
    UINT     lopnStyle; 
    POINT    lopnWidth; 
    COLORREF lopnColor; 
} 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 not be visible.

The lopnStyle argument follows the same rules we reviewed for the nPenStyle argument of the second constructor and the CreatePen() method.

The lopnWidth argument is provided as a POINT or a CPoint value. Only the POINT::x or the CPoint::x value is considered.

The lopnColor argument is a color and can be provided following the rules we reviewed for colors.

After initializing the LOGPEN variable, call the CPen::CreatePenIndirect() member function to create a pen. The syntax of the CreatePenIndirect() method is:

BOOL CreatePenIndirect(LPLOGPEN lpLogPen);

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. Here is an example:

void CExoView::OnDraw(CDC* pDC)
{
	CExoDoc* pDoc = GetDocument();
	ASSERT_VALID(pDoc);

	CPen NewPen;
	LOGPEN LogPen;

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

	NewPen.CreatePenIndirect(&LogPen);

	CPen* pPen = pDC->SelectObject(&NewPen);
	
	pDC->Ellipse(60, 40, 82, 80);
	pDC->Ellipse(80, 20, 160, 125);
	pDC->Ellipse(158, 40, 180, 80);
	
	pDC->Ellipse(100, 60, 110, 70);
	pDC->Ellipse(130, 60, 140, 70);
	pDC->Ellipse(100, 90, 140, 110);

	// Restore the previous pen
	pDC->SelectObject(pPen);
}
LOGPEN

 

Retrieving a Pen

If you want to know the currently selected pen used on a device context, you can call the CPen::GetLogPen() member function. Its syntax is:

int GetLogPen(LOGPEN* pLogPen);

To get the characteristics of the current pen, pass a pointer to the LOGPEN structure to this GetLogPen() method. The returned pLogPen value would give you the style, the width, and the color of the pen.

 

Home Copyright © 2003-2004 FunctionX, Inc.