
 |
|
Microsoft® Mastering: MFC Development Using Microsoft Visual C++® 6.0
|
|
 |
Author |
 |
Microsoft Corporation
|
 |
|
Pages |
624
|
|
Disk |
1 Companion CD(s)
|
|
Level |
Intermediate
|
|
Published |
02/23/2000
|
|
ISBN |
9780735609259
|
|
ISBN-10 |
0-7356-0925-X
|
|
Price(USD) |
$49.99
To see this book's discounted price, select a reseller below.
|
|
|
|
|
 |
|
|
Chapter 4: Implementing View Classes (continued)
Lab 4.3 (Optional): Building a Text Viewer
In this lab, you will implement a text viewer and control fonts for on-screen display.
To see the demonstration "Lab 4.3 Demonstration," see the accompanying CD-ROM.
Estimated time to complete this lab: 30 minutes
To complete the exercises in this lab, you must have the required software. For detailed information about the labs and setup for the labs, see "Labs" in "About This Course."
The solution code for this lab is located in the folder <install folder>\Labs\Ch04\Lab4.3. Objectives
After completing this lab, you will be able to:
- Implement supporting code for a viewer.
- Display streams of text.
- Control fonts for on-screen display.
Prerequisites
Before working on this lab, you should be familiar with the following:
- Member functions of CDC class
You may find it helpful to review the MSDN Library Visual Studio 98 Help topics on graphics before attempting this lab. Exercises
The following exercises provide practice working with the concepts and techniques covered in this chapter:
- Exercise 1: Implementing a Basic Text Viewer
In this exercise, you will add text display capability to a viewer.
- Exercise 2: Adding Font Support
In this exercise, you will implement a user interface for the text viewer you created in Exercise 1.
Exercise 1: Implementing a Basic Text Viewer
In this exercise, you will add text display capability to a viewer based on CScrollView. There are four parts to this exercise:
- Creating a multiple-document interface (MDI) application with MFC AppWizard
- Adding file-handling to the document
- Calculating the basic metrics of the view
- Displaying the text
At the end of this exercise, you will have created an MDI text viewer with file selection, display, and scrolling.
Use MFC AppWizard to create an MDI application
- Name the new project workspace Text.
- In Step 6, derive CTextView from CScrollView, rather than CView.
Finish and create the new project. Build it at this point.
In the Text.exe file, CTextDoc is little more than a holder for CStringList, into which you will read the lines of a selected file. CDocument provides the menu and the File Open dialog box. You will provide the file-handling code and the parsing of the file into CStringList.
Prepare CTextDoc for file reading
- Open the file TextDoc.h.
- Declare a protected CStringList member:
CStringList m_LineList;
- Declare a public member function to return a pointer to m_LineList:
CStringList* GetLineList() { return &m_LineList; }
- Save TextDoc.h.
- CTextDoc::OnNewDocument is called when the application starts and when a user action occurs. Because Text.exe is a read-only application, disable this function in the file TextDoc.cpp:
BOOL CTextDoc::OnNewDocument() { return FALSE; }
Implement OnOpenDocument
- CTextDoc::OnOpenDocument will read the selected text file, line by line, into the CStringList member. OnOpenDocument is called from the application after CTextApp has queried the user with a File Open dialog box. Create CTextDoc::OnOpenDocument from ClassWizard or WizardBar. Edit the code to remove the default handler.
- Reading a file into memory one line at a time could take a while. Show the wait cursor with CCmdTarget::BeginWaitCursor:
BeginWaitCursor();
- Clear all the items from m_LineList:
m_LineList.RemoveAll();
- CStdioFile provides stream-oriented file access with line-oriented file access. Open the file passed in lpszPathName by constructing a CStdioFile object as follows:
CStdioFile file(lpszPathName, CFile::modeRead | CFile::typeText);
- Declare a CString variable into which to read each line as follows:
CString strLine;
- CStdioFile::ReadString returns TRUE if anything was read and FALSE if the end of the file was encountered before reading any data. Read data as long as there is anything to read:
while (file.ReadString(strLine) != NULL) {
- Clean up the ends of the lines for white space and control characters as follows:
int nLastCharIndex = strLine.GetLength()-1; while (nLastCharIndex >= 0 && strLine[nLastCharIndex] < ' ') { strLine.SetAt(nLastCharIndex, '\0'); }
- Once the string is clean, add it to the end of m_LineList as follows:
m_LineList.AddTail(strLine);
- At the end of the read loop, restore the cursor as follows:
EndWaitCursor();
- Return TRUE to indicate that you have handled the message:
return TRUE;
- Save TextDoc.cpp and TextDoc.h.
The following sample code shows an example of how your code should look. To copy this code for use in your own projects, see "Lab 4.3.1 OnOpenDocument" on the accompanying CD-ROM.
BOOL CTextDoc::OnOpenDocument(LPCTSTR lpszPathName) { // Could be a big file BeginWaitCursor(); // Clear List, this will cleanup the CString objects m_LineList.RemoveAll(); // Read the file and store as a list // of CStrings CStdioFile file(lpszPathName, CFile::modeRead | CFile::typeText);
CString strLine; while (file.ReadString(strLine) != NULL) { //remove the noise characters at the end of the line int nLastCharIndex = strLine.GetLength()-1; while (nLastCharIndex >= 0 && strLine[nLastCharIndex] < ' ') { strLine.SetAt(nLastCharIndex, '\0'); } // Add to CStringList m_LineList.AddTail(strLine); } EndWaitCursor(); return TRUE; }
Declare the basic metrics members
You will need to have a number of basic metrics for the text view. These do not need to be calculated each time you draw text on the screen if you hold them in member variables.
- Right-click CTextView in ClassView and add the following protected variables:
CSize m_ViewCharSize CSize m_DocSize CFont* m_pFont
- Right-click CTextView in ClassView and declare a public function as follows:
CFont* GetFont()
- Right-click CTextView in ClassView and declare a protected function as follows:
void ComputeViewMetrics()
- Add public member functions manually to the file CTextView.h as follows:
CSize GetDocSize() const { return m_DocSize; } CSize GetCharSize() const { return m_ViewCharSize; }
- Open the file TextView.cpp.
- Initialize m_ViewCharSize, m_DocSize, and m_pFont in the constructor. The constructor looks like the following:
CTextView:: CTextView() : m_ViewCharSize(0,0), m_DocSize(0,0) { m_pFont = NULL; }
- Save TextView.cpp.
Get the current font
- In TextView.cpp, define the GetFont function as follows:
CFont* CTextView::GetFont()
- If no font has been created, construct a new font as follows:
if(m_pFont == NULL) { m_pFont = new Cfont;
- Create a nine-point Arial font in m_pFont as follows:
if(m_pFont) { // Default to 9 pt Arial m_pFont->CreatePointFont(90, "Arial"); }
- Return m_pFont. Save TextView.cpp.
The sample code on the following page shows an example of how your code should look. To copy this code for use in your own projects, see "Lab 4.3.1 GetFont" on the accompanying CD-ROM.
CFont * CTextView::GetFont() { if(m_pFont == NULL) { m_pFont = new CFont; if(m_pFont) { // Default to 9 pt Arial m_pFont->CreatePointFont(90, "Arial"); } } return m_pFont; }
Compute the basic metrics for the view by defining ComputeViewMetrics
- Get the pointer to the screen device context (DC) and save the state of the DC as follows:
CDC* pDC = CDC::FromHandle(::GetDC(NULL)); int nSaveDC = pDC->SaveDC();
- Set the mapping mode to MM_LOENGLISH as follows:
pDC->SetMapMode(MM_LOENGLISH);
- Select the display font into the DC and get the font’s text metrics as follows:
CFont* pPreviousFont = pDC->SelectObject(GetFont()); TEXTMETRIC tm; pDC->GetTextMetrics(&tm);
- Calculate the height of a font element (character) as the sum of its internal height (tmHeight) and the space between lines (tmExternalLeading):
m_ViewCharSize.cy = tm.tmHeight + tm.tmExternalLeading; m_ViewCharSize.cx = tm.tmAveCharWidth;
- Get a pointer to a document so you can access the CStringList member that holds the data:
CTextDoc* pDoc = GetDocument();
- Initialize the document width to 0. To calculate the document height, multiply the number of lines by the height of a line:
m_DocSize.cx = 0; m_DocSize.cy = m_ViewCharSize.cy * pDoc->GetLineList()->GetCount();
- The longest line has to be calculated by looking at each line of the document using the current font. Declare variables for a loop to interrogate each line as follows:
CString Line; CSize size;
- Because CStringList is a collection with an iterator, you will use a POSITION pointer to iterate through the list:
POSITION pos = pDoc->GetLineList()->GetHeadPosition(); while( pos != NULL )
- Get the current line, and from it get its text extent as follows:
Line = pDoc->GetLineList()->GetNext( pos ); size = pDC->GetTextExtent(Line, Line.GetLength());
- Set the width of the document to the largest size found as follows:
m_DocSize.cx = max(size.cx, m_DocSize.cx);
- After the loop is closed, add a four-pixel margin as follows:
m_DocSize.cx += 4 * m_ViewCharSize.cx;
- Select the application font out of the DC as follows:
if(pPreviousFont) { pDC->SelectObject(pPreviousFont); }
- Restore the DC to its original state as follows:
pDC->RestoreDC(nSaveDC);
- Release the DC as follows:
::ReleaseDC(NULL, pDC->GetSafeHdc());
- Save TextView.cpp and TextView.h.
The following sample code shows an example of how your code should look. To copy this code for use in your own projects, see "Lab 4.3.1 ComputeViewMetrics" on the accompanying CD-ROM.
void CTextView::ComputeViewMetrics() { // get a CDC* for the screen CDC* pDC = CDC::FromHandle(::GetDC(NULL)); int nSaveDC = pDC->SaveDC(); // select mapping mode pDC->SetMapMode(MM_LOENGLISH); // select the font and get its metrics CFont* pPreviousFont = pDC->SelectObject(GetFont()); TEXTMETRIC tm; pDC->GetTextMetrics(&tm); // Calculate view character size m_ViewCharSize.cy = tm.tmHeight + tm.tmExternalLeading; m_ViewCharSize.cx = tm.tmAveCharWidth; // convert to device units to minimize round off error pDC->LPtoDP(&m_ViewCharSize); // Calculate document size CTextDoc* pDoc = GetDocument(); m_DocSize.cy = m_ViewCharSize.cy * pDoc->GetLineList()->GetCount();
// loop through the document and find the longest line CString Line; CSize size; POSITION pos = pDoc->GetLineList()->GetHeadPosition(); while( pos != NULL ) { Line = pDoc->GetLineList()->GetNext( pos ); size = pDC->GetTextExtent(Line, Line.GetLength()); m_DocSize.cx = max(size.cx, m_DocSize.cx); }
// Account for our simple margin m_DocSize.cx += 4 * m_ViewCharSize.cx; // clean up if(pPreviousFont) { pDC->SelectObject(pPreviousFont); } pDC->RestoreDC(nSaveDC); ::ReleaseDC(NULL,pDC->GetSafeHdc()); }
Implement the OnDraw function
In the OnDraw function, you need to calculate the number of lines that can fit into the window and paint only those lines. In addition, you will need to process the OnUpdate message that is sent when the view window is resized.
- Move to the top of CTextView::OnDraw. Delete the contents of the function and declare variables to store the first and last lines of the visible text:
int nFirstLn, nLastLn;
- Calculate the lines to draw by calling ComputeVisibleLines as follows:
ComputeVisibleLines(pDC, nFirstLn, nLastLn);
- Calculate the position of the first line relative to the origin of the window as follows:
int nYPos = - nFirstLn * GetCharSize().cy; int nXPos = 4 * GetCharSize().cx;
- Call the core OnDraw handler as follows:
OnDraw(pDC, nFirstLn, nLastLn,nXPos,nYPos);
- Save TextView.cpp.
The following sample code shows an example of how your code should look. To copy this code for use in your own projects, see "Lab 4.3.1 OnDraw" on the accompanying CD-ROM.
void CTextView::OnDraw(CDC* pDC) { int nFirstLn, nLastLn; ComputeVisibleLines(pDC, nFirstLn, nLastLn); int nYPos = - nFirstLn * GetCharSize().cy; int nXPos = 4 * GetCharSize().cx; OnDraw(pDC, nFirstLn, nLastLn,nXPos,nYPos); }
Implement the ComputeVisibleLines function
- Right-click CTextView in ClassView and add ComputeVisibleLines as a protected function:
void ComputeVisibleLines(CDC* pDC, int& nFirst, int& nLast)
- Begin writing the code for the ComputeVisibleLines function by getting the number of lines in the CStringList as folllows:
int nLineCount = GetDocument()->GetLineList()->GetCount();
- Get the viewport origin in logical coordinates as follows:
CPoint pt = pDC->GetViewportOrg(); pDC->DPtoLP(&pt,1);
- Get the clipping region in logical coordinates as follows:
CRect rc; pDC->GetClipBox(&rc);
- Get the line height as follows:
CSize CharSize = GetCharSize();
- The algorithm for the first visible line accomplishes these points:
- Calculates the distance from the top of the viewport to the top of the clipping region.
- Divides this distance by the height of a line, giving the number of lines.
- Ensures that at least one line will be shown.
The code is written as follows:
nFirst = min(abs((rc.top - pt.y)/CharSize.cy), nLineCount-1);
- The algorithm for the last visible line accomplishes these points:
- Calculates the number of lines that will fit into the clipping region.
- Adds that to the starting line.
- Adds one more line to make sure that partial lines are displayed.
- Ensures that this is less than the total number of lines.
The code is written as follows:
nLast = min(abs(rc.Height())/CharSize.cy + nFirst + 1, nLineCount-1);
- Save TextView.cpp.
The following sample code shows an example of how your code should look. To copy this code for use in your own projects, see "Lab 4.3.1 ComputeVisibleLines" on the accompanying CD-ROM.
void CTextView::ComputeVisibleLines(CDC* pDC, int& nFirst, int& nLast) { int nLineCount = GetDocument()->GetLineList()->GetCount(); // Get the viewport origin, convert to logical coordinates CPoint pt = pDC->GetViewportOrg(); pDC->DPtoLP(&pt,1);
// Get the clipping region, in logical coordinates CRect rc; pDC->GetClipBox(&rc); // Get the logical line height CSize CharSize = GetCharSize(); // Compute the first visible line nFirst = min(abs((rc.top - pt.y)/CharSize.cy), nLineCount-1); // Compute the last visible line nLast = min(abs(rc.Height())/CharSize.cy + nFirst + 1, nLineCount-1); }
Implement the core OnDraw handler
- Declare a second OnDraw handler in TextView.h as follows:
virtual void OnDraw(CDC* pDC, int nFirstLn, int nLastLn, int nXPos = 0, int nYPos = 0);
- Define the second OnDraw handler in TextView.cpp as follows:
void CTextView::OnDraw(CDC* pDC, int nFirstLn, int nLastLn, int nXPos /*= 0*/, int nYPos /*= 0*/)
- Select your chosen font into the DC as follows:
CFont* pPreviousFont = pDC->SelectObject(GetFont());
- Get the size of the font as follows:
CSize CharSize = GetCharSize();
- Get the string list from the document as follows:
CStringList *pLineList = GetDocument()->GetLineList();
- You will loop through the lines in pLineList from the first line passed (which will be an index) to the last line passed, drawing the text on the screen and moving down the screen (that is, to lower y-coordinate values). Declare the necessary variables:
CString strLine; POSITION pos;
- Control the loop as follows:
while (nFirstLn <= nLastLn)
- Find the item in the list as follows:
if( ( pos = pLineList->FindIndex( nFirstLn )) != NULL )
- If you have a valid item, copy it to the string and display it as follows:
strLine = pLineList->GetAt(pos); pDC->TabbedTextOut(nXPos, nYPos, strLine, 0, NULL, 0);
- Decrement the y-coordinate and increment the line count as follows:
nYPos -= CharSize.cy; nFirstLn++;
- Back outside the loop, select your font out of the DC:
if(pPreviousFont) { pDC->SelectObject(pPreviousFont); }
- Save TextView.cpp.
The sample code on the following page shows an example of how your code should look. To copy this code for use in your own projects, see "Lab 4.3.1 core OnDraw" on the accompanying CD-ROM.
void CTextView::OnDraw(CDC* pDC, int nFirstLn, int nLastLn, int nXPos /*= 0*/, int nYPos /*= 0*/) { // Select specified font CFont* pPreviousFont = pDC->SelectObject(GetFont()); // Needed for height of each line CSize CharSize = GetCharSize(); // Get list of strings from the document // and output them to the display context CStringList *pLineList = GetDocument()->GetLineList(); CString strLine; POSITION pos; while (nFirstLn <= nLastLn) { if( ( pos = pLineList->FindIndex( nFirstLn )) != NULL ) { strLine = pLineList->GetAt(pos); pDC->TabbedTextOut(nXPos, nYPos, strLine, 0, NULL, 0); nYPos -= CharSize.cy; nFirstLn++; } } // Cleanup and restore original GDI Objects if(pPreviousFont) { pDC->SelectObject(pPreviousFont); } }
Implement OnUpdate
- Using ClassWizard, delete CTextView::OnInitialUpdate and manually remove the associated code.
- Using ClassWizard or WizardBar, add CTextView::OnUpdate. Edit the code to compute the view metrics as follows:
ComputeViewMetrics();
- CScrollView::SetScrollSizes sets the mapping mode for the scroll view. Because CTextView::ComputeViewMetrics uses MM_LOENGLISH for its calculations, you will need to use that mode here. It also sets the scrolling ranges. Get these ranges from the document size:
SetScrollSizes( MM_LOENGLISH, GetDocSize());
- CView::OnUpdate is sent whenever the view window is changed. Invalidate the window so that CTextView::OnDraw will be called to appropriately redisplay the text:
Invalidate();
- Save TextView.cpp.
The following sample code shows an example of how your code should look. To copy this code for use in your own projects, see "Lab 4.3.1 OnUpdate" on the accompanying CD-ROM.
void CTextView::OnUpdate(CView* pSender, LPARAM lHint, CObject* pHint) { ComputeViewMetrics(); SetScrollSizes(MM_LOENGLISH, GetDocSize()); Invalidate(); }
- Save TextView.h and TextView.cpp.
- Build your application.
The solution code for this exercise is located in the folder <install folder>\Labs\Ch04\Lab4.3\Ex01\Solution.
Exercise 2: Adding Font Support
Continue with the files you created in Exercise 1 or, if you do not have a starting point for this exercise, the code that forms the basis for this exercise is in <install folder>\Labs\Ch04\Lab4.3\Ex02.
In this exercise, you will implement a user interface to CTextView.m_pFont.
Add a menu item for Font
- Open the IDR_TEXTTYPE menu resource.
- Add a menu between the View and Window menus. Give it the caption Font.
- Add a menu item below the font menu, giving it the ID ID_FORMAT_FONT and the prompt, Change Font.
- Save the file Text.rc.
Add a handler for the menu message
- Use ClassWizard or WizardBar. Add a handler for ID_FORMAT_FONT to CTextView, and accept the default OnFormatFont function name.
Implement CTextView::OnFormatFont
- Go to the head of OnFormatFont. Get (or create, if it has not yet been created) the current font with GetFont as follows:
CFont * pFont = GetFont();
- Retrieve a LOGFONT structure with the font information as follows:
LOGFONT lf; pFont->GetObject(sizeof(LOGFONT), &lf);
- Use this structure to initialize a common font dialog box as follows:
CFontDialog dlg(&lf, CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT);
- Show the dialog box modally:
if(dlg.DoModal() == IDOK)
- If the user closed the font dialog box with OK, delete the current font (remember that this font is selected out of a device context (DC) each time it is selected in, so this deletion is safe):
if(m_pFont) { delete m_pFont; }
- Construct the new font and initialize it using the LOGFONT structure returned from CFontDialog as follows:
m_pFont = new CFont; if(m_pFont) { m_pFont->CreateFontIndirect(&lf); }
- Finally, you must recalculate all the metrics and invalidate the view’s window to redisplay the file using the new font. You already have a function that does this, OnUpdate, but it will take some significant amount code to set up the call. The simplest way to accomplish this is to call CDocument::UpdateAllViews. Because there is only one view of this document, this will be the equivalent of calling OnUpdate directly:
GetDocument()->UpdateAllViews(NULL);
- Save TextView.cpp.
The following sample code shows an example of how your code should look. To copy this code for use in your own projects, see "Lab 4.3.2 OnFormatFont" on the accompanying CD-ROM.
void CTextView::OnFormatFont() { CFont * pFont = GetFont(); LOGFONT lf; pFont->GetObject(sizeof(LOGFONT), &lf); CFontDialog dlg(&lf, CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT);
if(dlg.DoModal() == IDOK) { if(m_pFont) { delete m_pFont; } m_pFont = new CFont; if(m_pFont) { m_pFont->CreateFontIndirect(&lf); } // This will cause OnUpdate() to be called ensuring // that our cached metrics and scrolling get updated GetDocument()->UpdateAllViews(NULL); } }
Build and test your application
- Run your application and open a file. The view class supports scrolling and changes of fonts. Select the font option to change the text font.
The solution code for this exercise is located in the folder <install folder>\Labs\Ch04\Lab4.3\Ex02\Solution.
Sample Applications
The following table describes the sample applications related to this chapter. These sample applications are located in the folder <install folder>\Samples\Ch04.
| Sample application subfolder
| Description of application
|
| Mdiapp | Shows how document class tracks all views attached to that class. |
| SDI2VIEWS | An SDI application with two views available. User can toggle between those two views at run time. |
| MDI2VIEWS | Shows how to implement an MDI application with two views of its data. Defaults at startup to one view, and the user can choose another from a menu selection. |
| MDI2VIEWSB | Similar to MDI2VIEWS, except that the user is asked when the application starts, and when opening a new document, which view to use. |
| List | Implements an SDI application with its view based on CListView. Shows how to populate a report-style list control. |
| Form | Implemented using CFormView as the base class for the view. Also shows how to change text color in an edit box. |
| Scroll | An SDI application with CScrollView as its base class. Shows how to adjust the frame to fit the view. Also shows how converting device points to logical points. |
| Splitter | An SDI application with a static splitter bar and different views in each pane. CMainFrame::OnCreateClient also contains commented source code for a dynamic splitter. |
| Tmpgraph | An SDI application with a static splitter bar and different views in each pane. Shows the document/view concept of different views of data. Uses persistence. Demonstrates minimal graphing capabilities. |
| Treewimg | An SDI application that uses CTreeView as the base for its view class. Shows how to create a CImageList and attach it to a tree control. Also shows how to populate a tree control. |
| Treelist | An SDI application that shows how to build an application with an interface like Windows Explorer: a vertical splitter and a tree view in its left pane and list view in its right pane. |
Self-Check Questions
To see the answers to the Self-Check Questions, see Appendix A.
- What is the relationship between views and other framework classes?
- A view is owned by the mainframe object.
- A view is embedded in its corresponding document object.
- A view is refreshed by the mainframe object.
- A view displays the contents of a document object.
- How do you enhance an existing application to support a scrolling view?
- By adding code to the OnInitialUpdate function
- By setting the base class of the view to CScrollView
- By calling CView::SetScrollSizes
- By adding a scrollbar object to the view
- What differentiates a dynamic splitter window from a static splitter window?
- Static splitters cannot have different views associated with each pane, whereas dynamic splitters can.
- Dynamic splitters have no maximum number of panes, but static splitters are limited to 256 panes.
- You must declare a CDynSplitterWnd object for a dynamic splitter and a CSplitterWnd object for static splitters.
- Dynamic splitter windows can be destroyed at run time, whereas static splitter windows cannot.
- Which property name-value pairs is set by AppWizard for a form view?
- Style: Popup
- Border: None
- Titlebar: On
- System Modal: On
- Which class is used as the base class for control views?
- CControlView
- CCtrlView
- CListView
- CView
Previous
| Table of Contents
|
Next
Visit Microsoft Press
for more information on Microsoft® Mastering: MFC Development Using Microsoft Visual C++® 6.0
Last Updated: Friday, July 6, 2001 |