Think of a Windows pen as being like an ink pen at your desk. A Windows pen object has three attributes:
Just a Minute: A pen is perfect in situations in which you must draw a geometric shape or line. Although you can use a bitmap for complicated images, you easily can draw squares, rectangles, circles, and other basic shapes using GDI objects.
You can create and use pens with a variety of styles. Cosmetic pens are extremely quick and are mapped directly into device units. This makes them useful for drawing things like frames, borders, grid lines, and other screen objects that should not be affected by the current device context-mapping mode. Geometric pens require more CPU power but offer more styles. You can manipulate geometric pens using any of the available mapping modes.
Pens are also useful for drawing three-dimensional highlighting or other effects. It's not uncommon for pens and other GDI objects to be used to simulate controls in Windows; before Windows 95 was released, early versions of property pages used pens to draw simulated "tabs."
Time Saver: The number of styles available for geometric pens is much larger than for cosmetic pens. However, cosmetic pens have much less overhead. You should use cosmetic pens whenever possible. The next few sections discuss the various options available for cosmetic and geometric pens.
Examples of the styles available for pens.
The first constructor has no arguments:
CPen aGreenPen; aGreenPen.Create( PS_SOLID, 1, RGB(0,255,0);If you use this constructor, use the Create member function to actually create the pen and make it ready for use.
The second constructor provided for CPen also is used for cosmetic pens:
CPen penDottedAndRed( PS_DOT, 1, RGB(255,0,0) );This version of the constructor accepts three parameters: the pen style, width, and color. In this case, the CPen instance is a dotted red pen.
The third constructor used for CPen objects enables any type of pen to be created. It also uses more parameters, as shown in Listing 12.1.
LOGBRUSH lbrGrnHatch; lbrGrnHatch.lbStyle = BS_HATCHED; lbrGrnHatch.lbColor = RGB(0,255,0); lbrGrnHatch.lbHatch = HS_DIAGCROSS; CPen penGeometric( PS_DOT | PS_GEOMETRIC | PS_ENDCAPROUND, 50, &lbrGrnHatch, 0, NULL );The constructor's first parameter is the pen's style, with the C++ OR operator, |, used to combine all styles that are applied to the pen. The second parameter for the constructor is the width; if the pen is cosmetic, it must be set to 1. The third parameter is a pointer to a LOGBRUSH structure. In Listing 12.1, lbrGrnHatch is defined as a diagonally cross-hatched green brush.
The last two parameters are rarely used; they define a user-supplied pattern for the pen. These two parameters are used only if the pen is created with the PS_USERSTYLE attribute. The fourth parameter is the number of elements in the style array, whereas the fifth parameter is an array of DWORD values, each used to define the length of a dash or space in the pen's pattern.
CPen* pOldPen = pDC->SelectStockObject( BLACK_PEN );
The new version of the Mapping Mode dialog box.
Use the values from Table 12.1 for the new controls you add to the Mapping Mode dialog box, using ClassWizard to add member variables to the CMapModeDlg class. All existing controls should remain as they are.
| Edit Control | Resource ID | Variable Name | Type |
| Pen Width | IDC_WIDTH | m_nPenWidth | int |
| Ellipse Width | IDC_CXELLIPSE | m_cxEllipse | int |
| Ellipse Height | IDC_CYELLIPSE | m_cyEllipse | int |
// Attributes private: // Variables added for Section 11 CMap< int, int, CString, CString > m_map; int m_nMapMode; // Variables added for Section 12 - pens int m_cxEllipse; int m_cyEllipse; int m_nPenWidth;Do not modify the declarations for existing member variables.
Add three new lines to the CDCTestView constructor to initialize the new variables added to the CDCTestView class. Listing 12.3 is the new version of the CDCTestView constructor. Most of the function should already be entered; you must add only the last three lines before the closing parenthesis.
CDCTestView::CDCTestView()
{
m_nMapMode = MM_TEXT;
m_map.SetAt( MM_ANISOTROPIC, "MM_ANISOTROPIC" );
m_map.SetAt( MM_HIENGLISH, "MM_HIENGLISH" );
m_map.SetAt( MM_HIMETRIC, "MM_HIMETRIC" );
m_map.SetAt( MM_ISOTROPIC, "MM_ISOTROPIC" );
m_map.SetAt( MM_LOENGLISH, "MM_LOENGLISH" );
m_map.SetAt( MM_LOMETRIC, "MM_LOMETRIC" );
m_map.SetAt( MM_TEXT, "MM_TEXT" );
m_map.SetAt( MM_TWIPS, "MM_TWIPS" );
m_nPenWidth = 1;
m_cxEllipse = 100;
m_cyEllipse = 200;
}
Modify the CDCTestView::OnViewMapMode function to handle the changes
in the Mapping Mode dialog box. Listing 12.4 provides the source code for
the new version of OnViewMapMode. There are a total of six new
source code lines, each marked with a comment. You should need to add only
these six lines; the rest of the function should have been created already.
void CDCTestView::OnViewMapMode()
{
CMapModeDlg dlg;
// The next three lines are added for Section 12 - pens
dlg.m_nPenWidth = m_nPenWidth; // 1
dlg.m_cxEllipse = m_cxEllipse; // 2
dlg.m_cyEllipse = m_cyEllipse; // 3
if( dlg.DoModal() == IDOK )
{
// The next three lines are added for Section 12 - pens
m_nPenWidth = dlg.m_nPenWidth; // 4
m_cxEllipse = dlg.m_cxEllipse; // 5
m_cyEllipse = dlg.m_cyEllipse; // 6
POSITION pos;
pos = m_map.GetStartPosition();
while( pos != NULL )
{
CString szMapMode;
int nMapMode;
m_map.GetNextAssoc( pos, nMapMode, szMapMode );
if( szMapMode == dlg.m_szCombo )
{
m_nMapMode = nMapMode;
break;
}
}
InvalidateRect( NULL );
}
}
Last but not least, you must create a new version of the OnDraw
function. Most of this version of OnDraw is new because you are
now drawing with pens instead of listing device attributes. Use the source
code provided in Listing 12.5 for the new version of CDCTestView::OnDraw.
void CDCTestView::OnDraw(CDC* pDC)
{
pDC->SetMapMode( m_nMapMode );
// Draw an ellipse based on the current map-mode and values
// supplied by the user.
CRect rcClient;
GetClientRect( rcClient );
pDC->DPtoLP( rcClient ); // Covert device units to logical
CPoint ptCenter( rcClient.Width()/2, rcClient.Height()/2 );
CRect rcEllipse( ptCenter.x - ( m_cxEllipse/2 ),
ptCenter.y - ( m_cyEllipse/2 ),
ptCenter.x + ( m_cxEllipse/2 ),
ptCenter.y + ( m_cyEllipse/2 ) );
CPen penRed( PS_SOLID, m_nPenWidth, RGB(255,0,0) );
CPen* pOldPen = pDC->SelectObject( &penRed );
pDC->Ellipse( rcEllipse );
// Draw a black box around the ellipse, using one of the stock
// pens.
pDC->SelectStockObject( BLACK_PEN );
pDC->MoveTo( rcEllipse.TopLeft() );
pDC->LineTo( rcEllipse.right, rcEllipse.top );
pDC->LineTo( rcEllipse.BottomRight() );
pDC->LineTo( rcEllipse.left, rcEllipse.bottom );
pDC->LineTo( rcEllipse.left, rcEllipse.top );
// Draw an arc using the client area as a bounding rectangle.
// Clip the arc so that only the lower-left half is displayed.
CPen penDottedAndGreen( PS_DOT, 1, RGB(0,255,0) );
pDC->SelectObject( &penDottedAndGreen );
pDC->Arc(rcClient, rcClient.TopLeft(),rcClient.BottomRight());
pDC->SelectObject( &pOldPen );
}
Compile and run the DCTest project, and experiment by changing the values
in the Mapping Mode dialog box. Figure 12.3 shows the DCTest project running
with the mapping mode set to MM_TEXT.
The DCTest example after adding pen GDI objects.
Every brush has several attributes:
Your Windows program can create four basic types of brushes:
Examples of styles available for brushes.
You create each of these brush types using a different function call. For example, a solid brush is created using CreateSolidBrush, whereas a hatched brush is created using CreateHatchBrush. When using the CBrush class, it's also possible to call a specialized CBrush constructor to construct the CBrush object in the desired style. You will use the CBrush class later in this section.
You can create solid and hatch brushes with a color attribute that specifies the color used when the brush fills an area. In the case of a hatched brush, the color specifies the color of the hatching lines.
Examples of brush hatching styles.
CBrush brBlack( RGB(0,0,0) );Alternatively, use two-step construction, where the brush object is constructed and then explicitly created, like this:
CBrush brBlack(); brBlack.CreateSolidBrush( RGB(0,0,0) );The advantage of using two-step construction is that the function used to create a brush returns FALSE if the function fails.
Unlike pens, which use style bits to determine the type of pen to be created, separate functions are used for different brush types. In two-step construction, you can use three functions to create a brush after you construct the CBrush object:
CBrush brGreen( RGB(0,0,255) );Using this brush is equivalent to using the default constructor and then calling CreateSolidBrush.
The third form of the CBrush constructor is used to create a hatched brush, and takes the hatching style and hatch color as parameters:
CBrush brGray( HS_CROSS, RGB(192,192,192) );This constructor is equivalent to using the default constructor and then calling CreateHatchBrush.
Use the fourth and final constructor for CBrush to create brushes that have bitmap patterns. You will learn more about bitmaps in Section 15, "Using Bitmaps"; for now, just remember that a bitmap can be used as a pattern for a brush. The constructor takes a pointer to a CBitmap object as a parameter:
CBrush brArrow( &bmpArrow );The CBitmap object can be up to 8x8 pixels. If the bitmap is larger, only the upper-left eight pixel squares are used for the brush pattern.
The LOGBRUSH structure has three data members:
LOGBRUSH lbrRed; lbrRed.lbrStyle = BS_HATCH; lbrRed.lbrColor = RGB(255,0,0); lbrRed.lbrHatch = HS_CROSS; CBrush theRedBrush; theRedBrush.CreateBrushIndirect( &lbrRed );
CPen* pOldBrush = pDC->SelectStockObject( BLACK_BRUSH );
CColorDialog dlgColor;
if( dlgColor.DoModal() )
{ //....
If IDOK is returned from the dialog box, the CColorDialog::GetColor
function gets the selected color value. The example in the next section
uses the Common Color dialog box to choose a brush color. You will use
other common dialog boxes in later sections. (For example, the font-selection
dialog box is used in Section 13, "Fonts.")
Modify the Mapping Mode dialog box to allow the user to choose a color for the dialog box and a brush used for the view. The CMapModeDlg class needs two new variables: a COLORREF for the currently selected color, and a CBrush object that has been created using the current color. Listing 12.7 contains the changes to the CMapModeDlg class declaration. Add the new code in the Dialog Data section, just after the AFX_DATA comments.
// Dialog Data
//{{AFX_DATA(CMapModeDlg)
enum { IDD = IDD_MAP_MODE };
CString m_szCombo;
int m_cyEllipse;
int m_cxEllipse;
int m_nPenWidth;
//}}AFX_DATA
// Variable added in Section 12
public:
COLORREF m_clrChoice;
private:
CBrush m_brControl;
You must change the Mapping Mode dialog box slightly for this example.
Remove the pen-width edit control and add a pushbutton control, as shown
in Figure 12.6. Use ClassWizard to remove the m_nPenWidth member
variable from the CMapModeDlg class.
figure 12.6
The new version of the Mapping Mode dialog box.
Use the values from Table 12.2 for the new button control.
| Resource ID | Caption | Function |
| IDC_COLOR | &Color... | CMapModeDlg::OnColor |
Using ClassWizard, add a new message-handling function to the CMapModeDlg class named CMapModeDlg::OnColor. The source code for OnColor is provided in Listing 12.8.
void CMapModeDlg::OnColor()
{
CColorDialog dlgColor;
if( dlgColor.DoModal() == IDOK )
{
m_clrChoice = dlgColor.GetColor();
// If the brush already exists, delete the current
// GDI object before calling CreateSolidBrush
if( m_brControl.Detach() )
m_brControl.DeleteObject();
m_brControl.CreateSolidBrush( m_clrChoice );
InvalidateRect( NULL );
}
}
The OnColor function creates a Common Color dialog box and displays
it using DoModal. If the user selects a new color, the color is
collected and the brush is updated. If the brush has previously been created,
the Detach and DeleteObject functions must be called
to destroy the current brush before creating a new brush.
HBRUSH CMapModeDlg::OnCtlColor(CDC* pDC,CWnd* pWnd,UINT nCtlColor)
{
if( nCtlColor == CTLCOLOR_DLG || nCtlColor == CTLCOLOR_STATIC )
{
pDC->SetBkMode( TRANSPARENT );
return (HBRUSH)m_brControl.GetSafeHandle();
}
else
return CDialog::OnCtlColor(pDC, pWnd, nCtlColor);
}
Time Saver: The easiest way to deal with colored dialog boxes is shown in Listing 12.9, where the text-drawing mode is set to transparent by calling SetBkMode. If this line is commented out, you will see that the static text has colored areas around each color. By setting the drawing mode to transparent, the text is drawn without including the text background color, allowing the dialog box color to show through.
The GetSafeHandle function is used with all GDI objects to return a handle to the underlying object. A CBrush object returns an HBRUSH handle; a CPen object returns an HPEN, and so on.
The WM_CTLCOLOR message is sent for every control type found in the dialog box. It's possible to set different colors for each control type by testing for the values found in Table 12.3. If a brush is not returned, determine the return value by calling CDialog::OnCtlColor.
| Control Message Value | Control Type |
| CTLCOLOR_BTN | Button control |
| CTLCOLOR_DLG | Dialog box |
| CTLCOLOR_EDIT | Edit control |
| CTLCOLOR_LISTBOX | List box control |
| CTLCOLOR_MSGBOX | Message box |
| CTLCOLOR_SCROLLBAR | Scrollbar control |
| CTLCOLOR_STATIC | Static control |
// Attributes private: // Variables added for Section 11 CMap< int, int, CString, CString > m_map; int m_nMapMode; // Variables added for Section 12 - pens int m_cxEllipse; int m_cyEllipse; // Variable added for Section 12 - brushes COLORREF m_clrChoice;
CDCTestView::CDCTestView()
{
m_nMapMode = MM_TEXT;
m_map.SetAt( MM_ANISOTROPIC, "MM_ANISOTROPIC" );
m_map.SetAt( MM_HIENGLISH, "MM_HIENGLISH" );
m_map.SetAt( MM_HIMETRIC, "MM_HIMETRIC" );
m_map.SetAt( MM_ISOTROPIC, "MM_ISOTROPIC" );
m_map.SetAt( MM_LOENGLISH, "MM_LOENGLISH" );
m_map.SetAt( MM_LOMETRIC, "MM_LOMETRIC" );
m_map.SetAt( MM_TEXT, "MM_TEXT" );
m_map.SetAt( MM_TWIPS, "MM_TWIPS" );
// The next two lines are added for Section 12 - pens
m_cxEllipse = 100;
m_cyEllipse = 200;
// The next line is added for Section 12 - brushes
m_clrChoice = RGB(0,0,0);
}
Modify the CDCTestView::OnViewMapMode function as shown in Listing
12.12. The code in this listing removes all references to the m_nPenWidth
variable, and the function now tracks the color selected by the user. A
total of two lines have been removed and one line added to the existing
function.
void CDCTestView::OnViewMapMode()
{
CMapModeDlg dlg;
// The next two lines are added for Section 12 - pens
dlg.m_cxEllipse = m_cxEllipse;
dlg.m_cyEllipse = m_cyEllipse;
// The next line is added for Section 12 - brushes
dlg.m_clrChoice = m_clrChoice;
if( dlg.DoModal() == IDOK )
{
// The next two lines are added for Section 12 - pens
m_cxEllipse = dlg.m_cxEllipse;
m_cyEllipse = dlg.m_cyEllipse;
// The next line is added for Section 12 - brushes
m_clrChoice = dlg.m_clrChoice;
POSITION pos;
pos = m_map.GetStartPosition();
while( pos != NULL )
{
CString szMapMode;
int nMapMode;
m_map.GetNextAssoc( pos, nMapMode, szMapMode );
if( szMapMode == dlg.m_szCombo )
{
m_nMapMode = nMapMode;
break;
}
}
InvalidateRect( NULL );
}
}
Modify the CDCTestView::OnDraw function as shown in Listing 12.13.
The new version of OnDraw uses a CBrush object to fill
the view window with a red brush. Another CBrush object is used
to draw an ellipse in the center of the view using a user-defined color
to fill the figure.
void CDCTestView::OnDraw(CDC* pDC)
{
CRect rcClient;
GetClientRect( rcClient );
pDC->DPtoLP( rcClient );
CBrush brBackground( RGB( 255, 255, 100 ) );
pDC->FillRect( rcClient, &brBackground );
CPoint ptCenter( rcClient.Width()/2, rcClient.Height()/2 );
CRect rcEllipse( ptCenter.x - ( m_cxEllipse/2 ),
ptCenter.y - ( m_cyEllipse/2 ),
ptCenter.x + ( m_cxEllipse/2 ),
ptCenter.y + ( m_cyEllipse/2 ) );
CBrush brEllipse( m_clrChoice );
CBrush* pOldBrush = pDC->SelectObject( &brEllipse );
pDC->Ellipse( rcEllipse );
pDC->SelectObject( &pOldBrush );
}
Compile and run the DCTest project, and experiment by changing the values
in the Mapping Mode dialog box. Also experiment with different colors for
the dialog box and ellipse by clicking the Color button. Figure 12.7 shows
an example of the DCTest project running.
The DCTest example after adding brush GDI objects.
A Yes, in fact, you are always drawing with both a pen and a brush. Earlier in this section the ellipse and rectangle were drawn with specific pens; the shapes weren't filled in because the default brush for a device context is the hollow, or null brush. Because this brush has no effect, it seems as though no brush is being used.
Q Why aren't pushbuttons affected by handling WM_CTLCOLORBTN?
A The WM_CTLCOLORBTN message will affect radio buttons and check boxes, but not pushbuttons. To change the color of a pushbutton you must make it an owner-drawn pushbutton and take over responsibility for drawing every aspect of the button.
2. What are the two types of pens?
3. What MFC class is used to manage pens?
4. What stock pens are maintained by Windows?
5. What styles are available for cosmetic pens?
6. What styles are available for geometric pens?
7. What are the four types of brushes?
8. What stock brushes are maintained by Windows?
9. What MFC class is used to manage brushes?
10. What function is used to draw a circle?
2. Modify the DCTest example so that the ellipse is drawn with a red outline.
Download Sample Programs - DCTest.zip(Pen Demo) DCTest.zip(Brush Demo)
|
|
|
|