6.3 Add Message Handlers for File Menu Commands

This section explains the fifth step in writing Phone Book: add message-handler member functions to the CMainWindow class for File menu commands. Figure 6.1 shows the Phone Book File menu.

·To add File menu message-handler functions:

1.Add the following OnNew message-handler member function to CMainWindow section of your VIEW.CPP file. If you want your file to resemble Listing 2 in Chapter 5, add OnNew just below the CMainWindow message map:

// CMainWindow::OnNew

// After checking to see if current data needs to be stored, call

// database New and resize/repaint the window.

//

void CMainWindow::OnNew()

{

if( !CheckForSave( "File New", "Save file before New?" ) )

return; m_people.New();

SetMenu();

SetWindowText( m_people.GetTitle() );

OnSize( 0, m_cxClient, m_cyClient );

}

OnNew creates a new, empty database, to which the user can add persons. OnNew calls a SetMenu utility member function. You'll add that function later in the chapter.

2.Add the following OnOpen message-handler member function to the CMainWindow section of your VIEW.CPP file below the OnNew member function:

// CMainWindow::OnOpen

//

void CMainWindow::OnOpen()

{

if( !CheckForSave( "File Open", "Save file before Open?" ) )

return;

// Attempt to open a database file and read it.

// If a file or archive exception occurs, catch it and

// present an error message box.

CString szFileName, szFileTitle;

TRY

{

// Use CommDlg to get the filename and then call DoOpen.

// Set the Window title and menus. Resize/Repaint.

if ( FileDlg( TRUE, SIZESTRING, szFileName.GetBuffer

( SIZESTRING ), SIZESTRING,

szFileTitle.GetBuffer( SIZESTRING ) ) )

{

szFileName.ReleaseBuffer();

szFileTitle.ReleaseBuffer();

m_people.DoOpen( szFileName );

m_people.SetTitle( szFileTitle );

SetWindowText( m_people.GetTitle() );

SetMenu();

OnSize( 0, m_cxClient, m_cyClient );

}

}

CATCH( CFileException, e )

{

char ErrorMsg[25];

sprintf( ErrorMsg,"Opening %s returned a 0x%lx.",

(const char*)szFileTitle, e -> m_lOsError );

MessageBox( ErrorMsg, "File Open Error" );

}

AND_CATCH( CArchiveException, e )

{

char ErrorMsg[25];

sprintf( ErrorMsg,"Reading the %s archive failed.",

(const char*)szFileTitle );

MessageBox( ErrorMsg, "File Open Error" );

}

END_CATCH

}

OnOpen opens an existing database file and deserializes the data into the current database. OnOpen calls a utility member function FileDlg to put up a standard Windows file open dialog box. You'll add that utility function later in the chapter.

3.Add the following OnSave message-handler member function to the CMainWindow section of your VIEW.CPP file below the OnOpen member function:

void CMainWindow::OnSave()

{

Save( m_people.IsNamed() );

}

OnSave responds to the Save command in the File menu to serialize the current database to a disk file. Save is a utility member function called by a number of other member functions. It is not a message-handler function. You'll learn what code to add for Save later in the chapter.

4.Add the following OnSaveAs message-handler member function to the CMainWindow section of your VIEW.CPP file below the OnSave member function:

void CMainWindow::OnSaveAs()

{

Save();

}

OnSaveAs responds to the Save As command in the File menu. It simply calls the Save utility member function, which you'll add to the code later.

1.Add the following Save member function to the CMainWindow section of VIEW.CPP. To keep your file in the same order as Listing 2 in Chapter 5, add Save to the end of the file. It's one of the group of utility member functions, which will be kept together at the end of the file.

// CMainWindow::Save

// Handles any time a file needs to be saved to the disk.

// Passing in FALSE for name brings up the file save as dialog

// whether or not the database had a name before.

//

BOOL CMainWindow::Save( BOOL bIsNamed /* = FALSE */ )

{

CString szFileName, szFileTitle;

TRY

{

if ( bIsNamed )

m_people.DoSave();

else

{

szFileName = m_people.GetName();

if ( FileDlg( FALSE, SIZESTRING,

szFileName.GetBuffer( SIZESTRING ), SIZESTRING,

szFileTitle.GetBuffer( SIZESTRING ) ) )

{

szFileName.ReleaseBuffer();

m_people.DoSave( szFileName );

m_people.SetTitle( szFileTitle );

SetWindowText( m_people.GetTitle() );

}

else

return FALSE;

}

}

CATCH( CFileException, e )

{

char ErrorMsg[25];

sprintf( ErrorMsg,"Saving %s returned a 0x%lx.",

(const char*)szFileTitle, e -> m_lOsError );

MessageBox( ErrorMsg, "File Open Error" );

}

AND_CATCH( CArchiveException, e )

{

char ErrorMsg[25];

sprintf( ErrorMsg,"Reading the %s archive failed.",

(const char*)szFileTitle );

MessageBox( ErrorMsg, "File Open Error" );

}

END_CATCH

return TRUE;

}

Although Save is a member function of class CMainWindow, it is not a
message-handler function. It's a utility member function called by other member functions when the current database needs to be saved. In particular, Save is used to implement both OnSave and OnSaveAs.

2.Add the following OnDBClose message-handler member function to the CMainWindow section of VIEW.CPP below the OnSaveAs member function:

// CMainWindow::OnDBClose

// Closes the current database, checking to see if it should be

// saved first. Reset the window title and the scroll regions.

// Invalidating the entire screen causes OnPaint to repaint but

// this time without any data.

//

void CMainWindow::OnDBClose()

{

if( !CheckForSave( "File Close", "Save file before closing?" ) )

return; m_people.Terminate();

SetWindowText( "Phone Book" );

SetMenu();

OnSize( 0, m_cxClient, m_cyClient );

}

OnDBClose is called in response to the Close command in the File menu. It handles closing the current database and adjusting the window to reflect that no database is open. The similarly named OnClose member function, which you'll add next, is called during the termination process in response to the Exit command in the File menu.

3.Add the following OnClose message-handler member function to the CMainWindow section of your VIEW.CPP file. To keep your file in the same order as Listing 2 in Chapter 5, add OnClose just above the Save member function that you added earlier.

// CMainWindow::OnClose

// Check to see if the current file needs to be saved. Terminate

// the database and destroy the window.

//

void CMainWindow::OnClose()

{

if( !CheckForSave( "File Exit", "Save file before exit?" ) )

return; m_people.Terminate();

DestroyWindow();

}

Where OnDBClose simply closes the current database, OnClose also destroys and removes the window. Because of this action, OnClose is placed in the file with “Creation and Sizing” member functions, which you'll add later.

4.Add the following OnExit message-handler member function to the CMainWindow section of your VIEW.CPP file below the OnDBClose member function:

// CMainWindow::OnExit

//

void CMainWindow::OnExit()

{

OnClose();

}

OnExit responds to the Exit command in the File menu. It simply calls the
OnClose
member function to clean up in case there's an open database.
OnClose also destroys the window and removes it from the screen. The same mechanism is used to process both a system WM_CLOSE message and an Exit command from the menu. The same cleanup is required in both instances.

5.Add the following OnPrint message-handler member function to the CMainWindow section of your VIEW.CPP file below the OnDBClose member function and above OnExit. This order keeps OnPrint in the same sequence as Listing 2 in Chapter 5 and in the sequence of File menu commands.

// CMainWindow::OnPrint

// Uses the commdlg print dialog to create a printer dc

// Then it uses code almost identical to the OnPaint code

// to write the data to the printer.

//

void CMainWindow::OnPrint()

{

PRINTDLG pd;

pd.lStructSize = sizeof( PRINTDLG );

pd.hwndOwner=m_hWnd;

pd.hDevMode=(HANDLE)NULL;

pd.hDevNames=(HANDLE)NULL;

pd.Flags=PD_RETURNDC | PD_NOSELECTION | PD_NOPAGENUMS;

pd.nFromPage=0;

pd.nToPage=0;

pd.nMinPage=0;

pd.nMaxPage=0;

pd.nCopies=1;

pd.hInstance=(HANDLE)NULL;

if ( PrintDlg( &pd ) != 0 )

{

// CommDlg returned a DC so create a CDC object from it.

ASSERT( pd.hDC != 0 );

CDC * dc;

dc = CDC::FromHandle( pd.hDC );

// Change to hourglass while printing

SetCursor( AfxGetApp() -> LoadStandardCursor( IDC_WAIT ) );

// Begin printing the document.

int rc;

char szError[50];

rc = dc -> StartDoc( "Phone Book" );

if ( rc < 0 )

{

sprintf( szError, "Unable to Begin printing - Error[%d]",

rc );

MessageBox( szError, NULL,MB_OK );

return;

}

int x, y;

CPerson* pCurrent;

UINT nPerson=0;

CString szDisplay;

int nStart, nEnd;

// Get Height and Width of large character

CSize extentChar = dc -> GetTextExtent( "M", 1 );

int nCharHeight = extentChar.cy;

int nCharWidth = extentChar.cx;

// Get Page size in # of full lines

UINT nExtPage = ( dc -> GetDeviceCaps(VERTRES) - nCharHeight )

/ nCharHeight;

CString szTitle;

szTitle = CString( "Phone Book - " ) + m_people.GetName();

while ( nPerson != m_people.GetCount() )

{

// Print a Page Header

dc -> StartPage();

dc -> SetTextAlign ( TA_LEFT | TA_TOP );

dc -> TextOut( 0, 0, szTitle, szTitle.GetLength() );

dc -> MoveTo( 0, nCharHeight );

dc -> LineTo( dc -> GetTextExtent( szTitle,

szTitle.GetLength() ).cx, nCharHeight );

// Print People from start to last person or page size

// minus 2 ( header size )

nEnd = min( m_people.GetCount() - nPerson, nExtPage-2 );

for ( nStart = 0; nStart < nEnd; nStart++, nPerson++ )

{

x = 0;

y = nCharHeight * ( nStart+2 );

pCurrent = m_people.GetPerson( nPerson );

szDisplay = " " + pCurrent -> GetLastName() + ", " +

pCurrent -> GetFirstName();

dc -> SetTextAlign( TA_LEFT | TA_TOP );

dc -> TextOut( x, y, szDisplay,

szDisplay.GetLength() )

szDisplay = pCurrent -> GetPhoneNumber();

dc -> SetTextAlign( TA_RIGHT | TA_TOP );

dc -> TextOut( x + SIZENAME * nCharWidth, y,

szDisplay, szDisplay.GetLength() );

szDisplay = pCurrent -> GetModTime().Format(

"%m/%d/%y %H:%M" );

dc -> TextOut( x + ( SIZENAME + SIZEPHONE ) *

nCharWidth, y,

szDisplay, szDisplay.GetLength() );

}

dc -> EndPage();

}

dc -> EndDoc();

dc -> DeleteDC();

SetCursor( AfxGetApp() -> LoadStandardCursor( IDC_ARROW ) );

}

}

OnPrint prints the current database in response to the Print command in the File menu. It calls the PrintDlg function defined in COMMDLG.DLL to put up a standard Windows print dialog.

To continue the tutorial, see “Add Message Handlers for Person Menu Commands” on page 216. For more information on the handlers you just added, see the discussion below, “Discussion: File Menu Message Handlers.”