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.

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.
|
Function |
Purpose |
Aids in |
|
OnInitialUpdate(
) |
Called when view first
gets attached to a document. Creates font,
obtains text metrics and sets scroll
sizes. |
Painting |
|
OnDraw(
) |
Compulsory to override.
Contains logic to display file contents in the
view. |
Painting |
|
OnPreparePrinting(
) |
Calls DoPreparePrinting( ) which pops up the
‘Print’ dialog. |
Printing |
|
OnBeginPrinting(
) |
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. |
Printing |
|
OnPrint(
) |
Called before each page
is printed. Prints header, page body and
footer. |
Printing |
|
OnEndPriting(
) |
Called when printing is
finished. Deletes font and deallocates list of
strings. |
Printing |
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 ;
POSITION pos,
CString str ;
pos = list.GetHeadPosition( ) ;
int count = list.GetCount( ) ;
for ( int i = 0, i < count, 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.
|