Home Index  | Back

New Page 1
Lesson 1 -Building Your First Windows Application

1.1 : Creating a Windows Application

1.2 : Creating a Dialog-Based Application

1.3 : Creating DLLs, Console Applications, and More

1.4 : Changing Your AppWizard Decisions
 1.5 : Understanding AppWizard’s Code
 1.6 : Understanding a MDI Application
 1.7 : Understanding the Components of a Dialog-Based Application

Lesson 2 - Dialogs and Controls

2.1 : Understanding Dialog Boxes

2.2 : Creating a Dialog Box Resource

 2.3 : Writing a Dialog Box Class
 2.4 : Using the Dialog Box Class
Lesson 3 - Messages and Commands

3.1 : Understanding Message Routing

3.2 : Understanding Message Loops

 3.3 : Reading Message Maps
 3.4 : Learning How ClassWizard Helps You Catch Messages
 3.5 : Recognizing Messages
 3.6 : Understanding Commands
 3.7 : Understanding Command Updates
 3.8 : Learning How ClassWizard Helps You Catch Commands and Command Updates

Lesson 4 - Documents and Views

4.1 : Understanding the Document Class

4.2 : Understanding the View Class

4.3 : Creating the Rectangles Application

 4.4 : Other View Classes

4.5 : Document Templates, Views, and Frame Windows

Lesson 5 - Drawing on the Screen

5.1 :Understanding Device Contexts

 5.2 : Introducing the Paint1 Application
 5.3 : Building the Paint1 Application
 5.4 : Scrolling Windows
 5.5 : Building the Scroll Application
Lesson 6 - Building a Complete Application: ShowString

6.1 : Building an Application That Displays a String

 6.2 : Building the ShowString Menus
 6.3 : Building the ShowString Dialog Boxes
 6.4 : Making the Menu Work
 6.5 : Making the Dialog Box Work
 6.6 : Adding Appearance Options to the Options Dialog Box
Lesson 7 -  Status Bars and Toolbars

7.1 : Working with Toolbars

 7.2 : Working with Status Bars
Lesson 8 - Common Controls

8.1 : The Progress Bar Control

 8.2 : The Up-Down Control
 8.3 : The Image List Control
 8.4 : The List View Control
 8.5 : The Tree View Control
 8.6 : The Rich Edit Control
 8.7 : The Date Picker Control
 8.8 : Month Calendar Control
 8.9 : Scrolling the View
Lesson 9 - Property Pages and Sheets

9.1 : Introducing Property Sheets

 9.2 : Creating the Property Sheet Demo Application
 9.3 : Running the Property Sheet Demo Application
Lesson 10 - ActiveX Concepts

10.1 : The Purpose of ActiveX

10.2 : Object Linking

10.3 : Object Embedding

 10.4 : Containers and Servers
 10.5 : Toward a More Intuitive User Interface
 10.6 : The Component Object Model  

10.7 : Automation

 10.8 : ActiveX Controls

Lesson 11 -  Building an ActiveX Control

11.1 : Creating a Rolling-Die Control

11.2 : Displaying the Current Value

11.3 : Reacting to a Mouse Click and Rolling the Die 

 11.4 : Creating a Better User Interface
 11.5 : Generating Property Sheets
Lesson 12 - Database Access

12.1 : Understanding Database Concepts

12.2 : Creating an ODBC Database Program

 12.3 : Choosing Between ODBC and DAO
 12.4 : OLE DB

Lesson - 5 :Drawing on the Screen

5.3 - Building the Paint1 Application

To build the Paint1 application, you first need to understand how painting and drawing work in an MFC program. Then you can set up the skeleton code to handle user clicks and the three different kinds of display. Finally, you’ll fill in the code for each kind of display in turn.

5.3.1 Painting in an MFC Program

In Unit 3, "Messages and Commands," you learned about message maps and how you can tell MFC which functions to call when it receives messages from Windows. One important message that every Windows program with a window must handle is WM_PAINT. Windows sends the WM_PAINT message to an application’s window when the window needs to be redrawn. Several events cause Windows to send a WM_PAINT message:

· When users simply run the program: In a properly written Windows application, the application’s window receives a WM_PAINT message almost immediately after being run, to ensure that the appropriate data is displayed from the very start.

· When the window has been resized or has recently been uncovered (fully or partially) by another window: Part of the window that wasn’t visible before is now onscreen and must be updated.

· When a program indirectly sends itself a WM_PAINT message by invalidating its client area: This capability ensures that an application can change its window’s contents almost any time it wants. For example, a word processor might invalidate its window after users paste some text from the Clipboard.

When you studied message maps, you learned to convert a message name to a message-map macro and function name. You now know, for example, that the message-map macro for a WM_PAINT message is ON_WM_PAINT(). You also know that the matching message-map function should be called OnPaint(). This is another case where MFC has already done most of the work of matching a Windows message with its message-response function.

You might guess that your next step is to catch the WM_PAINT message or to override the OnPaint() function that your view class inherited from CView, but you won’t do that. Listing 5.1 shows the code for CView::OnPaint(). As you can see, WM_PAINT is already caught and handled for you.

Listing 5.1 CView::OnPaint()

void CView::OnPaint()

{

// standard paint routine

CPaintDC dc(this);

OnPrepareDC(&dc);

OnDraw(&dc);

}

CPaintDC is a special class for managing paint DCs—device contexts used only when responding to WM_PAINT messages. An object of the CPaintDC class does more than just create a DC; it also calls the BeginPaint() Windows API function in the class’s constructor and calls EndPaint() in its destructor. When a program responds to WM_PAINT messages, calls to BeginPaint() and EndPaint() are required. The CPaintDC class handles this requirement without your having to get involved in all the messy details. As you can see, the CPaintDC constructor takes a single argument, which is a pointer to the window for which you’re creating the DC. The this pointer points to the current view, so it’s passed to the constructor to make a DC for the current view.

OnPrepareDC() is a CView function that prepares a DC for use.

OnDraw() does the actual work of visually representing the document. In most cases you will write the OnDraw() code for your application and never touch OnPaint().

5.3.2 Switching the Display

The design for Paint1 states that when you click the application’s window, the window’s display changes. This seemingly magical feat is actually easy to accomplish. You add a member variable to the view to store what kind of display is being done and then change it when users click the window. In other words, the program routes WM_LBUTTONDOWN messages to the OnLButtonDown() message-response function, which sets the m_display flag as appropriate.

First, add the member variable. You must add it by hand rather than through the shortcut menu because the type includes an enum declaration. Open Paint1View.h from the FileView and add these lines after the //Attributes comment:

protected:

enum {Fonts, Pens, Brushes} m_Display;

Choose ClassView in the Project Workspace pane, expand the classes, expand CPaint1View, and then double-click the constructor CPaint1View(). Add this line of code in place of the TODO comment:

m_Display = Fonts;

This initializes the display selector to the font demonstration. You use the display selector in the OnDraw() function called by CView::OnPaint(). AppWizard has created CPaint1View::OnDraw(), but it doesn’t do anything at the moment. Double-click the function name in ClassView and add the code in Listing 5.2 to the function, removing the TODO comment left by AppWizard.

Listing 5.2 CPaint1View::OnDraw()

void CPaint1View::OnDraw(CDC* pDC)

{

CPaint1Doc* pDoc = GetDocument();

ASSERT_VALID(pDoc);

switch (m_Display)

{

case Fonts:

ShowFonts(pDC);

break;

case Pens:

ShowPens(pDC);

break;

case Brushes:

ShowBrushes(pDC);

break;

}

}

You will write the three functions ShowFonts(), ShowPens(), and ShowBrushes() in upcoming sections of this Unit. Each function uses the same DC pointer that was passed to OnDraw() by OnPaint(). Add them to the class now by following these steps:

1. Right-click the CPaint1View class in ClassView and select Add Member Function.

2. Enter void for the Function Type.

3. Enter ShowFonts(CDC* pDC) for the Function Declaration.

4. Change the access to protected. Click OK.

5. Repeat steps 1 through 4 for ShowPens(CDC* pDC) and ShowBrushes(CDC* pDC).

The last step in arranging for the display to switch is to catch left mouse clicks and write code in the message handler to change m_display.

Right-click CPaint1View in the ClassView and select Add Windows Message Handler from the shortcut menu that appears. Double-click WM_LBUTTONDOWN in the New Windows Messages/Events list box. ClassWizard adds a function called OnLButtonDown() to the view and adds entries to the message map so that this function will be called whenever users click the left mouse button over this view.

Click Edit Existing to edit the OnLButtonDown() you just created, and add the code shown in Listing 5.3.

Listing 5.3 CPaint1View::OnLButtonDown()

void CPaint1View::OnLButtonDown(UINT nFlags, CPoint point)

{

if (m_Display == Fonts)

m_Display = Pens;

else if (m_Display == Pens)

m_Display = Brushes;

else

m_Display = Fonts

Invalidate();

CView::OnLButtonDown(nFlags, point);

}

As you can see, depending on its current value, m_display is set to the next display type in the series. Of course, just changing the value of m_display doesn’t accomplish much; the program still needs to redraw the contents of its window. The call to Invalidate() tells Windows that all of the window needs to be repainted. This causes Windows to generate a WM_PAINT message for the window, which means that eventually OnDraw() will be called and the view will be redrawn as a font, pen, or brush demonstration.

5.3.3 Using Fonts

Changing the font used in a view is a technique you’ll want to use in various situations. It’s not as simple as you might think because you can never be sure that any given font is actually installed on the user’s machine. You set up a structure that holds information about the font you want, attempt to create it, and then work with the font you actually have, which might not be the font you asked for.

A Windows font is described in the LOGFONT structure outlined in Table 5.1. The LOGFONT structure uses 14 fields to hold a complete description of the font. Many fields can be set to 0 or the default values, depending on the program’s needs.

Table 5.1 LOGFONT Fields and Their Descriptions

Field

Description

lfHeight Font height in logical units
lfWidth Font width in logical units
lfEscapement Angle at which to draw the text
lfOrientation Character tilt in tenths of a degree
lfWeight Font weight
lfItalic A nonzero value indicates italics
lfUnderline A nonzero value indicates an underlined font
lfStrikeOut A nonzero value indicates a strikethrough font
lfCharSet Font character set
lfOutPrecision How to match requested font to actual font
lfClipPrecision How to clip characters that run over clip area
lfQuality Print quality of the font
lfPitchAndFamily Pitch and font family
lfFaceName Typeface name

 Some terms in Table 5.1 need a little explanation. The first is logical units. How high is a font with a height of 8 logical units, for example? The meaning of a logical unit depends on the mapping mode you’re using, as shown in Table 5.2. The default mapping mode is MM_TEXT, which means that one logical unit is equal to 1 pixel.

Table 5.2 Mapping Modes

Mode Unit
MM_HIENGLISH 0.001 inch
MM_HIMETRIC

0.01 millimeter

MM_ISOTROPIC

Arbitrary

MM_LOENGLISH  0.01 inch
MM_LOMETRIC  0.1 millimeter
MM_TEXT Device pixel
MM_TWIPS  1/1440 inch

Escapement refers to writing text along an angled line. Orientation refers to writing angled text along a flat line. The font weight refers to the thickness of the letters. A number of constants have been defined for use in this field: FW_DONTCARE, FW_THIN, FW_EXTRALIGHT, FW_ULTRALIGHT, FW_LIGHT, FW_NORMAL, FW_REGULAR, FW_MEDIUM, FW_SEMIBOLD, FW_DEMIBOLD, FW_BOLD, FW_EXTRABOLD, FW_ULTRABOLD, FW_BLACK, and FW_HEAVY. Not all fonts are available in all weights. Four character sets are available (ANSI_CHARSET, OEM_CHARSET, SYMBOL_CHARSET, and UNICODE_CHARSET), but for writing English text you’ll almost always use ANSI_CHARSET. The last field in the LOGFONT structure is the face name, such as Courier or Helvetica.

Listing 5.4 shows the code you need to add to the empty ShowFonts() function you created earlier.

Listing 5.4 CPaint1View::ShowFonts()

void CPaint1View::ShowFonts(CDC * pDC)

{

// Initialize a LOGFONT structure for the fonts.

LOGFONT logFont;

logFont.lfHeight = 8;

logFont.lfWidth = 0;

logFont.lfEscapement = 0;

logFont.lfOrientation = 0;

logFont.lfWeight = FW_NORMAL;

logFont.lfItalic = 0;

logFont.lfUnderline = 0;

logFont.lfStrikeOut = 0;

logFont.lfCharSet = ANSI_CHARSET;

logFont.lfOutPrecision = OUT_DEFAULT_PRECIS;

logFont.lfClipPrecision = CLIP_DEFAULT_PRECIS;

logFont.lfQuality = PROOF_QUALITY;

logFont.lfPitchAndFamily = VARIABLE_PITCH | FF_ROMAN;

strcpy(logFont.lfFaceName, "Times New Roman");

// Initialize the position of text in the window.

UINT position = 0;

// Create and display eight example fonts.

for (UINT x=0; x<8; ++x)

{

// Set the new font’s height.

logFont.lfHeight = 16 + (x * 8);

// Create a new font and select it into the DC.

CFont font;

font.CreateFontIndirect(&logFont);

CFont* oldFont = pDC->SelectObject(&font);

// Print text with the new font.

position += logFont.lfHeight;

pDC->TextOut(20, position, "A sample font.");

// Restore the old font to the DC.

pDC->SelectObject(oldFont);

}

}

ShowFonts()starts by setting up a Times Roman font 8 pixels high, with a width that best matches the height and all other attributes set to normal defaults.

To show the many fonts displayed in its window, the Paint1 application creates its fonts in a for loop, modifying the value of the LOGFONT structure’s lfHeight member each time through the loop, using the loop variable x to calculate the new font height:

logFont.lfHeight = 16 + (x * 8);

Because x starts at 0, the first font created in the loop will be 16 pixels high. Each time through the loop, the new font will be 8 pixels higher than the previous one.

After setting the font’s height, the program creates a CFont object and calls its CreateFontIndirect() function, which attempts to create a CFont object corresponding to the LOGFONT you created. It will change the LOGFONT to describe the CFont that was actually created, given the fonts installed on the user’s machine.

After ShowFonts() calls CreateFontIndirect(), the CFont object is associated with a Windows font. Now you can select it into the DC. Selecting objects into device contexts is a crucial concept in Windows output programming. You can’t use any graphical object, such as a font, directly; instead, you select it into the DC and then use the DC. You always save a pointer to the old object that was in the DC (the pointer is returned from the SelectObject() call) and use it to restore the device context by selecting the old object again when you’re finished. The same function, SelectObject(), is used to select various objects into a device context: the font you’re using in this section, a pen, a brush, or a number of other drawing objects.

After selecting the new font into the DC, you can use the font to draw text onscreen. The local variable position holds the vertical position in the window at which the next line of text should be printed. This position depends on the height of the current font. After all, if there’s not enough space between the lines, the larger fonts will overlap the smaller ones. When Windows created the new font, it stored the font’s height (most likely the height that you requested, but maybe not) in the LOGFONT structure’s lfHeight member. By adding the value stored in lfHeight, the program can determine the next position at which to display the line of text. To make the text appear onscreen, ShowFonts() calls TextOut().

TextOut()’s first two arguments are the X and Y coordinates at which to print the text. The third argument is the text to print. Having printed the text, you restore the old font to the DC in case this is the last time through the loop.

Build the application and run it. It should resemble Figure 5.3. If you click the window, it will go blank because the ShowPens() routine doesn’t draw anything. Click again and it’s still blank, this time because the ShowBrushes() routine doesn’t draw anything. Click a third time and you are back to the fonts screen.


FIG. 5.3
The font display shows different types of text output.

5.3.4 Sizing and Positioning the Window

As you can see in Figure 5.3, Paint1 doesn’t display eight different fonts at 800*600 screen settings—only seven can fit in the window. To correct this, you need to set the size of the window a little larger than the Windows default. In an MFC program, you do this in the mainframe class PreCreateWindow() function. This is called for you just before the mainframe window is created. The mainframe window surrounds the entire application and governs the size of the view.

PreCreateWindow() takes one parameter, a reference to a CREATESTRUCT structure. The CREATESTRUCT structure contains essential information about the window that’s about to be created, as shown in Listing 5.5.

Listing 5.5 The CREATESTRUCT Structure

typedef struct tagCREATESTRUCT {

LPVOID lpCreateParams;

HANDLE hInstance;

HMENU hMenu;

HWND hwndParent;

int cy;

int cx;

int y;

int x;

LONG style;

LPCSTR lpszName;

LPCSTR lpszClass;

DWORD dwExStyle;

} CREATESTRUCT;

If you’ve programmed Windows without application frameworks such as MFC, you’ll recognize the information stored in the CREATESTRUCT structure. You used to supply much of this information when calling the Windows API function CreateWindow() to create your application’s window. Of special interest to MFC programmers are the cx, cy, x, and y members of this structure. By changing cx and cy, you can set the window width and height, respectively. Similarly, modifying x and y changes the window’s position. By overriding PreCreateWindow(), you have a chance to fiddle with the CREATESTRUCT structure before Windows uses it to create the window.

AppWizard created a CMainFrame::PreCreateWindow() function. Expand CMainFrame in ClassView, double-click PreCreateWindow() to edit it, and add lines to obtain the code shown in Listing 5.6. This sets the application’s height and width. It also prevents users from resizing the application by using the bitwise and operator (&) to turn off the WS_SIZEBOX style bit.

Listing 5.6 CMainFrame::PreCreateWindow()

BOOL CMainFrame::PreCreateWindow(CREATESTRUCT& cs)

{

cs.cx = 440;

cs.cy = 480;

cs.style &= ~WS_SIZEBOX;

if( !CFrameWnd::PreCreateWindow(cs) )

return FALSE;

return TRUE;

}

It’s important that after your own code in PreCreateWindow(), you call the base class’s PreCreateWindow(). Failure to do this will leave you without a valid window because MFC never gets a chance to pass the CREATESTRUCT structure on to Windows, so Windows never creates your window. When overriding class member functions, you usually need to call the base class’s version.

Build and run Paint1 to confirm that all eight fonts fit in the application’s window. Now you’re ready to demonstrate pens.

5.3.5 Using Pens

You’ll be pleased to know that pens are much easier to deal with than fonts, mostly because you don’t have to fool around with complicated data structures like LOGFONT. In fact, to create a pen, you need to supply only the pen’s line style, thickness, and color. The Paint1 application’s ShowPens() function displays in its window the lines drawn by using different pens created within a for loop. Listing 5.7 shows the code.

Listing 5.7 CPaint1View::ShowPens()

void CPaint1View::ShowPens(CDC * pDC)

{

// Initialize the line position.

UINT position = 10;

// Draw sixteen lines in the window.

for (UINT x=0; x<16; ++x)

{

// Create a new pen and select it into the DC.

CPen pen(PS_SOLID, x*2+1, RGB(0, 0, 255));

CPen* oldPen = pDC->SelectObject(&pen);

// Draw a line with the new pen.

position += x * 2 + 10;

pDC->MoveTo(20, position);

pDC->LineTo(400, position);

// Restore the old pen to the DC.

pDC->SelectObject(oldPen);

}

}

Within the loop, ShowPens() first creates a custom pen. The constructor takes three parameters. The first is the line’s style, one of the styles listed in Table 5.3. (You can draw only solid lines with different thicknesses. If you specify a pattern and a thickness greater than 1 pixel, the pattern is ignored and a solid line is drawn.) The second argument is the line thickness, which increases each time through the loop. The third argument is the line’s color. The RGB macro takes three values for the red, green, and blue color components and converts them to a valid Windows color reference. The values for the red, green, and blue color components can be anything from 0 to 255—the higher the value, the brighter that color component. This code creates a bright blue pen. If all the color values were 0, the pen would be black; if the color values were all 255, the pen would be white.

Table 5.3 Pen Styles

Style Description
PS_DASH A pen that draws dashed lines
PS_DASHDOT A pen that draws dash-dot patterned lines
PS_DASHDOTDOT A pen that draws dash-dot-dot patterned lines
PS_DOT A pen that draws dotted lines
PS_INSIDEFRAME A pen that’s used with shapes, in which the line’s thickness must not extend outside the shape’s frame
PS_NULL A pen that draws invisible lines
PS_SOLID A pen that draws solid lines

After creating the new pen, ShowPens() selects it into the DC, saving the pointer to the old pen. The MoveTo() function moves the pen to an X,Y coordinate without drawing as it moves; the LineTo() function moves the pen while drawing. The style, thickness, and color of the pen are used. Finally, you select the old pen into the DC.

TIP:[ There are a number of line drawing functions other than LineTo(), including Arc(), ArcTo(), AngleArc(), and PolyDraw().

Build and run Paint1 again. When the font display appears, click the window. You will see a pen display similar to the one in Figure 5.4.

5.3.6 Using Brushes

A pen draws a line of a specified thickness onscreen. A brush fills a shape onscreen. You can create solid and patterned brushes and even brushes from bitmaps that contain your own custom fill patterns. Paint1 will display both patterned and solid rectangles in the ShowBrushes() function, shown in Listing 5.8.


FIG. 5.4 The pen display shows the effect of setting line thickness.

Listing 5.8 CPaint1View::ShowBrushes()

void CPaint1View::ShowBrushes(CDC * pDC)

// Initialize the rectangle position.

UINT position = 0;

// Select pen to use for rectangle borders

CPen pen(PS_SOLID, 5, RGB(255, 0, 0));

CPen* oldPen = pDC->SelectObject(&pen);

// Draw seven rectangles.

for (UINT x=0; x<7; ++x)

{

CBrush* brush;

// Create a solid or hatched brush.

if (x == 6)

brush = new CBrush(RGB(0,255,0));

else

brush = new CBrush(x, RGB(0,160,0));

// Select the new brush into the DC.

CBrush* oldBrush = pDC->SelectObject(brush);

// Draw the rectangle.

position += 50;

pDC->Rectangle(20, position, 400, position + 40);

// Restore the DC and delete the brush.

pDC->SelectObject(oldBrush);

delete brush;

}

// Restore the old pen to the DC.

pDC->SelectObject(oldPen);

}

The rectangles painted with the various brushes in this routine will all be drawn with a border. To arrange this, create a pen (this one is solid, 5 pixels thick, and bright red) and select it into the DC. It will be used to border the rectangles without any further work on your part. Like ShowFonts() and ShowPens(), this routine creates its graphical objects within a for loop. Unlike those two functions, ShowBrushes() creates a graphical object (in this routine, a brush) with a call to new. This enables you to call the one-argument constructor, which creates a solid brush, or the two-argument constructor, which creates a hatched brush.

In Listing 5.8, the first argument to the two-argument constructor is just the loop variable, x. Usually, you don’t want to show all the hatch patterns but want to select a specific one. Use one of these constants for the hatch style:

· HS_HORIZONTAL—Horizontal

· HS_VERTICAL—Vertical

· HS_CROSS—Horizontal and vertical

· HS_FDIAGONAL—Forward diagonal

· HS_BDIAGONAL—Backward diagonal

· HS_DIAGCROSS—Diagonal in both directions

In a pattern that should be familiar by now, ShowBrushes() selects the brush into the DC, determines the position at which to work, uses the brush by calling Rectangle(), and then restores the old brush. When the loop is complete, the old pen is restored as well.

Rectangle() is just one of the shape-drawing functions that you can call. Rectangle() takes as arguments the coordinates of the rectangle’s upper-left and lower-right corners. Some others of interest are Chord(), DrawFocusRect(), Ellipse(), Pie(), Polygon(), PolyPolygon(), Polyline(), and RoundRect(), which draws a rectangle with rounded corners.

Again, build and run Paint1. Click twice, and you will see the demonstration of brushes, as shown in Figure 5.5.

NOTE: Remember the call to Invalidate() in CPaint1View::OnLButtonDown()? Invalidate() actually takes a Boolean argument with a default value of TRUE. This argument tells Windows whether to erase the window’s background. If you use FALSE for this argument, the background isn’t erased. In Figure 5.6, you can see what happens to the Paint1 application if Invalidate() is called with an argument of FALSE.


FIG. 5.5 The brushes display shows several patterns inside thick-bordered rectangles.

Next>>
 
© Dewsoft Overseas