This article shows how one can read text files from the disk and print them page by page. While printing, on each page we would print a header consisting of filename and a footer containing page number. Figure 1 displays one page of the document in print preview mode.
Figure 1. A page in print preview mode.
In this article we have managed drawing on the screen using OnDraw( ) and printing on the printer using OnPrint( ). This makes the code more straightforward. It also emphasizes the fact that MFC doesn’t make it compulsory for programmers to do printing and painting in OnDraw( ) itself.
To be able to print a file we must first load it from the disk into memory. This is achieved using ‘File | Open’ option. On selecting this option a menu handler mydoc::openfile( ) gets called. In this function we have popped up a dialog shown in Figure 2.
There are two ways in which we can specify the file to be opened:
- We can supply the name of the file in the edit box of the dialog.
- We can select the ‘Browse’ button to popup a ‘File’ dialog. The user can now navigate through the various directories and then select the file that he wishes to open. The selected filename is displayed in the edit box of dialog shown in Figure 2.
On clicking OK, the dialog is dismissed and the file is opened using CStdioFile constructor. Once the file is opened we must read it line by line. This has been achieved using the mydoc::openfile( ) function as shown below:
void mydoc::openfile( )
mydialog d ( IDD_DIALOG1 ) ;
if ( d.DoModal( ) == IDOK )
CStdioFile fr ( d.filepath_str, CFile::modeRead ) ;
filelines_str.RemoveAll( ) ;
CString str ;
while ( fr.ReadString ( str ) )
filelines_str.AddTail ( str ) ;
SetTitle ( d.filename_str ) ;
UpdateAllViews ( NULL ) ;
}POSITION pos = GetFirstViewPosition( ) ;
myview *p = ( myview * ) GetNextView ( pos ) ;
p -> setview( ) ;
Here, each line from the file is read into a CString object. This object is then added to a list of CString objects maintained using a CStringList object. To carry out the addition we have used the function CStringList::AddTail( ). Once the file is loaded into memory we must now make a provision to display it on the screen and to print it on the printer. To this effect we have overridden several CView virtual functions. Table 1 displays a list of the functions, the purpose of each and whether it aids in printing or painting.
Called when view first gets attached to a document. Creates font, obtains text metrics and sets scroll sizes.
Compulsory to override. Contains logic to display file contents in the view.
Calls DoPreparePrinting( ) which pops up the ‘Print’ dialog.
Called just before printing begins. Creates fonts, obtains text metrics, sets maximum page limit and prepares a list of strings to be printed on the printer.
Called before each page is printed. Prints header, page body and footer.
Called when printing is finished. Deletes font and deallocates list of strings.
Table 1. Overridden CView virtual functions.
As in any Doc/View application, here too, painting has been done through myview::OnDraw( ) function. While displaying the file contents in myview::OnDraw( ) there are two things that we should take care of:
- The view window should be big enough to accommodate all the lines in the file. This has a direct bearing on determining the scroll sizes to be set.
- Deciding the y-coordinate of every line being displayed in the view window. The y-coordinate and the scroll sizes would vary according to the font and the actual contents of the file. For example, the scroll sizes for a file containing 100 lines, a maximum line length of 150 characters being displayed in 10-point, ‘Arial’ font would be different if any of these three parameters change. For the sake of simplicity we have kept the font fixed—10-point, ‘Arial’ font. No assumptions can however be made about the number of lines in the file as well as the maximum line length.
We have done the creation of font in myview::OnInitialUpdate( ) function. To create a font we have used the CFont::CreatePoint-Font( ) function. Note that this function is more direct and compact as compared to the CFont::CreateFont( ) function. Had we used CreateFont( ) we would have been first required to calculate the height of the characters corresponding to the 10-point, ‘Arial’ font. This height would then have been passed to CreateFont( ). As against this, we can pass point size multiplied by 10 to CreatePointFont( ) to create a font of 10 points.
Once the font is created it is selected into the device context. Next, the height and width of the characters is determined in logical units using CClientDC::GetTextMetrics( ). Since we have set the mapping mode to MM_LOENGLISH the height and width obtained using GetTextMetrics( ) would be in logical units, where, one logical units is equal to 0.01 inches. Lastly, we have called myview::setview( ).
In setview( ) we have determined the maximum line length by iterating through list of lines maintained by the CStringList object called filelines_str (this object is a data member of the mydoc class and is initialized when the file is opened.) Once the maximum line length is determined we have set the scroll sizes by calling SetScrollSizes( ).
Why have we written a separate setview( ) function? Could we not have determined the maximum line length and set the scroll sizes in myview::OnIntialUpdate( )? This would not be feasible because every time we open a new document the scroll sizes would change, but the OnInitialUpdate( ) function would be called only once when the view is attached to the document. Hence, whenever a new document is opened, setview( ) is called again such that the scroll sizes are set appropriately as per the contents of the file being opened.
Note that whenever we derive our view class from CScrollView it is necessary to override OnInitialUpdate( ) and set the scroll sizes in it (or in a function called from it).
When OnDraw( ) is called for displaying the lines the scroll sizes already stand set. Hence in OnDraw( ) all that we have done is select the font in the device context, get the count of lines in the document being displayed, and display the lines in the view. For displaying the lines we have used the CDC::TabbedTextOut( ) function, rather than the normal TextOut( ) function. This ensures that a tab character, if present in the document, is not displayed as a character. Instead the output is properly tabbed.
This program does not generate a WYSIWYG output. This is because there are virtually no limits to the screen width and height once we have a scrollable view in place. However, on the printer we have to print the output keeping in mind the physical dimensions of the page. This is the reason why we have done painting through OnDraw( ) and printing through OnPrint( ).
If the document contains a line whose length is more than the width of the page then in one line we should print as much as can be accommodated in it, and the balance in the next line. To achieve this we have to do a lot of arrangements in myview::OnBeginPrinting( ). These are as under:
- We have created a 10-point ‘Arial’ font and selected it into a device context. It is necessary to do the font creation for printer separately because the printer metrics are different than the screen metrics.
- We have determined the number of lines that can be accommodated per page and the number of characters that can be accommodated per line. These have been determined keeping in view the following things:
- The width and the height of the paper selected.
- The width and the height of the character of the selected font.
- We have copied the list of lines maintained by the CStringList object called filelines_str into another CStringList object called prn_str. This copying is done since we wanted that the two string lists—one going to the screen and another going to the printer—to be maintained separately. This is necessary, because, on the screen we want to scroll the line if its length is more than the width of the client area, whereas, on the printer we want to split the line appropriately.
- We have updated the list of strings going to the printer by
- Splitting the line if its length is more than the page width.
- Replacing every tab with four spaces.
While copying or updating the list of strings we have to iterate through the list. This is achieved through the following simple code:
CStringList list ;
CString str ;
pos = list.GetHeadPosition( ) ;
int count = list.GetCount( ) ;
for ( int i = 0, i <>
str = list.GetNext( ) ;
To begin with, we have to retrieve the POSITION of the first item in the list using CStringList::GetHeadPosition( ). Then to iterate through the list we have to use the GetNext( ) function. This function accepts a POSITION value identifying the current position in the list and returns the string at that position. It also updates the POSITION value to reference the next item in the list.
Given a POSITION value pos identifying a particular item, we can retrieve or remove an item using the CStringList member functions GetAt( ) and RemoveAt( ). We can also insert items into the list using the member function InsertAfter( ).
- Once the list of strings going to printer is updated we have determined maximum number of pages that the document would need for printing. We have then set the maximum page value into the CPrintInfo structure using CPrintInto::SetMax-Page( )
We are now all set to do the actual printing. This has been done in myview::OnPrint( ). This function contains three logical parts:
- Printing header
- Printing page contents
- Printing footer
In the header we want to display the name of the file. Hence we have retrieved it using CDocument::GetTitle( ). Once retrieved, depending upon its length we have calculated the x-coordinate where it should be printed. Lastly, we have printed the header using CDC::TextOut( ).
While printing the page body, on each page we must print the appropriate lines from the string list. To pick up the relevant lines, we have found the index of the first string to be printed on the current page. The current page number is available in CPrintInfo::m_nCurPage and the index can be obtained using CStringList::FindIndex( ). Once the index is found out, through a loop, as many lines as can be accommodated on a page are printed using CDC::TextOut( ). Lastly, the footer is printed with the page number properly aligned in the center.
Once the printing is over the font as well as the list of strings is deleted by calling the functions CFont::DeleteObject( ) and CStringList::RemoveAll( ), respectively.
In the print preview mode we can see how the printed output will look like. Once we have incorporated the ability to print in a Doc/View application, adding print previewing facility is a simple matter. We just need to add the ‘Print Preview’ item to the file menu (with id ID_FILE_ PRINT_PREVIEW) and connect it to the function CView::OnFilePrintPreview( ) through a message map entry.
MFC has packed a lot of code in the OnFilePrintPreview( ) function. This function creates a view from a CScrollView-derived class called CPreviewView. It also adds a toolbar containing buttons to go to next or previous page, to switch between one-page and two-page views, to zoom in or zoom out etc. The CPreviewView::OnDraw( ) function does three jobs:
- It draws a white rectangle representing a printed page. If a two-page view is selected then two white rectangles are drawn.
- It sets up the scaling parameters such that the printable area of the white rectangle matches the printable area of the real page.
- Lastly, it calls the OnPrint( ) function to do the drawing in the rectangle.
The application may believe that the output is being sent to the printer, whereas, in actuality it is being sent to the print preview window.
Printing a Text file
Labels: Visual C++