Teach Yourself Visual C++ 6 in 21 Days

Previous chapterNext chapterContents


- B -
Answers



This appendix provides the answers to the quiz questions and exercises at the end of each chapter.

Day 1

Quiz

1. How do you change the caption on a button?

In the window layout editor, select the button to be changed. Right-click the mouse and select Properties from the pop-up menu. Change the value in the Caption field.

2. What can you do with the Visual C++ AppWizard?

You can use it to build a shell for your application, based on the type of application and the functionality needs of the application. The shell will have support for the desired functionality already built in.

3. How do you attach functionality to the click of a button?

By using the Class Wizard, you can create a function and attach it to an object for handling a specific Windows message. The Class Wizard creates the function and can take you right to the spot in the function's code where you need to begin adding your own code.

Exercise

Add a second button to the About window in your application. Have the button display a different message from the one on the first window.

1. In the workspace pane, select the Resource View tab.

2. Expand the dialog tree branch and double-click the IDD_ABOUTBOX dialog, bringing it into the Developer Studio editor.

3. Click the button control on the toolbar.

4. Click and drag the mouse on the window where you want the button to be placed.

5. Open the properties dialog for the new button, changing the ID and caption to describe the message to be displayed by the button. Close the properties dialog.

6. Open the Class Wizard and add a new function for the clicked message for your new button.

7. Click the Edit Code button in the Class Wizard to take you to the spot in your code where your new function is.

8. Add the MessageBox function to display a message to the user.

9. Compile and run your application to test your new button.

Day 2

Quiz

1. Why do you need to specify the tab order of the controls on your application windows?

By specifying the tab order of the controls on your application windows, you can control the order in which the user navigates the application window. If the user is using the keyboard to navigate around the application window, then the two primary means of navigating between controls are the tab key and mnemonics that jump directly to specific controls. The tab order helps provide the user with a consistent and predictable experience when using your application.

2. How can you include a mnemonic in a static text field that will take the user to the edit box or combo box beside the text control?

If you place a mnemonic in a static text control and then make sure that the static text control is just before the edit control associated with the static text, the user can select the mnemonic in the static text control to jump directly to the edit box control.

3. Why do you need to give unique object IDs to the static text fields in front of the edit box and combo boxes?

The unique object IDs on the two static text controls were necessary because you need to manipulate those two controls with the check boxes that enable or disable and show or hide sets of controls.

4. Why do you need to call the UpdateData function before checking the value of one of the controls?

If the user has changed the value of the control on the screen, the UpdateData function must be called, passing it TRUE as the function argument, to copy the values from the controls on the window to the variables that are associated with those controls. If UpdateData is not called, then the values of the variables may not correctly reflect what the user has changed on the screen.

Exercises

1. Add code to the Default Message button to reset the edit box to say Enter a message here.

Using the Class Wizard, add a function to the Default Message button's clicked event. In this function, add the code in Listing B.1.

LISTING B.1. DAY2DLG.CPP--THE CODE TO PLACE A DEFAULT MESSAGE IN THE EDIT BOX.

 1: void CDay2Dlg::OnDfltmsg()
 2: {
 3:     // TODO: Add your control notification handler code here
 4: 
 5:     ///////////////////////
 6:     // MY CODE STARTS HERE
 7:     ///////////////////////
 8: 
 9:     // Set the message to a default message
10:     m_strMessage = "Enter a message here";
11: 
12:     // Update the screen
13:     UpdateData(FALSE);
14: 
15:     ///////////////////////
16:     // MY CODE ENDS HERE
17:     ///////////////////////
18: }
2. Add code to enable or disable and show or hide the controls used to select and run another application.

Add functions to the Enable and Show Program Action check boxes. In these functions, add the code in Listing B.2.

LISTING B.2. DAY2DLG.CPP--THE CODE TO ENABLE OR DISABLE AND SHOW OR HIDE THE RUN PROGRAM CONTROLS.

 1: void CDay2Dlg::OnCkenblpgm()
 2: {
 3:     // TODO: Add your control notification handler code here
 4: 
 5:     ///////////////////////
 6:     // MY CODE STARTS HERE
 7:     ///////////////////////
 8: 
 9:     // Get the current values from the screen
10:     UpdateData(TRUE);
11: 
12:     // Is the Enable Program Action check box checked?
13:     if (m_bEnablePgm == TRUE)
14:     {
15:         // Yes, so enable all controls that have anything
16:         // to do with running a program
17:         GetDlgItem(IDC_PROGTORUN)->EnableWindow(TRUE);
18:         GetDlgItem(IDC_RUNPGM)->EnableWindow(TRUE);
19:         GetDlgItem(IDC_STATICPGM)->EnableWindow(TRUE);
20:     }
21:     else
22:     {
23:         // No, so disable all controls that have anything
24:         // to do with running a program
25:         GetDlgItem(IDC_PROGTORUN)->EnableWindow(FALSE);
26:         GetDlgItem(IDC_RUNPGM)->EnableWindow(FALSE);
27:         GetDlgItem(IDC_STATICPGM)->EnableWindow(FALSE);
28:     }
29: 
30:     ///////////////////////
31:     // MY CODE ENDS HERE
32:     ///////////////////////
33: }
34: 
35: void CDay2Dlg::OnCkshwpgm()
36: {
37:     // TODO: Add your control notification handler code here
38: 
39:     ///////////////////////
40:     // MY CODE STARTS HERE
41:     ///////////////////////
42: 
43:     // Get the current values from the screen
44:     UpdateData(TRUE);
45: 
46:     // Is the Show Program Action check box checked?
47:     if (m_bShowPgm == TRUE)
48:     {
49:         // Yes, so show all controls that have anything
50:         // to do with running a program
51:         GetDlgItem(IDC_PROGTORUN)->ShowWindow(TRUE);
52:         GetDlgItem(IDC_RUNPGM)->ShowWindow(TRUE);
53:         GetDlgItem(IDC_STATICPGM)->ShowWindow(TRUE);
54:     }
55:     else
56:     {
57:         // No, so hide all controls that have anything
58:         // to do with running a program
59:         GetDlgItem(IDC_PROGTORUN)->ShowWindow(FALSE);
60:         GetDlgItem(IDC_RUNPGM)->ShowWindow(FALSE);
61:         GetDlgItem(IDC_STATICPGM)->ShowWindow(FALSE);
62:     }
63: 
64:     ///////////////////////
65:     // MY CODE ENDS HERE
66:     ///////////////////////
67: }
3. Extend the code in the OnRunpgm function to allow the user to enter his own program name to be run.

Modify the OnRunpgm function as in Listing B.3.

LISTING B.3. DAY2DLG.CPP--THE CODE TO RUN ANY PROGRAM NAME TYPED INTO THE RUN PROGRAM COMBO BOX.

 1: void CDay2Dlg::OnRunpgm()
 2: {
 3:     // TODO: Add your control notification handler code here
 4: 
 5:     ///////////////////////
 6:     // MY CODE STARTS HERE
 7:     ///////////////////////
 8: 
 9:     // Get the current values from the screen
10:     UpdateData(TRUE);
11: 
12:     // Declare a local variable for holding the program name
13:     CString strPgmName;
14: 
15:     // Copy the program name to the local variable
16:     strPgmName = m_strProgToRun;
17: 
18:     // Make the program name all uppercase
19:     strPgmName.MakeUpper();
20: 
21:     // Did the user select to run the Paint program?
22:     if (strPgmName == "PAINT")
23:         // Yes, run the Paint program
24:         WinExec("pbrush.exe", SW_SHOW);
25: 
26:     // Did the user select to run the Notepad program?
27:     if (strPgmName == "NOTEPAD")
28:         // Yes, run the Notepad program
29:         WinExec("notepad.exe", SW_SHOW);
30: 
31:     // Did the user select to run the Solitaire program?
32:     if (strPgmName == "SOLITAIRE")
33:         // Yes, run the Solitaire program
34:         WinExec("sol.exe", SW_SHOW);
35: 
36:         // Run any other program name typed into the combo box
37:         if ((strPgmName != "PAINT") && (strPgmName != "NOTEPAD") &&
38:             (strPgmName != "SOLITAIRE"))
39:               // Yes, run the program typed into the combo box
40:               WinExec(strPgmName, SW_SHOW);
41: 
42:     ///////////////////////
43:     // MY CODE ENDS HERE
44:     ///////////////////////

45: }

Day 3

Quiz

1. What are the possible mouse messages that you can add functions for?

WM_LBUTTONDOWN, WM_LBUTTONUP, WM_LBUTTONDBLCLK, WM_RBUTTONDOWN, WM _RBUTTONUP, WM_RBUTTONDBLCLK, WM_MOUSEMOVE, and WM_MOUSEWHEEL.

2. How can you tell if the left mouse button is down on the WM_MOUSEMOVE event message?

You can mask the flags passed to the OnMouseMove function with the MK_LBUTTON flag, as follows:

((nFlags & MK_LBUTTON) == MK_LBUTTON)
3. How can you prevent the cursor from changing back to the default cursor after you set it to a different one?

Return TRUE in the OnSetCursor event function, preventing the ancestor OnSetCursor function from being called.

Exercises

1. Modify your drawing program so that the left mouse button can draw in red and the right mouse button can draw in blue.

Add a function for the WM_RBUTTONDOWN event message and write the code for it as in Listing B.4.

LISTING B.4. MOUSEDLG.CPP--THE OnRButtonDown FUNCTION.

 1: void CMouseDlg::OnRButtonDown(UINT nFlags, CPoint point)
 2: {
 3:     // TODO: Add your message handler code here and/or call default
 4:
 5:     ///////////////////////
 6:     // MY CODE STARTS HERE
 7:     ///////////////////////
 8:
 9:         // Set the current point as the starting point
10:     m_iPrevX = point.x;
11:     m_iPrevY = point.y;
12:
13:     ///////////////////////
14:     // MY CODE ENDS HERE
15:     ///////////////////////
16:
17:     CDialog::OnRButtonDown(nFlags, point);
18: }
Extend the OnMouseMove function as in Listing B.5.

LISTING B.5. MOUSEDLG.CPP--THE MODIFIED OnMouseMove FUNCTION.

 1: void CMouseDlg::OnMouseMove(UINT nFlags, CPoint point)
 2: {
 3:     // TODO: Add your message handler code here and/or call default
 4:
 5:     ///////////////////////
 6:     // MY CODE STARTS HERE
 7:     ///////////////////////
 8:
 9:         // Check to see if the left mouse button is down
10:     if ((nFlags & MK_LBUTTON) == MK_LBUTTON)
11:     {
12:         // Get the Device Context
13:         CClientDC dc(this);
14:
15:         // Create a new pen
16:         CPen lpen(PS_SOLID, 16, RGB(255, 0, 0));
17:
18:         // Use the new pen
19:         dc.SelectObject(&lpen);
20:
21:         // Draw a line from the previous point to the current point
22:         dc.MoveTo(m_iPrevX, m_iPrevY);
23:         dc.LineTo(point.x, point.y);
24: 
25:         // Save the current point as the previous point
26:         m_iPrevX = point.x;
27:         m_iPrevY = point.y;
28:     }
29:
30:     // Check to see if the right mouse button is down
31:     if ((nFlags & MK_RBUTTON) == MK_RBUTTON)
32:     {
33:         // Get the Device Context
34:         CClientDC rdc(this);
35:
36:         // Create a new pen
37:         CPen rpen(PS_SOLID, 16, RGB(0, 0, 255));
38: 
39:         // Use the new pen
40:         rdc.SelectObject(&rpen);
41: 
42:         // Draw a line from the previous point to the current point
43:         rdc.MoveTo(m_iPrevX, m_iPrevY);
44:         rdc.LineTo(point.x, point.y);
45: 
46:         // Save the current point as the previous point
47:         m_iPrevX = point.x;
48:         m_iPrevY = point.y;
49:     }
50:
51:     ///////////////////////
52:     // MY CODE ENDS HERE
53:     ///////////////////////
54:
55:     CDialog::OnMouseMove(nFlags, point);
56: }
2. Extend the OnKeyDown function to add some of the following standard cursors:

Your modified OnKeyDown function can look something like the following:

void CMouseDlg::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
{
    // TODO: Add your message handler code here and/or call default
    ///////////////////////
    // MY CODE STARTS HERE
    ///////////////////////
    char lsChar;        // The current character being pressed
    HCURSOR lhCursor;    // The handle to the cursor to be displayed
    // Convert the key pressed to a character
    lsChar = char(nChar);
    // Is the character "A"
    if (lsChar == `A')
    {
        // Load the arrow cursor
        lhCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
        // Set the cursor flag
        m_bCursor = TRUE;
        // Set the screen cursor
        SetCursor(lhCursor);
    }
    // Is the character "B"
    if (lsChar == `B')
    {
        // Load the I beam cursor
        lhCursor = AfxGetApp()->LoadStandardCursor(IDC_IBEAM);
        // Set the cursor flag
        m_bCursor = TRUE;
        // Set the screen cursor
        SetCursor(lhCursor);
    }
    // Is the character "C"
    if (lsChar == `C')
    {
        // Load the hourglass cursor
        lhCursor = AfxGetApp()->LoadStandardCursor(IDC_WAIT);
        // Set the cursor flag
        m_bCursor = TRUE;
        // Set the screen cursor
        SetCursor(lhCursor);
    }
    // Is the character "D"
    if (lsChar == `D')
    {
        // Load the cross hair cursor
        lhCursor = AfxGetApp()->LoadStandardCursor(IDC_CROSS);
        // Set the cursor flag
        m_bCursor = TRUE;
        // Set the screen cursor
        SetCursor(lhCursor);
    }
    // Is the character "E"
    if (lsChar == `E')
    {
        // Load the up arrow cursor
        lhCursor = AfxGetApp()->LoadStandardCursor(IDC_UPARROW);
        // Set the cursor flag
        m_bCursor = TRUE;
        // Set the screen cursor
        SetCursor(lhCursor);
    }
    // Is the character "F"
    if (lsChar == `F')
    {
        // Load the size cursor
        lhCursor = AfxGetApp()->LoadStandardCursor(IDC_SIZEALL);
        // Set the cursor flag
        m_bCursor = TRUE;
        // Set the screen cursor
        SetCursor(lhCursor);
    }
    // Is the character "G"
    if (lsChar == `G')
    {
        // Load the up/right-down/left size cursor
        lhCursor = AfxGetApp()->LoadStandardCursor(IDC_SIZENWSE);
        // Set the cursor flag
        m_bCursor = TRUE;
        // Set the screen cursor
        SetCursor(lhCursor);
    }
    // Is the character "H"
    if (lsChar == `H')
    {
        // Load the up/left-down/right size cursor
        lhCursor = AfxGetApp()->LoadStandardCursor(IDC_SIZENESW);
        // Set the cursor flag
        m_bCursor = TRUE;
        // Set the screen cursor
        SetCursor(lhCursor);
    }
    // Is the character "I"
    if (lsChar == `I')
    {
        // Load the left-right size cursor
        lhCursor = AfxGetApp()->LoadStandardCursor(IDC_SIZEWE);
        // Set the cursor flag
        m_bCursor = TRUE;
        // Set the screen cursor
        SetCursor(lhCursor);
    }
    // Is the character "J"
    if (lsChar == `J')
    {
        // Load the up-down size cursor
        lhCursor = AfxGetApp()->LoadStandardCursor(IDC_SIZENS);
        // Set the cursor flag
        m_bCursor = TRUE;
        // Set the screen cursor
        SetCursor(lhCursor);
    }
    if (lsChar == `K')
    {
        // Load the no cursor
        lhCursor = AfxGetApp()->LoadStandardCursor(IDC_NO);
        // Set the cursor flag
        m_bCursor = TRUE;
        // Set the screen cursor
        SetCursor(lhCursor);
    }
    if (lsChar == `L')
    {
        // Load the app starting cursor
        lhCursor = AfxGetApp()->LoadStandardCursor(IDC_APPSTARTING);
        // Set the cursor flag
        m_bCursor = TRUE;
        // Set the screen cursor
        SetCursor(lhCursor);
    }
    if (lsChar == `M')
    {
        // Load the help cursor
        lhCursor = AfxGetApp()->LoadStandardCursor(IDC_HELP);
        // Set the cursor flag
        m_bCursor = TRUE;
        // Set the screen cursor
        SetCursor(lhCursor);
    }
    // Is the character "X"
    if (lsChar == `X')
    {
        // Load the arrow cursor
        lhCursor = AfxGetApp()->LoadStandardCursor(IDC_ARROW);
        // Set the cursor flag
        m_bCursor = TRUE;
        // Set the screen cursor
        SetCursor(lhCursor);
        // Exit the application
        OnOK();
    }
    ///////////////////////
    // MY CODE ENDS HERE
    ///////////////////////
    CDialog::OnKeyDown(nChar, nRepCnt, nFlags);
}

Day 4

Quiz

1. What did you accomplish by adding the two timer IDs to the resource symbols?

You defined the two IDs so that they were available as constants throughout the application.

2. What is another way to add these two IDs to the application?

Add them as #define constants in the class header file (Day2Dlg.h), as follows:

/////////////////////////////////////////////////////////////////////// CTimersDlg dialog
#define ID_CLOCK_TIMER 1
#define ID_COUNT_TIMER 2
class CTimersDlg : public CDialog
{
.
.
.
3. How can you tell two timers apart in the OnTimer function?

You use the timer ID to determine which timer triggered the event.

4. How many timer events does your application receive if the timer is set for one second and your application has been busy for one minute, preventing it from receiving any timer event messages?

One.

Exercise

Update your application so that when the counter timer is started, the clock timer is reset to run at the same interval as the counter timer. When the counter timer is stopped, return the clock timer to a one-second interval.

To change the interval at which a timer is running, you need to first stop the timer and then restart it, as in Listing B.6.

LISTING B.6. THE REVISED OnStarttime AND OnStoptimer FUNCTIONS.

 1: void CTimersDlg::OnStarttime()
 2: {
 3:     // TODO: Add your control notification handler code here
 4: 
 5:     ///////////////////////
 6:     // MY CODE STARTS HERE
 7:     ///////////////////////
 8: 
 9:     // Update the variables
10:     UpdateData(TRUE);
11: 
12:     // Initialize the count
13:     m_iCount = 0;
14:     // Format the count for displaying
15:     m_sCount.Format("%d", m_iCount);
16: 
17:     // Update the dialog
18:     UpdateData(FALSE);
19:     // Start the timer
20:     SetTimer(ID_COUNT_TIMER, m_iInterval, NULL);
21: 
22:     // Stop the clock timer
23:     KillTimer(ID_CLOCK_TIMER);
24:     // Restart the clock timer with the counter interval
25:     SetTimer(ID_CLOCK_TIMER, m_iInterval, NULL);
26: 
27:     // Enable the Stop Timer button
28:     m_cStopTime.EnableWindow(TRUE);
29:     // Disable the Start Timer button
30:     m_cStartTime.EnableWindow(FALSE);
31: 
32:     ///////////////////////
33:     // MY CODE ENDS HERE
34:     ///////////////////////
35: }
36: 
37: void CTimersDlg::OnStoptimer()
38: {
39:     // TODO: Add your control notification handler code here
40: 
41:     ///////////////////////
42:     // MY CODE STARTS HERE
43:     ///////////////////////
44: 
45:     // Stop the timer
46:     KillTimer(ID_COUNT_TIMER);
47: 
48:     // Stop the clock timer
49:     KillTimer(ID_CLOCK_TIMER);
50:     // Restart the clock timer with 1 second interval
51:     SetTimer(ID_CLOCK_TIMER, 1000, NULL);
52: 
53:     // Disable the Stop Timer button
54:     m_cStopTime.EnableWindow(FALSE);
55:     // Enable the Start Timer button
56:     m_cStartTime.EnableWindow(TRUE);
57: 
58:     ///////////////////////
59:     // MY CODE ENDS HERE
60:     ///////////////////////
61: }

Day 5

Quiz

1. What are the possible return codes that your application might receive from the MessageBox function call when you specify the MB_RETRYCANCEL button combination?

IDRETRY and IDCANCEL.

2. What are the common dialogs that are built into the Windows operating systems that are defined as MFC classes?

The common Windows dialogs that are defined as MFC classes are

3. What is the difference between a modal dialog and a modeless dialog?

A modal dialog stops all application processing until the user responds to the dialog. A modeless dialog allows the user to continue working with the rest of the application while the dialog is open for use.

4. How can you display a File Save dialog for the user instead of the File Open dialog that you did have in your application?
In the class instance variable declaration, pass FALSE instead of TRUE. This makes the variable declaration look like this:

CFileDialog m_ldFile(FALSE);
5. Why did you not need to create any functions and add any code to your custom dialog?

The only functionality that was needed on the custom dialog was calling UpdateData before closing the dialog. Because the OK and Cancel buttons were never deleted from the dialog, the OK button automatically performed this functionality.

Exercises

1. Modify your application so that it includes the directory with the filename in the application. (Hint: The GetFileName function returns the path and filename that was selected in the File Open dialog.)

Modify the OnFileopen function as follows:

void CDialogsDlg::OnFileopen() 
{
    // TODO: Add your control notification handler code here
    ///////////////////////
    // MY CODE STARTS HERE
    ///////////////////////
    CFileDialog m_ldFile(TRUE);
    // Show the File open dialog and capture the result
    if (m_ldFile.DoModal() == IDOK)
    {
        // Get the filename selected
        m_sResults = m_ldFile.GetPathName();
        // Update the dialog
        UpdateData(FALSE);
    }
    ///////////////////////
    // MY CODE ENDS HERE
    ///////////////////////
}
The GetPathName function returns the path and filename, so changing the function call from GetFileName to GetPathName alters the display to include the path with the filename.

2. Add a button on the custom dialog that calls the MessageBox function with a Yes or No selection. Pass the result back to the main application dialog.

Follow these steps:

1. Using the Class View, add a member variable to the CMsgDlg class. Specify the variable type as int, the name as m_iYesNo, and the access as Public.

2. Using the Resource View, bring the custom dialog into the editor area. Add a command button to the window, named IDC_YESNO with a caption &Yes or No.

3. Using the Class Wizard, add a function to the new button you just added and edit the function. Include the following code:

void CMsgDlg::OnYesno() 
{
    // TODO: Add your control notification handler code here
    ///////////////////////
    // MY CODE STARTS HERE
    ///////////////////////
    // Ask the user
    m_iYesNo = MessageBox("Choose Yes or No", "Yes or No", ÂMB_YESNO);
    ///////////////////////
    // MY CODE ENDS HERE
    ///////////////////////
}
4. Add a button to the main dialog window named IDC_YESNO with the caption Y&es or No.

5. Using the Class Wizard, add a function to the new button, including the following code:

void CDialogsDlg::OnYesno() 
{
    // TODO: Add your control notification handler code here
    ///////////////////////
    // MY CODE STARTS HERE
    ///////////////////////
    // What did the user answer
    switch (m_dMsgDlg.m_iYesNo)
    {
    case IDYES:    // Did the user answer YES?
        m_sResults = "Yes!";
        break;
    case IDNO:     // Did the user answer NO?
        m_sResults = "No!";
        break;
}

    // Update the dialog
    UpdateData(FALSE);
    ///////////////////////
    // MY CODE ENDS HERE
    ///////////////////////
}

Day 6

Quiz

1. What event message does a menu selection send to the window message queue?

COMMAND.

2. How do you attach a menu to a dialog window?

In the dialog designer, open the properties dialog for the window, and choose the menu from the drop-down list of menus.

3. Which existing class do you specify for handling event messages for the menu?

The dialog class for the window on which the menu appears.

4. What event message should a pop-up menu be triggered by?

The WM_CONTEXTMENU event.

Exercises

1. Add a button to the main window and have it call the same function as the Hello menu entry.

Follow these steps:

1. Add a button to the dialog screen. Supply a button ID of IDC_HELLO and a caption of &Hello.

2. Using the Class Wizard, add a function to the button. Name the function OnHello.

2. Add a pop-up menu to your application that uses the Help drop-down menu as the pop-up menu.

Follow these steps:

1. Using the Class Wizard, add a function for the WM_CONTEXTMENU event message in your dialog window.

2. Edit the function, adding the following code:

void CMenusDlg::OnContextMenu(CWnd* pWnd, CPoint point)
{
    // TODO: Add your message handler code here and/or call Âdefault
    ///////////////////////
    // MY CODE STARTS HERE
    ///////////////////////
    // Declare local variables
    CMenu *m_lMenu;    // A pointer to the menu
    CPoint m_pPoint;    // A copy of the mouse position
    // Copy the mouse position to a local variable
    m_pPoint = point;
    // Convert the position to a screen position
    ClientToScreen(&m_pPoint);
    // Get a pointer to the window menu
    m_lMenu - GetMenu();
    // Get a pointer to the first submenu
    m_lMenu = m_lMenu->GetSubMenu(1);
    // Show the Pop-up Menu
    m_lMenu->TrackPopupMenu(TPM_CENTERALIGN + TPM_LEFTBUTTON,
        m_pPoint.x, m_pPoint.y, this, NULL);
    ///////////////////////
    // MY CODE ENDS HERE
    ///////////////////////
    CDialog::OnRButtonDown(nFlags, point);
}

Day 7

Quiz

1. How can you specify that the text is to be underlined?

Pass 1 as the value for the bUnderline argument to the CreateFont function.

2. How can you print your text upside down?

Pass 1800 as the nEscapement argument to the CreateFont function.

3. How many times is the EnumFontFamProc callback function called by the operating system?

The function is called once for each font that is available in the system, unless the callback function returns 0 and stops the listing of fonts.

Exercises

1. Add a check box to switch between using the entered text to display the font and using the font name to display the font, as in Figure 7.4.

Add the check box to the dialog. Set its properties as follows:

ID: IDC_CBUSETEXT

Caption: &Use Entered Text

Using the Class Wizard, attach a variable to this control. Specify the variable type as a boolean with the name m_bUseText.

Using the Class Wizard, add a function for the BN_CLICKED event message for the check box. Edit the function, adding the following code:

void CDay7Dlg::OnCbusetext()
{
    // TODO: Add your control notification handler code here
    ///////////////////////
    // MY CODE STARTS HERE
    ///////////////////////
    // Update the variables with the dialog controls
    UpdateData(TRUE);
    // Using the font name for the font sample?
    if (!m_bUseText)
        // Using the font name
        m_strDisplayText = m_strFontName;
    else
        // Using the entered text
        m_strDisplayText = m_strSampText;
    // Update the dialog
    UpdateData(FALSE);
    ///////////////////////
    // MY CODE ENDS HERE
    ///////////////////////
}
Modify the OnInitDialog function to initialize the check box as follows:

BOOL CDay7Dlg::OnInitDialog()
{
    CDialog::OnInitDialog();
.
.
.
    // TODO: Add extra initialization here
    ///////////////////////
    // MY CODE STARTS HERE
    ///////////////////////
    // Fill the font list box
    FillFontList();
    // Initialize the text to be entered
    m_strSampText = "Testing";
    // Copy the text to the font sample area
    m_strDisplayText = m_strSampText;
    // Initialize the check box
    m_bUseText = TRUE;
    // Update the dialog
    UpdateData(FALSE);
    ///////////////////////
    // MY CODE ENDS HERE
    ///////////////////////
    return TRUE;  // return TRUE  unless you set the focus 
                  // to a control
}
Modify the OnSelchangeLfonts function as follows:

void CDay7Dlg::OnSelchangeLfonts()
{
    // TODO: Add your control notification handler code here
    ///////////////////////
    // MY CODE STARTS HERE
    ///////////////////////
    // Update the variables with the dialog controls
    UpdateData(TRUE);
    // Using the font name for the font sample?
    if (!m_bUseText)
    {
        // Copy the font name to the font sample
        m_strDisplayText = m_strFontName;
        // Update the dialog with the variables
        UpdateData(FALSE);
    }
    // Set the font for the sample
    SetMyFont();
    ///////////////////////
    // MY CODE ENDS HERE
    ///////////////////////
}
Finally, modify the OnChangeEsamptext function as follows:

void CDay7Dlg::OnChangeEsamptext()
{
    // TODO: If this is a RICHEDIT control, the control will not
    // send this notification unless you override the 
    // CDialog::OnInitialUpdate()
    // function and call CRichEditCrtl().SetEventMask()
    // with the EN_CHANGE flag ORed into the mask.
    // TODO: Add your control notification handler code here
    ///////////////////////
    // MY CODE STARTS HERE
    ///////////////////////
    // Update the variables with the dialog controls
    UpdateData(TRUE);
    // Using the text for the font sample?
    if (m_bUseText)
    {
        // Copy the current text to the font sample
        m_strDisplayText = m_strSampText;
        // Update the dialog with the variables
        UpdateData(FALSE);
    }
    ///////////////////////
    // MY CODE ENDS HERE
    ///////////////////////
}
2. Add a check box to display the font sample in italics, as in Figure 7.5.

Add the check box to the dialog. Set its properties as follows:

ID: IDC_CBITALIC

Caption: &Italic

Using the Class Wizard, attach a variable to this control. Specify the variable type as a boolean with the name m_bItalic.

Using the Class Wizard, add a function for the BN_CLICKED event message for the check box. Edit the function, adding the following code:

void CDay7Dlg::OnCbitalic()
{
    // TODO: Add your control notification handler code here
    ///////////////////////
    // MY CODE STARTS HERE
    ///////////////////////
    // Update the variables with the dialog controls
    UpdateData(TRUE);
    // Set the font for the sample
    SetMyFont();
    ///////////////////////
    // MY CODE ENDS HERE
    ///////////////////////
}
Modify the SetMyFont function as in the following code:

void CDay7Dlg::SetMyFont()
{
    CRect rRect;        // The rectangle of the display area
    int iHeight;        // The height of the display area
    int iItalic = 0;    // Italicize the font?
    // Has a font been selected?
    if (m_strFontName != "")
    {
        // Get the dimensions of the font sample display area
        m_ctlDisplayText.GetWindowRect(&rRect);
        // Calculate the area height
        iHeight = rRect.top - rRect.bottom;
        // Make sure the height is positive
        if (iHeight < 0)
            iHeight = 0 - iHeight;
        // Should the font be italicized?
        If (m_bItalic)
            iItalic = 1;
        // Create the font to be used
        m_fSampFont.CreateFont((iHeight - 5), 0, 0, 0, 
                FW_NORMAL, iItalic, 0, 0, DEFAULT_CHARSET, 
                OUT_CHARACTER_PRECIS, CLIP_CHARACTER_PRECIS, 
                DEFAULT_QUALITY, DEFAULT_PITCH |
                FF_DONTCARE, m_strFontName);
        // Set the font for the sample display area
        m_ctlDisplayText.SetFont(&m_fSampFont);
    }
}

Day 8

Quiz

1. What are the three values that are combined to specify a color?

Red, green, and blue.

2. What do you use to draw on windows without needing to know what graphics card the user has?

The device context.

3. What size bitmap can you use to make a brush from it?

8 pixels by 8 pixels.

4. What event message is sent to a window to tell it to redraw itself?

The WM_PAINT message.

5. How can you cause a window to repaint itself?

Use the Invalidate function on it.

Exercises

1. Make the second dialog window resizable, and make it adjust the figures drawn on it whenever it's resized.

Open the second dialog in the dialog layout designer. Open the properties for the window. Select the Style tab. Change the border to Resizing. Open the Class Wizard and add an event-handler function for the WM_SIZE event message. Edit the function that you just created and call the Invalidate function, as in Listing B.7.

LISTING B.7. THE OnSize FUNCTION.

1: void CPaintDlg::OnSize(UINT nType, int cx, int cy)
2: {
3:     CDialog::OnSize(nType, cx, cy);
4:
5:     // TODO: Add your message handler code here
6:     // Redraw the window
7:     Invalidate();
8:}
2. Add a bitmap brush to the set of brushes used to create the rectangles and ellipses.

Open the Resources View tab on the workspace pane. Right-click on the top folder of the resource tree. Select Insert from the pop-up menu. Select Bitmap from the list of available resources to add. Paint a pattern on the bitmap that you just created. Right-click on the bitmap ID in the workspace pane. Open the properties dialog and change the object ID to IDB_BITMAPBRUSH. Open the source code for the DrawRegion function. Add the code in Listing B.8, lines 22 through 24 and lines 105 through 112. Increase the number of loops in the for statement on line 39.

LISTING B.8. THE DrawRegion FUNCTION.

1:  void CPaintDlg::DrawRegion(CPaintDC *pdc, int iColor, int iTool, int 		  ÂiShape)
2:  {
3:      // Declare and create the pens
.
.
.
19:     CBrush lVertBrush(HS_VERTICAL, m_crColors[iColor]);
20:     CBrush lNullBrush(RGB(192, 192, 192));
21:
22:     CBitmap lBitmap;
23:     lBitmap.LoadBitmap(IDB_BITMAPBRUSH);
24:     CBrush lBitmapBrush(&lBitmap);
25:
26:     // Calculate the size of the drawing regions
27:     CRect lRect;
28:     GetClientRect(lRect);
.
.
.
37:     int i;
38:     // Loop through all of the brushes and pens
39:     for (i = 0; i < 8; i++)
40:     {
41:         switch (i)
42:         {
.
.
.
103:            pdc->SelectObject(&lVertBrush);
104:            break;
105:        case 7:    // Null - Bitmap
106:            // Determine the location for this figure.
107:            lDrawRect.left = lDrawRect.left + liHorz;
108:            lDrawRect.right = lDrawRect.left + liWidth;
109:            // Select the appropriate pen and brush
110:            pdc->SelectObject(&lNullPen);
111:            pdc->SelectObject(&lBitmapBrush);
112:            break;
113:        }
114:        // Which tool are we using?
.
.
.
126:    pdc->SelectObject(lOldBrush);
127:    pdc->SelectObject(lOldPen);
128:}

Day 9

Quiz

1. How does an ActiveX container call methods in an ActiveX control?

By using the IDispatch interface, the container can call the Invoke method, passing the DISPID of the control's method that the container wants to run.

2. How does an ActiveX control trigger events in the container application?

The container application has its own IDispatch interface, through which the control can trigger events.

3. What AppWizard option must be selected for ActiveX controls to work properly in a Visual C++ application?

You select the ActiveX Controls check box in the second step of the AppWizard.

4. How does Visual C++ make it easy to work with ActiveX controls?

It generates C++ classes that encapsulate the control's functionality.

5. Why might it be difficult to work with older controls in Visual C++?

Older controls might not contain the information necessary for Visual C++ to generate the C++ classes that are used to encapsulate the control's functionality.

Exercise

Modify the application so that the user can double-click a column header and make it the first column in the grid.

Using the Class Wizard, add a function to the DblClick event message for the grid control.

Edit the function in exercise 1 to add the following code:

void CActiveXDlg::OnDblClickMsfgrid()
{
    // TODO: Add your control notification handler code here
    ///////////////////////
    // MY CODE STARTS HERE
    ///////////////////////
    // Did the user click on a data row and not the
    // header row?
    if (m_ctlFGrid.GetMouseRow() != 0)
    {
        // If so, then zero out the column variable
        // and exit
        m_iMouseCol = 0;
        return;
    }
    // Save the column clicked on
    m_iMouseCol = m_ctlFGrid.GetMouseCol();
    // If the selected column was the first column,
    // there's nothing to do
    if (m_iMouseCol == 0)
        return;
    // Turn the control redraw off
    m_ctlFGrid.SetRedraw(FALSE);
    // Change the selected column position
    m_ctlFGrid.SetColPosition(m_iMouseCol, 0);
    // Resort the grid
    DoSort();
    // Turn redraw back on
    m_ctlFGrid.SetRedraw(TRUE);
    ///////////////////////
    // MY CODE ENDS HERE
    ///////////////////////
}

Day 10

Quiz

1. What does SDI stand for?

Single Document Interface.

2. What functionality is in the view class?

The view class is responsible for displaying the document for the user.

3. What function is called to redraw the document if the window has been hidden behind another window?

The OnDraw function in the view class is called to redraw the document.

4. Where do you place code to clear out the current document before starting a new document?

The DeleteContents function in the document class is where you place code to clear the current document.

5. What is the purpose of the document class?

The document class is where the data is managed and manipulated. It maintains the abstract representation of the document being edited and processed.

Exercise

Add another pull-down menu to control the width of the pen used for drawing. Give it the following settings:

Menu Entry Width Setting
Very Thin 1
Thin 8
Medium 16
Thick 24
Very Thick 32

Follow these steps:

1. Select the CLine class in the Class View tab of the workspace pane. Right-click the mouse and select Add Member Variable from the pop-up menu.

2. Specify the variable type as UINT, the name as m_nWidth, and the access as private. Click OK to add the variable.

3. Right-click the CLine constructor in the Class View tree. Select Go to Declaration from the pop-up menu.

4. Add UINT nWidth as a fourth argument to the constructor declaration.

5. Right-click the CLine constructor in the Class View tree. Select Go to Definition from the pop-up menu.

6. Modify the constructor to add the fourth argument and to set the m_nWidth member to the new argument, as in Listing B.9.

LISTING B.9. THE MODIFIED CLine CONSTRUCTOR.

1: CLine::CLine(CPoint ptFrom, CPoint ptTo, COLORREF crColor, UINT nWidth)
2: {
3:     //Initialize the from and to points
4:     m_ptFrom = ptFrom;
5:     m_ptTo = ptTo;
6:     m_crColor = crColor;
7:     m_nWidth = nWidth;
8: }
7. Scroll down to the Draw function and modify it as in Listing B.10.

LISTING B.10. THE MODIFIED Draw FUNCTION.

 1: void CLine::Draw(CDC * pDC)
 2: {
 3:     // Create a pen
 4:     CPen lpen (PS_SOLID, m_nWidth, m_crColor);
 5: 
 6:     // Set the new pen as the drawing object
 7:     CPen* pOldPen = pDC->SelectObject(&lpen);
 8:     // Draw the line
 9:     pDC->MoveTo(m_ptFrom);
10:     pDC->LineTo(m_ptTo);
11:     // Reset the previous pen
12:     pDC->SelectObject(pOldPen);
13: }
8. Scroll down to the Serialize function and modify it as in Listing B.11.

LISTING B.11. THE MODIFIED Serialize FUNCTION.

 1: void CLine::Serialize(CArchive &ar)
 2: {
 3:     CObject::Serialize(ar);
 4: 
 5:     if (ar.IsStoring())
 6:         ar << m_ptFrom << m_ptTo << (DWORD) m_crColor << m_nWidth;
 7:     else
 8:         ar >> m_ptFrom >> m_ptTo >> (DWORD) m_crColor >> m_nWidth;
9: }
9. Select the CDay10Doc class in the Class View tab on the workspace pane. Right-click the mouse and choose Add Member Variable from the pop-up menu.

10. Specify the variable type as UINT, the name as m_nWidth, and the access as private. Click OK to add the variable.

11. Open the CDay10Doc source code (Day10Doc.cpp), scroll down to the OnNewDocument function, and edit it as in Listing B.12.

LISTING B.12. THE MODIFIED OnNewDocument FUNCTION.

 1: BOOL CDay10Doc::OnNewDocument()
 2: {
 3:     if (!CDocument::OnNewDocument())
 4:         return FALSE;
 5: 
 6:     // TODO: add reinitialization code here
 7:     // (SDI documents will reuse this document)
 8: 
 9:     ///////////////////////
10:     // MY CODE STARTS HERE
11:     ///////////////////////
12: 
13:     // Initialize the color to black
14:     m_nColor = ID_COLOR_BLACK - ID_COLOR_BLACK;
15:     // Initialize the width to thin
16:     m_nWidth = ID_WIDTH_VTHIN - ID_WIDTH_VTHIN;
17: 
18:     ///////////////////////
19:     // MY CODE ENDS HERE
20:     ///////////////////////
21: 
22:     return TRUE;
23: }
12. Scroll down to the AddLine function, and modify it as in Listing B.13.

LISTING B.13. THE MODIFIED AddLine FUNCTION.

 1: CLine * CDay10Doc::AddLine(CPoint ptFrom, CPoint ptTo)
 2: {
 3:     static UINT nWidths[5] = { 1, 8, 16, 24, 32};
 4: 
 5:     // Create a new CLine object
 6:     CLine *pLine = new CLine(ptFrom, ptTo,
                  Âm_crColors[m_nColor], nWidths[m_nWidth]);
 7:     try
 8:     {
 9:         // Add the new line to the object array
10:         m_oaLines.Add(pLine);
11:         // Mark the document as dirty
12:         SetModifiedFlag();
13:     }
14:     // Did we run into a memory exception?
15:     catch (CMemoryException* perr)
16:     {
17:         // Display a message for the user, giving him or her the
18:         // bad news
19:         AfxMessageBox("Out of memory", MB_ICONSTOP | MB_OK);
20:         // Did we create a line object?
21:         if (pLine)
22:         {
23:             // Delete it
24:             delete pLine;
25:             pLine = NULL;
26:         }
27:         // Delete the exception object
28:         perr->Delete();
29:     }
30:     return pLine;
31: }
13. Add a new member function to the CDay10Doc class. Specify the function type as UINT, the declaration as GetWidth, and the access as public.

14. Edit the GetWidth function, adding the code in Listing B.14.

LISTING B.14. THE GetWidth FUNCTION.

1: UINT CDay10Doc::GetWidth()
2: {
3:     // Return the current width
4:     return ID_WIDTH_VTHIN + m_nWidth;
5: }
15. Select the Resource View tab in the workspace pane. Expand the tree so that you can see the contents of the Menu folder. Double-click the menu resource.

16. Grab the blank top-level menu (at the right end of the menu bar) and drag it to the left, dropping it in front of the View menu entry.

17. Open the properties for the blank menu entry. Specify the caption as &Width. Close the properties dialog.

18. Add submenu entries below the Width top-level menu. Specify the submenus in order, setting their properties as specified in Table B.1.

TABLE B.1. MENU PROPERTY SETTINGS.

Object Property Setting
Menu Entry ID ID_WIDTH_VTHIN
Caption &Very Thin
Menu Entry ID ID_WIDTH_THIN
Caption Thi&n
Menu Entry ID ID_WIDTH_MEDIUM
Caption &Medium
Menu Entry ID ID_WIDTH_THICK
Caption Thic&k
Menu Entry ID ID_WIDTH_VTHICK
Caption Very &Thick

19. Open the Class Wizard. Select the CDay10Doc in the Class Name combo box.

20. Add functions for both the COMMAND and UPDATE_COMMAND_UI event messages for all the width menu entries.

21. After you add the final menu entry function, click Edit Code.

22. Edit the Very Thin menu functions as in Listing B.15.

LISTING B.15. THE VERY THIN MENU FUNCTIONS.

 1: void CDay10Doc::OnWidthVthin()
 2: {
 3:     // TODO: Add your command handler code here
 4: 
 5:     ///////////////////////
 6:     // MY CODE STARTS HERE
 7:     ///////////////////////
 8: 
 9:     // Set the current width to Very Thin
10:     m_nColor = ID_WIDTH_VTHIN - ID_WIDTH_VTHIN;
11: 
12:     ///////////////////////
13:     // MY CODE ENDS HERE
14:     ///////////////////////
15: }
16: 
17: void CDay10Doc::OnUpdateWidthVthin(CCmdUI* pCmdUI)
18: {
19:     // TODO: Add your command update UI handler code here
20: 
21:     ///////////////////////
22:     // MY CODE STARTS HERE
23:     ///////////////////////
24: 
25:     // Determine if the Very Thin menu entry should be checked
26:     pCmdUI->SetCheck(GetWidth() == ID_WIDTH_VTHIN ? 1 : 0);
27: 
28:     ///////////////////////
29:     // MY CODE ENDS HERE
30:     ///////////////////////
31: }
23. Edit the Thin menu functions as in Listing B.16. Edit the remaining menu functions in the same way, substituting their menu IDs for ID_WIDTH_THIN.

LISTING B.16. THE THIN MENU FUNCTIONS.

 1: void CDay10Doc::OnWidthThin()
 2: {
 3:     // TODO: Add your command handler code here
 4: 
 5:     ///////////////////////
 6:     // MY CODE STARTS HERE
 7:     ///////////////////////
 8: 
 9:     // Set the current width to Thin
10:     m_nColor = ID_WIDTH_THIN - ID_WIDTH_VTHIN;
11: 
12:     ///////////////////////
13:     // MY CODE ENDS HERE
14:     ///////////////////////
15: }
16: 
17: void CDay10Doc::OnUpdateWidthThin(CCmdUI* pCmdUI)
18: {
19:     // TODO: Add your command update UI handler code here
20: 
21:     ///////////////////////
22:     // MY CODE STARTS HERE
23:     ///////////////////////
24: 
25:     // Determine if the Thin menu entry should be checked
26:     pCmdUI->SetCheck(GetWidth() == ID_WIDTH_THIN ? 1 : 0);
27: 
28:     ///////////////////////
29:     // MY CODE ENDS HERE
30:     ///////////////////////
31: }

Day 11

Quiz

1. What are the five base classes that are used in MDI applications?

The CWinApp-derived class, the CMDIFrameWnd-derived class, the CMDIChildWnd-derived class, the CDocument-derived class, and the CView-derived class.

2. Why do you have to place the ON_COMMAND_RANGE message map entry outside the section maintained by the Class Wizard?

The Class Wizard doesn't understand the ON_COMMAND_RANGE message map entry and thus would either remove or corrupt it.

3. What argument does ON_COMMAND_RANGE pass to the event function?

The ID of the event message.

4. What event message should you use to display a pop-up menu?

WM_CONTEXTMENU.

Exercise

Add the pull-down and context menus for the width, using the same pen widths as yesterday.

Follow these steps:

1. Add the Width handling code as in yesterday's exercise.

2. Add the Width menu entries using the same settings as yesterday.

3. Open the Day11Doc.h header file.

4. Scroll down toward the bottom of the header file until you find the protected section where the AFX_MSG message map is declared (search for //{{AFX_MSG(CDay11Doc)).

5. Add the function declarations in Listing B.17 before the line that you searched for. (The string that you searched for is the beginning marker for the Class Wizard maintained message map. Anything you place between it and the end marker, //}}AFX_MSG, is likely to be removed or corrupted by the Class Wizard.)

LISTING B.17. THE EVENT-HANDLER DECLARATIONS IN DayllDoc.H.

.
.
.
 1: #ifdef _DEBUG
 2:     virtual void AssertValid() const;
 3:     virtual void Dump(CDumpContext& dc) const;
 4: #endif
 5: 
 6: protected:
 7: 
 8: // Generated message map functions
 9: protected:
10:     afx_msg void OnColorCommand(UINT nID);
11:     afx_msg void OnWidthCommand(UINT nID);
12:     afx_msg void OnUpdateColorUI(CCmdUI* pCmdUI);
13:     afx_msg void OnUpdateWidthUI(CCmdUI* pCmdUI);
14:     //{{AFX_MSG(CDay11Doc)
15:         // NOTE - the ClassWizard will add and remove member functions             //here.
16:         //    DO NOT EDIT what you see in these blocks of generated             //code !
17:     //}}AFX_MSG
18:     DECLARE_MESSAGE_MAP()
19: private:
20:     UINT m_nColor;
21:     CObArray m_oaLines;
22: };
6. Open the Day11Doc.cpp source-code file.

7. Search for the line BEGIN_MESSAGE_MAP and add the lines in Listing B.18 just after it. It's important that this code be between the BEGIN_MESSAGE_MAP line and the //{{AFX_MSG_MAP line. If these commands are between the //{{AFX_MSG_MAP and //}}AFX_MSG_MAP lines, then the Class Wizard will remove or corrupt them.

LISTING B.18. THE EVENT-HANDLER MESSAGE MAP ENTRIES IN Day11Doc.cpp.

 1:///////////////////////////////////////////////////////////////////////  2: // CDay11Doc
 3: 
 4: IMPLEMENT_DYNCREATE(CDay11Doc, CDocument)
 5: 
 6: BEGIN_MESSAGE_MAP(CDay11Doc, CDocument)
 7:     ON_COMMAND_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, OnColorCommand)
 8:     ON_COMMAND_RANGE(ID_WIDTH_VTHIN, ID_WIDTH_VTHICK, OnWidthCommand)
 9:     ON_UPDATE_COMMAND_UI_RANGE(ID_COLOR_BLACK, ID_COLOR_WHITE, 		ÂOnUpdateColorUI)
10:     ON_UPDATE_COMMAND_UI_RANGE(ID_WIDTH_VTHIN, ID_WIDTH_VTHICK, ÂOnUpdateWidthUI)
11:     //{{AFX_MSG_MAP(CDay11Doc)
12:         // NOTE - the ClassWizard will add and remove mapping macros             //here.
13:         //    DO NOT EDIT what you see in these blocks of generated             //code!
14:     //}}AFX_MSG_MAP
15: END_MESSAGE_MAP()
16: 
17: const COLORREF CDay11Doc::m_crColors[8] = {
18:     RGB(   0,   0,   0),    // Black
19:     RGB(   0,   0, 255),    // Blue
.
.

.

8. Scroll to the bottom of the file and add the two event message handler functions in Listing B.19.

LISTING B.19. THE WIDTH MENU EVENT HANDLER FUNCTIONS.

 1: void CDay11Doc::OnWidthCommand(UINT nID)
 2: {
 3:     // Set the current width
 4:     m_nWidth = nID - ID_WIDTH_VTHIN;
 5: }
 6: 
 7: void CDay11Doc::OnUpdateWidthUI(CCmdUI* pCmdUI)
 8: {
 9:     // Determine if the menu entry should be checked
10:     pCmdUI->SetCheck(GetWidth() == pCmdUI->m_nID ? 1 : 0);
11: }
9. Open the IDR_CONTEXTMENU in the Menu Designer.

10. In the Width cascading menu, add the width menu entries just like you did for the IDR_DAY11TYPE menu, using the same property settings. You can select the ID from the drop-down list of IDs if you would rather search for them instead of type.

Day 12

Quiz

1. How do you tie a toolbar button to a menu entry that triggers that same function?

Give the toolbar button the same object ID as the menu entry.

2. How do you make sure that a toolbar can be docked with the frame window?

Both must have docking enabled on the same sides (using the EnableDocking function) in the OnCreate function of the frame class.

3. How can you remove the Num Lock status indicator from the status bar?

Remove the ID_INDICATOR_NUM from the indicators table near the top of the main frame source code file.

4. Why do you have to edit the resource file to add a combo box to a toolbar?

You need to add a separator to the toolbar as a placeholder in the toolbar. The toolbar designer will do its best to prevent you from adding the separators, assuming that they are a mistake.

Exercises

1. Add another pane to the status bar to display the current width selected.

Add an entry to the strings table with an ID of ID_INDICATOR_WIDTH and a caption of VERY THICK.

Add another entry to the status bar indicators table at the beginning of CMainFrame.cpp:

static UINT indicators[] =
{
    ID_SEPARATOR,           // status line indicator
    ID_INDICATOR_WIDTH,
    ID_INDICATOR_COLOR,
    ID_INDICATOR_CAPS,
    ID_INDICATOR_NUM,
    ID_INDICATOR_SCRL,
};
Add a new member function to the CToolbarDoc class. Specify the function type as afx_msg void, the function definition as OnUpdateIndicatorWidth (CCmdUI *pCmdUI), and the access as protected. Edit the function as follows:

void CToolbarDoc::OnUpdateIndicatorWidth(CCmdUI *pCmdUI)
{
    CString strWidth;
    // What is the current width?
    switch (m_nWidth)
    {
    case 0:    // Very Thin
        strWidth = "VERY THIN";
        break;
    case 1:    // Thin
        strWidth = "THIN";
        break;
    case 2:    // Medium
        strWidth = "MEDIUM";
        break;
    case 3:    // Thick
        strWidth = "THICK";
        break;
    case 4:    // Very Thick
        strWidth = "VERY THICK";
        break;
    }
    // Enable the status bar pane
    pCmdUI->Enable(TRUE);
    // Set the text of the status bar pane
    // to the current width
    pCmdUI->SetText(strWidth);
}
Edit the CToolbarDoc message map, adding the ON_UPDATE_COMMAND_UI message handler entry as follows:

////////////////////////////////////////////////////////////////
// CToolbarDoc
IMPLEMENT_DYNCREATE(CToolbarDoc, CDocument)
BEGIN_MESSAGE_MAP(CToolbarDoc, CDocument)
    ON_UPDATE_COMMAND_UI(ID_INDICATOR_WIDTH,
ÂOnUpdateIndicatorWidth)
    ON_UPDATE_COMMAND_UI(ID_INDICATOR_COLOR,
ÂOnUpdateIndicatorColor)
    //{{AFX_MSG_MAP(CToolbarDoc)
    ON_UPDATE_COMMAND_UI(ID_WIDTH_VTHIN, OnUpdateWidthVthin)
.
.
    ON_COMMAND(ID_WIDTH_VTHIN, OnWidthVthin)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()
2. Add a button to the main toolbar that can be used to toggle the color toolbar on and off, as in Figure 12.7.

Open the IDR_MAINFRAME toolbar in the toolbar designer. Paint an icon for the blank button at the end of the toolbar. Double-click the button to open its properties dialog. Specify the button ID as ID_VIEW_COLORBAR and enter an appropriate prompt for the button. Recompile and run your application, and the color toolbar toggle should be working on the main toolbar.

Day 13

Quiz

1. What two macros do you have to add to a class to make it serializable?

DECLARE_SERIAL and IMPLEMENT_SERIAL.

2. How can you determine whether the CArchive object is reading from or writing to the archive file?

You call the IsStoring or IsLoading functions.

3. What arguments do you need to pass to the IMPLEMENT_SERIAL macro?

The class name, the base class name, and the version number.

4. What class do you need to inherit the view class from to be able to use the dialog designer to create a form for the main window in an SDI or MDI application?

CFormView.

5. What type of file does the CArchive write to by default?

CFile.

Exercise

Add a couple of radio buttons to the form to specify the person's sex, as in Figure 13.5. Incorporate this change into the CPerson class to make the field persistent.

In the window designer, add the two radio buttons and the static text prompt. Specify the control properties in Table B.2.

TABLE B.2. CONTROL PROPERTY SETTINGS.

Object Property Setting
Static Text ID IDC_STATIC
Caption Sex:
Radio Button ID IDC_RMALE
Caption Mal&e
Group Checked
Radio Button ID IDC_RFEMALE
Caption &Female

Move the mnemonic in the First button from &First to Fi&rst to prevent a conflict with the new radio buttons.

Attach a variable to the new radio buttons as in Table B.3.

TABLE B.3. CONTROL VARIABLES.

Object Name Category Type
IDC_RMALE m_iSex Value int

Increment the version number in the IMPLEMENT_SERIAL macro in the CPerson class. Add a new member variable to the CPerson class. Specify the type as int, the name as m_iSex, and the access as private. Update the CPerson constructor function, adding the m_iSex variable to the initializations as in line 8 of Listing B.20.

LISTING B.20. THE MODIFIED CPerson CONSTRUCTOR.

 1: CPerson::CPerson()
 2: {
 3:     // Initialize the class variables
 4:     m_iMaritalStatus = 0;
 5:     m_iAge = 0;
 6:     m_bEmployed = FALSE;
 7:     m_sName = "";
 8:     m_iSex = 0;
9: }

Add the inline functions to the CPerson class declaration to set and get the value of this new variable, as in lines 9 and 15 of Listing B.21.

LISTING B.21. THE MODIFIED CPerson CLASS DECLARATION.

 1: class CPerson : public CObject
 2: {
 3:     DECLARE_SERIAL (CPerson)
 4: public:
 5:     // Functions for setting the variables
 6:     void SetEmployed(BOOL bEmployed) { m_bEmployed = bEmployed;}
 7:     void SetMaritalStat(int iStat) { m_iMaritalStatus = iStat;}
 8:     void SetAge(int iAge) { m_iAge = iAge;}
 9:     void SetSex(int iSex) { m_iSex = iSex;}
10:     void SetName(CString sName) { m_sName = sName;}
11:     // Functions for getting the current settings of the variables
12:     BOOL GetEmployed() { return m_bEmployed;}
13:     int GetMaritalStatus() { return m_iMaritalStatus;}
14:     int GetAge() {return m_iAge;}
15:     int GetSex() {return m_iSex;}
16:     CString GetName() {return m_sName;}
17:     CPerson();
18:     virtual ~CPerson();
19: 
20: private:
21:     BOOL m_bEmployed;
22:     int m_iMaritalStatus;
23:     int m_iAge;
24:     CString m_sName;
25: };

Update the Serialize function in the CPerson class to include the m_iSex variable as in lines 9 and 12 of Listing B.22.

LISTING B.22. THE MODIFIED CPerson.Serialize FUNCTION.

 1: void CPerson::Serialize(CArchive &ar)
 2: {
 3:     // Call the ancestor function
 4:     CObject::Serialize(ar);
 5: 
 6:     // Are we writing?
 7:     if (ar.IsStoring())
 8:         // Write all of the variables, in order
 9:         ar << m_sName << m_iAge << m_iMaritalStatus << m_bEmployed <<
                  Âm_iSex;
10:     else
11:         // Read all of the variables, in order
12:         ar >> m_sName >> m_iAge >> m_iMaritalStatus >> m_bEmployed >>
                 Âm_iSex;
13: 
14: }

Modify the PopulateView function in the view object to include the Sex variable in the data exchange, as in line 19 of Listing B.23.

LISTING B.23. THE MODIFIED CSerializeView.POPULATEVIEW FUNCTION.

 1: void CSerializeView::PopulateView()
 2: {
 3:     // Get a pointer to the current document
 4:     CSerializeDoc* pDoc = GetDocument();
 5:     if (pDoc)
 6:     {
 7:         // Display the current record position in the set
 8:         m_sPosition.Format("Record %d of %d", pDoc->GetCurRecordNbr(), 
 9:                 pDoc->GetTotalRecords());
10:     }
11:     // Do we have a valid record object?
12:     if (m_pCurPerson)
13:     {
14:         // Yes, get all of the record values
15:         m_bEmployed = m_pCurPerson->GetEmployed();
16:         m_iAge = m_pCurPerson->GetAge();
17:         m_sName = m_pCurPerson->GetName();
18:         m_iMaritalStatus = m_pCurPerson->GetMaritalStatus();
19:         m_iSex = m_pCurPerson->GetSex();
20:     }
21:     // Update the display
22:     UpdateData(FALSE);
23: }

Add an event handler for the clicked event of both new radio buttons, using the same function for both event handlers. Update the record object's field using the Set function, as in Listing B.24.

LISTING B.24. THE CSerializeView.OnSex FUNCTION.

 1: void CSerializeView::OnSex()
 2: {
 3:     // TODO: Add your control notification handler code here
 4: 
 5:     // Sync the data in the form with the variables
 6:     UpdateData(TRUE);
 7:     // If we have a valid person object, pass the data changes to it
 8:     if (m_pCurPerson)
 9:         m_pCurPerson->SetSex(m_iSex);
10:     // Get a pointer to the document
11:     CSerializeDoc * pDoc = GetDocument();
12:     if (pDoc)
13:         // Set the modified flag in the document
14:         pDoc->SetModifiedFlag();
15: }

Day 14

Quiz

1. What does ODBC stand for?

Open Database Connector.

2. What functions can you use to navigate the record set in a CRecordset object?

Move, MoveNext, MovePrev, MoveFirst, MoveLast, and SetAbsolutePosition.

3. What view class should you use with an ODBC application?

CRecordView.

4. What sequence of functions do you need to call to add a new record to a record set?

AddNew, Update, and Requery.

5. What function do you need to call before the fields in the CRecordset object can be updated with any changes?
Edit.

Exercise

Add a menu entry and dialog to let the user indicate the record number to move to, and then move to that record.

1. Create a new dialog, designing the dialog layout as in Figure B.1. Configure the controls as in Table B.4.

FIGURE B.1.

The Move To dialog layout.

TABLE B.4. DIALOG PROPERTY SETTINGS.

Object Property Setting
Static Text ID IDC_STATIC
Caption Move to record:
Edit Box ID IDC_ERECNBR

2. Open the Class Wizard. Create a new class for the new dialog. Give the new class the name CMoveToDlg. After you create the new class, add a variable to the Edit Box control. Specify the variable type as long and the name as m_lRowNbr.

3. Add another menu entry to the main application menu. Specify the menu properties as in Table B.5.

TABLE B.5. MENU PROPERTY SETTINGS.

Object Property Setting
Menu Entry ID IDM_RECORD_MOVE
Caption &Move To...
Prompt Move to a specific record\nMove To

4. Open the Class Wizard and add an event-handler function for the COMMAND message for this new menu to the view class. Edit this function, adding the code in Listing B.25.

LISTING B.25. THE CDbOdbcView OnRecordMove FUNCTION.

 1:  void CTestdb5View::OnRecordMove() 
 2:  {
 3:      // TODO: Add your command handler code here
 4:      // Create an instance of the Move To dialog
 5:      CMoveToDlg dlgMoveTo;
 6:      // Get the row number to move to
 7:      if (dlgMoveTo.DoModal() == IDOK)
 8:      {
 9:          // Get a pointer to the record set
10:         CRecordset* pSet = OnGetRecordset();
11:         // Make sure that there are no outstanding changes to be saved
12:         if (pSet->CanUpdate() && !pSet->IsDeleted())
13:         {
14:             pSet->Edit();
15:             if (!UpdateData())
16:                 return;
17:
18:             pSet->Update();
19:         }
20:         // Set the new position
21:         pSet->SetAbsolutePosition(dlgMoveTo.m_lRowNbr);
22:         // Update the form
23:         UpdateData(FALSE);
24:     }
25: }
5. Include the header file for the new dialog in the view class source code, as in line 10 of Listing B.26.

LISTING B.26. THE CDbOdbcView INCLUDES.

 1:  // DbOdbcView.cpp : implementation of the CDbOdbcView class
 2:  //
 3:
 4:  #include "stdafx.h"
 5:  #include "DbOdbc.h"
 6:
 7:  #include "DbOdbcSet.h"
 8:  #include "DbOdbcDoc.h"
 9:  #include "DbOdbcView.h"
10: #include "MoveToDlg.h"
6. Add a toolbar button for the new menu entry.

Day 15

Quiz

1. What does ADO stand for?

ActiveX Data Objects.

2. What does ADO use for database access?

OLE DB.

3. What are the objects in ADO?

Connection, Command, Parameter, Error, Recordset, and Field.

4. How do you initialize the COM environment?

::CoInitialize(NULL);
5. How do you associate a Connection object with a Command object?

pCmd->ActiveConnection = pConn;
6. How do you associate a Command object with and populate a Recordset object?

One of two ways:

_RecordsetPtr pRs;
pRs = pCmd->Execute();
Or

_RecordsetPtr pRs;
pRs.CreateInstance(__uuidof(Recordset));
pRs->PutRefSource(pCmd);

Exercise

Enable and disable the navigation menus and toolbar buttons based on whether the recordset is at the beginning of file (BOF) or end of file (EOF, renamed to EndOfFile).

Add event-handler functions to the document class for the navigation menu entries' UPDATE_COMMAND_UI event message. Edit these functions, adding the code in Listing B.27 to the functions for the First and Previous menus, and the code in Listing B.28 to the functions for the Last and Next menus.

LISTING B.27. THE CDbAdoDoc OnUpdateDataFirst FUNCTION.

 1: void CDbAdoDoc::OnUpdateDataFirst(CCmdUI* pCmdUI)
 2: {
 3:     // TODO: Add your command update UI handler code here
 4:     // Does the record set exist?
 5:     if (m_pRs)
 6:     {
 7:         // Are we at the BOF?
 8:         if (m_pRs->BOF)
 9:             pCmdUI->Enable(FALSE);
10:         else
11:             pCmdUI->Enable(TRUE);
12:     }
13: }

LISTING B.28. THE CDbAdoDoc OnUpdateDataLast FUNCTION.

 1: void CDbAdoDoc::OnUpdateDataLast(CCmdUI* pCmdUI)
 2: {
 3:     // TODO: Add your command update UI handler code here
 4:     // Does the record set exist?
 5:     if (m_pRs)
 6:     {
 7:         // Are we at the EOF?
 8:         if (m_pRs->EndOfFile)
 9:             pCmdUI->Enable(FALSE);
10:         else
11:             pCmdUI->Enable(TRUE);
12:     }
13: }

Day 16

Quiz

1. When do you want to create a new MFC class?

When you need to create a new class that is inherited from an existing MFC class.

2. When you make changes to a library file, what do you have to do to the applications that use the library file?

They all have to be relinked.

3. What are the different types of classes that you can create?

MFC, generic, and form.

4. When you package some functionality in a library file, what do you need to give to other programmers who want to use your library module?

The LIB library file and the header files for the objects in the module.

5. What are two of the basic principles in object-oriented software design?

Encapsulation and inheritance. The third principle is polymorphism, which was not discussed today.

Exercises

Separate the CLine class into a different library module from the drawing class so that you have two library modules instead of one. Link them into the test application.

1. Create a new project. Specify that the project is a Win32 Static Library project. Give the project a suitable name, such as Line.

2. Specify that the project contain support for MFC and precompiled headers.

3. Copy the Line.cpp and Line.h files into the project directory. Add both of these files to the project. Compile the library module.

4. Open the original library module project. Delete the Line.cpp and Line.h files from the project. Edit the include statement at the top of the drawing object source-code file to include the Line.h file from the Line module project directory, as on line 9 of Listing B.29. Recompile the project.

LISTING B.29. THE CModArt INCLUDES AND COLOR TABLE.

1:  // ModArt.cpp: implementation of the CModArt class.
2:  //
3:  //////////////////////////////////////////////////////////////////////
4:
 5:  #include <stdlib.h>
 6:  #include <time.h>
 7:
 8:  #include "stdafx.h"
 9:  #include "..\Line\Line.h"
10: #include "ModArt.h"
5. Open the test application project. Add the Line library file to the project. Build the project.

Day 17

Quiz

1. What kind of DLL do you have to create to make classes in the DLL available to applications?

An MFC extension DLL.

2. What do you have to add to the class to export it from a DLL?

The AFX_EXT_CLASS macro in the class declaration.

3. What kind of DLL can be used with other programming languages?

A regular DLL.

4. If you make changes in a DLL, do you have to recompile the applications that use the DLL?

Normally, no. Only if changes were made in the exported interface for the DLL do you need to recompile the applications that use the DLL.

5. What function does the LIB file provide for a DLL?

The LIB file contains stubs of the functions in the DLL, along with the code to locate and pass the function call along to the real function in the DLL.

Exercises

1. Separate the line class into its own MFC extension DLL and use it with the second (regular) DLL.

Create a new project. Specify that the project is an AppWizard (DLL) project, and give the project a suitable name, such as LineDll.

Specify that the DLL will be an MFC extension DLL.

After generating the project skeleton, copy the line source code and header files into the project directory. Add these files into the project.

Edit the CLine class declaration, adding the AFX_EXT_CLASS macro to the class declaration.

Compile the DLL. Copy the DLL into the debug directory for the test application.
Open the regular DLL project. Delete the line source code and header files from the project in the File View of the workspace pane. Add the line DLL LIB file to the project. Edit the drawing functionality source-code file, changing the line class header include to include the version in the CLine DLL project directory, as in Listing B.30.

LISTING B.30. THE CModArt INCLUDES.

 1:  // ModArt.cpp: implementation of the CModArt class.
 2:  //
 3:  /////////////////////////////////////////////////////////////////////
 4:
 5:  #include <stdlib.h>
 6:  #include <time.h>
 7:
 8:  #include "stdafx.h"
 9:  #include "..\LineDll\Line.h"
10: #include "ModArt.h"
Compile the project. Copy the DLL into the test application project debug directory.

Run the test application.

2. Alter the line class DLL so that it uses a consistent line width for all lines.

Open the line class DLL project that you created in the previous exercise. Edit the class constructor, replacing the initialization of the m_nWidth variable with a constant value, as in Listing B.31.

LISTING B.31. THE CLine CONSTRUCTOR.

1: CLine::CLine(CPoint ptFrom, CPoint ptTo, UINT nWidth, COLORREF crColor)
2: {
3:     m_ptFrom = ptFrom;
4:     m_ptTo = ptTo;
5:     m_nWidth = 1;
6:     m_crColor = crColor;
7: }
Compile the DLL. Copy the DLL into the test application project debug directory. Run the test application.############################

Day 18

Quiz

1. When is the OnIdle function called?

When the application is idle and there are no messages in the application message queue.

2. How can you cause the OnIdle function to be repeatedly called while the application is sitting idle?

Returning a value of TRUE will cause the OnIdle function to continue to be called as long as the application remains idle.

3. What is the difference between an OnIdle task and a thread?

An OnIdle task executes only when the application is idle and there are no messages in the message queue. A thread executes independently of the rest of the application.

4. What are the four thread synchronization objects?

Critical sections, mutexes, semaphores, and events.

5. Why shouldn't you specify a higher than normal priority for the threads in your application?

The rest of the threads and processes running on the computer will receive a greatly reduced amount of processor time.

Exercises

1. If you open a performance monitor on your system while the application that you built today is running, you'll find that even without any of the threads running, the processor usage remains 100 percent, as in Figure 18.11. The OnIdle function is continuously being called even when there is nothing to be done.

Modify the OnIdle function so that if there's nothing to be done, neither of the OnIdle tasks are active. Then, the OnIdle function will not continue to be called until one of these threads is active, at which time it should be continuously called until both threads are once again turned off. This will allow the processor to drop to a minimal utilization, as in Figure 18.12.

Edit the OnIdle function as in Listing B.32.

LISTING B.32. THE MODIFIED CTaskingApp OnIdle FUNCTION.

 1: BOOL CTaskingApp::OnIdle(LONG lCount)
 2: {
 3:     // TODO: Add your specialized code here and/or call the base class
 4: 
 5:     // Call the ancestor's idle processing
 6:     BOOL bRtn = CWinApp::OnIdle(lCount);
 7: 
 8:     // Get the position of the first document template
 9:     POSITION pos = GetFirstDocTemplatePosition();
10:     // Do we have a valid template position?
11:     if (pos)
12:     {
13:         // Get a pointer to the document template
14:         CDocTemplate* pDocTemp = GetNextDocTemplate(pos);
15:         // Do we have a valid pointer?
16:         if (pDocTemp)
17:         {
18:             // Get the position of the first document
19:             POSITION dPos = pDocTemp->GetFirstDocPosition();
20:             // Do we have a valid document position?
21:             if (dPos)
22:             {
23:                 // Get a pointer to the document
24:                 CTaskingDoc* pDocWnd =
25:                     (CTaskingDoc*)pDocTemp->GetNextDoc(dPos);
26:                 // Do we have a valid pointer?
27:                 if (pDocWnd)
28:                 {
29:                     // Get the position of the view
30:                     POSITION vPos = pDocWnd->GetFirstViewPosition();
31:                     // Do we have a valid view position?
32:                     if (vPos)
33:                     {
34:                         // Get a pointer to the view
35:                         CTaskingView* pView = 
              Â(CTaskingView*)pDocWnd->GetNextView(vPos);
36:                         // Do we have a valid pointer?
37:                         if (pView)
38:                         {
39:                             // Should we spin the first idle thread?
40:                             if (pView->m_bOnIdle1)
41:                             {
42:                                 // Spin the first idle thread
43:                                 pDocWnd->DoSpin(0);
44:                                 bRtn = TRUE;
45:                             }
46:                             // Should we spin the second idle thread?
47:                             if (pView->m_bOnIdle2)
48:                             {
49:                                 // Spin the second idle thread
50:                                 pDocWnd->DoSpin(2);
51:                                 bRtn = TRUE;
52:                             }
53:                         }
54:                     }
55:                 }
56:             }
57:         }
58:     }
59:     return bRtn;
60: }
2. When starting the independent threads, give one of the threads a priority of THREAD_PRIORITY_NORMAL and the other a priority of THREAD_PRIORITY_LOWEST.

Edit the SuspendSpinner function as in Listing B.33.

LISTING B.33. THE MODIFIED CTaskingDoc SuspendSpinner FUNCTION.

 1: void CTaskingDoc::SuspendSpinner(int nIndex, BOOL bSuspend)
 2: {
 3:     // if suspending the thread
 4:     if (!bSuspend)
 5:     {
 6:         // Is the pointer for the thread valid?
 7:         if (m_pSpinThread[nIndex])
 8:         {
 9:             // Get the handle for the thread
10:             HANDLE hThread = m_pSpinThread[nIndex]->m_hThread;
11:             // Wait for the thread to die
12:             ::WaitForSingleObject (hThread, INFINITE);
13:         }
14:     }
15:     else    // We are running the thread
16:     {
17:         int iSpnr;
18:         int iPriority;
19:         // Which spinner to use?
20:         switch (nIndex)
21:         {
22:         case 0:
23:             iSpnr = 1;
24:             iPriority = THREAD_PRIORITY_NORMAL;
25:             break;
26:         case 1:
27:             iSpnr = 3;
28:             iPriority = THREAD_PRIORITY_LOWEST;
29:             break;
30:         }
31:         // Start the thread, passing a pointer to the spinner
32:         m_pSpinThread[nIndex] = AfxBeginThread(ThreadFunc,
33:             (LPVOID)&m_cSpin[iSpnr], iPriority);
34:     }
35: }

Day 19

Quiz

1. What are the three aspects of a control that are visible to the container application?

Properties, methods, and events.

2. Why do you need to design a property page for your control?

To provide the user with the ability to set the properties of the control.

3. What are the four types of properties that a control might have?

Ambient, extended, stock, and custom.

4. What happens to the parameters that are passed to the methods of a control?

They are marshaled into a standardized, machine-independent structure.

5. What tool can you use to test your controls?

The ActiveX Control Test Container.

Exercises

1. Add a method to your control to enable the container application to trigger the generation of a new squiggle drawing.

Open the Class Wizard to the Automation tab. Click the Add Method button. Enter a method name, such as GenNewDrawing, and specify the return type as void. Click OK to add the method. Edit the method, adding the code in Listing B.34.

LISTING B.34. THE CSquiggleCtrl GenNewDrawing FUNCTION.

1: void CSquiggleCtrl:: GenNewDrawing()
2: {
3:     // TODO: Add your specialized code here and/or call the base class
4:     // Set the flag so a new drawing will be generated
5:     m_bGenNewDrawing = TRUE;
6:     // Invalidate the control to trigger the OnDraw function
7:    Invalidate();
8: }
2. Add a method to your control to save a squiggle drawing. Use the CFile::modeWrite and CArchive::store flags when creating the CFile and CArchive objects.

Open the Class Wizard to the Automation tab. Click the Add Method button. Enter a method name, such as SaveDrawing, and specify the return type as BOOL. Add a single parameter, sFileName, with a type of LPCTSTR. Click OK to add the method. Edit the method, adding the code in Listing B.35.

LISTING B.35. THE CSquiggleCtrl SaveDrawing FUNCTION.

 1:  BOOL CSquiggleCtrl::SaveDrawing(LPCTSTR sFileName)
 2:  {
 3:      // TODO: Add your dispatch handler code here
 4:      try
 5:      {
 6:          // Create a CFile object
 7:          CFile lFile(sFileName, CFile::modeWrite);
 8:          // Create a CArchive object to store the file
 9:          CArchive lArchive(&lFile, CArchive::store);
10:         // Store the file
11:         m_maDrawing.Serialize(lArchive);
12:     }
13:     catch (CFileException err)
14:     {
15:         return FALSE;
16:     }
17:     return TRUE;
18: }

Day 20

Quiz

1. What are the two things that a client application must know to be able to connect to a server application?

The network address (or name) of the computer and the port on which the server is listening.

2. What CAsyncSocket function is used to enable a server application to detect connection efforts by client applications?

Listen.

3. What CAsyncSocket member function is called to signal that data has arrived through a socket connection?

OnReceive.

4. What function is called to signal that a connection has been established?

OnConnect.

5. What function do you use to send a message through a socket connection to the application on the other end?

Send.

Exercises

The server application that you wrote can handle only a single connection at a time. If a second application tries to open a connection to it while it has an existing connection to an application, the server application will crash. The server tries to accept the second connection into the socket that is already connected to the first client application. Add a third socket object to the application that will be used to reject additional client connections until the first client closes the connection.


Follow these steps:

1. Add a member variable to the dialog class (CSockDlg). Specify the variable type as BOOL, the name as m_bConnected, and the access as private.

2. Initialize the variable as FALSE in the OnInitDialog function.

3. Set the variable to TRUE in the OnAccept dialog function once the connection has been accepted.

4. Set the variable to FALSE in the OnClose dialog function.

5. Modify the OnAccept dialog function as in Listing B.36.

LISTING B.36. THE MODIFIED CSockDlg OnAccept FUNCTION.

 1: void CSockDlg::OnAccept()
 2: {
 3:     if (m_bConnected)
 4:     {
 5:         // Create a rejection socket
 6:         CAsyncSocket sRjctSock;
 7:         // Create a message to send
 8:         CString strMsg = "Too many connections, try again later.";
 9:         // Accept using the rejection socket
10:         m_sListenSocket.Accept(sRjctSock);
11:         // Send the rejection message
12:         sRjctSock.Send(LPCTSTR(strMsg), strMsg.GetLength());
13:         // Close the socket
14:         sRjctSock.Close();
15:     }
16:     else
17:     {
18:         // Accept the connection request
19:         m_sListenSocket.Accept(m_sConnectSocket);\
20:         // Mark the socket as connected
21:         m_bConnected = TRUE;
22:         // Enable the text and message controls
23:         GetDlgItem(IDC_EMSG)->EnableWindow(TRUE);
24:         GetDlgItem(IDC_BSEND)->EnableWindow(TRUE);
25:         GetDlgItem(IDC_STATICMSG)->EnableWindow(TRUE);
26:     }
27: }

Day 21

Quiz

1. What does the CHtmlView class encapsulate for use in Visual C++ applications?

The Internet Explorer Web browser.

2. How can you get the URL for the current Web page from the CHtmlView class?

GetLocationURL().

3. What command is triggered for the frame class when the user presses the Enter key in the edit box on the dialog bar?

IDOK.

4. What functions can you call to navigate the browser to the previous and the next Web pages?

GoBack() and GoForward().

5. How can you stop a download in progress?

With the Stop() function.

Exercises

1. Add the GoSearch function to the menu and toolbar.

Add a menu entry to the Go menu. Specify the menu entry properties in Table B.6.

TABLE B.6. MENU PROPERTY SETTINGS.

Object Property Setting
Menu Entry ID IDM_GO_SEARCH
Caption &Search
Prompt Search the Web\nSearch

Using the Class Wizard, add an event-handler function to the view class on the IDM_GO_SEARCH ID for the COMMAND event message. Edit the code as in Listing B.37.

LISTING B.37. THE CWebBrowseView OnGoSearch FUNCTION.

1: void CWebBrowseView::OnGoSearch()
2: {
3:     // TODO: Add your command handler code here
4:
5:     // Go to the search page
6:     GoSearch();
7: }
Add a toolbar button for the menu ID IDM_GO_SEARCH.

2. Add the GoHome function to the menu and toolbar.

Add a menu entry to the Go menu. Specify the menu entry properties in Table B.7.

TABLE B.7. MENU PROPERTY SETTINGS.

Object Property Setting
Menu Entry ID IDM_GO_START
Caption S&tart Page
Prompt Go to the start page\nHome

Using the Class Wizard, add an event-handler function to the view class on the IDM_GO_START ID for the COMMAND event message. Edit the code as in Listing B.38.

LISTING B.38. THE CWebBrowseView OnGoStart FUNCTION.

1: void CWebBrowseView::OnGoStart()
2: {
3:     // TODO: Add your command handler code here
4:
5:     // Go to the start page
6:     GoHome();
7: }
Add a toolbar button for the menu ID IDM_GO_START.

3. Disable the Stop toolbar button and menu entry when the application is not downloading a Web page.

Using the Class Wizard, add an event handler to the view class for the IDM_VIEW_STOP object ID on the UPDATE_COMMAND_UI event message. Edit the function, adding the code in Listing B.39.

LISTING B.39. THE CWebBrowseView OnUpdateViewStop FUNCTION.

1: void CWebBrowseView::OnUpdateViewStop(CCmdUI* pCmdUI)
2: {
3:     // TODO: Add your command update UI handler code here
4:
5:     // Enable the button if busy
6:     pCmdUI->Enable(GetBusy());
7: }


Previous chapterNext chapterContents

© Copyright, Macmillan Computer Publishing. All rights reserved.