Home

Pens

The Fundamentals of a Pen

As mentioned already, 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 device context class as HDC. The device context is a combination of the platform on which the drawing is performed and the necessary tools to draw on it.

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

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

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

The fnPenStyle argument is characteristic is referred to as the style of the pen. The possible values of this argument are:

Value  Illustration Description
PS_SOLID  A continuous solid line
PS_DASH A continuous line with dashed interruptions
PS_DOT A line with a dot interruption at every other pixel
PS_DASHDOT A combination of alternating dashed and dotted points
PS_DASHDOTDOT A combination of dash and double dotted interruptions
PS_NULL   No visible line
PS_INSIDEFRAME 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 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 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.

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:

#include <windows.h>

LRESULT CALLBACK WindProcedure(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam);

INT WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nCmdShow)
{
    WNDCLASSEX  WndCls;
    static char szAppName[] = "ExoPen";
    MSG         Msg;

    WndCls.cbSize        = sizeof(WndCls);
    WndCls.style         = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;
    WndCls.lpfnWndProc   = WindProcedure;
    WndCls.cbClsExtra    = 0;
    WndCls.cbWndExtra    = 0;
    WndCls.hInstance     = hInstance;
    WndCls.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
    WndCls.hCursor       = LoadCursor(NULL, IDC_ARROW);
    WndCls.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH);
    WndCls.lpszMenuName  = NULL;
    WndCls.lpszClassName = szAppName;
    WndCls.hIconSm       = LoadIcon(hInstance, IDI_APPLICATION);
    RegisterClassEx(&WndCls);

    CreateWindowEx(WS_EX_OVERLAPPEDWINDOW,
                   szAppName, "Pens Fundamentals",
                   WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                   CW_USEDEFAULT, CW_USEDEFAULT, 420, 220,
                   NULL, NULL, hInstance, NULL);

    while( GetMessage(&Msg, NULL, 0, 0) )
    {
        TranslateMessage(&Msg);
        DispatchMessage( &Msg);
    }

    return static_cast<int>(Msg.wParam);
}

LRESULT CALLBACK WindProcedure(HWND hWnd, UINT Msg,
                               WPARAM wParam, LPARAM lParam)
{
	HDC         hDC;
         PAINTSTRUCT Ps;
	HPEN	   hPen;

	switch(Msg)
	{
	case WM_PAINT:
	    hDC = BeginPaint(hWnd, &Ps);
		        
             hPen = CreatePen(PS_DASHDOTDOT, 1, RGB(255, 25, 5));
             SelectObject(hDC, hPen);
             Rectangle(hDC, 20, 22, 250, 125);

	    EndPaint(hWnd, &Ps);
	    break;
	case WM_DESTROY:
	    PostQuitMessage(WM_QUIT);
	    break;
	default:
	    return DefWindowProc(hWnd, Msg, wParam, lParam);
	}
	return 0;
}

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 create a new 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. This is done by calling the DeleteObject() function as follows:

LRESULT CALLBACK WindProcedure(HWND hWnd, UINT Msg,
                               WPARAM wParam, LPARAM lParam)
{
	HDC         hDC;
         PAINTSTRUCT Ps;
	HPEN		hPen;

	switch(Msg)
	{
	case WM_PAINT:
	    hDC = BeginPaint(hWnd, &Ps);
		        
             hPen = CreatePen(PS_DASHDOTDOT, 1, RGB(255, 25, 5));
             SelectObject(hDC, hPen);
             Rectangle(hDC, 20, 22, 250, 125);
	    DeleteObject(hPen);

	    EndPaint(hWnd, &Ps);
	    break;
	case WM_DESTROY:
	    PostQuitMessage(WM_QUIT);
	    break;
	default:
	    return DefWindowProc(hWnd, Msg, wParam, lParam);
    }
    return 0;
}

The Win32 API provides the LOGPEN structure that you can use to individually specify each characteristics of a pen. The LOGPEN structure is defined 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 CreatePen() function.

The lopnWidth argument is provided as a POINT value. Only the POINT::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 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. Here is an example:

LRESULT CALLBACK WindProcedure(HWND hWnd, UINT Msg,
                               WPARAM wParam, LPARAM lParam)
{
	HDC         hDC;
    PAINTSTRUCT Ps;
	HPEN		hPen;
    LOGPEN      LogPen;
	POINT       Pt = { 1, 105 };

	switch(Msg)
	{
	case WM_PAINT:
		hDC = BeginPaint(hWnd, &Ps);
		LogPen.lopnStyle = PS_SOLID;
        LogPen.lopnWidth = Pt;
        LogPen.lopnColor = RGB(235, 115, 5);

        hPen = CreatePenIndirect(&LogPen);

        SelectObject(hDC, hPen);

        Ellipse(hDC, 60, 40, 82, 80);
        Ellipse(hDC, 80, 20, 160, 125);
        Ellipse(hDC, 158, 40, 180, 80);

        Ellipse(hDC, 100, 60, 110, 70);
        Ellipse(hDC, 130, 60, 140, 70);
        Ellipse(hDC, 100, 90, 140, 110);

        DeleteObject(hPen);

		EndPaint(hWnd, &Ps);
		break;
	case WM_DESTROY:
		PostQuitMessage(WM_QUIT);
		break;
	default:
		return DefWindowProc(hWnd, Msg, wParam, lParam);
	}
	return 0;
}

Retrieving a Pen

If you want to know the currently selected pen used on a device context, you can call the GetObject() member function.

 

 
 

Previous Copyright © 2003-2015, FunctionX, Inc. Next