GDI
Accessories and Tools: Pens, CPen,
CreatePen, LOGPEN
|
|
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 ucancode.neted 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 |
|
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
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);
}
|
|
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);
}
|
|
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.
|