Click here to Skip to main content
15,867,594 members
Articles / Desktop Programming / MFC

Printing with MFC Made Easy

Rate me:
Please Sign up or sign in to vote.
4.71/5 (25 votes)
1 Dec 199910 min read 465.7K   10.3K   115   118
Two classes to add advanced print functionality to your MFC application

Sample Image - printingmadeeasy.jpg

Three of the biggest frustrations you're apt to encounter when adding print functionality to your MFC application are:

  • Printing simple row/column based reports.
    There hasn't been and probably never will be support for this - you're out of luck if you want to print a simple text based row and column report. You have to write it from scratch and worry about device contexts, page numbers, and headers and footers – all things you'd expect to be built into MFC but are not.
  • Figuring out how to print something without a CView.
    The MFC class CView has a lot of print functionality built into it. The problem is knowing how to apply it when printing outside of a CView, eliminating the unnecessary steps while keeping the required ones performed by CView::OnPrint().
  • Smoothly combining the printing of individual documents into one job.
    Users want the ability to generate custom reports – to be able to include different types of information in any combination they desire. There is no direct support for this in MFC, no class structure which makes coding this easy, and no standard way to communicate from one document to the next what page number or position on the page it left off.

Knowing this, I decided it was time to create my own classes for dealing with these issues: GPrintJob and GPrintUnit. The benefits of using them are:

  • You can print any type of report.
    These classes were designed with printing text based row and column reports in mind. You work in terms of rows, columns, and text – the burden of managing the device context, coordinates, word-wrapping, and other grunt work is done for you. However, because these classes come with tons of overrides which allow access to all stages of the printing process, you can use it to print anything you want.
  • They don't require a CView.
    Many types of reports are printed with "hidden" documents. That is, just because a view of the document isn't open doesn't mean the user won't want to print it. However, because most print functionality is built into CView, this is a difficult task in MFC. These two classes get around this because they have no dependence upon a CView, or any window for that matter.
  • Allows you to combine individual documents into a single report.
    There is a one-to-many relationship between GPrintJob and GPrintUnit. You only need one instance of GPrintJob to manage the global resources required for printing. On the other hand, you may have multiple GPrintUnits, where the actual "meat" of the report is printed.

For any given report, you'll need one and only one instance of a GPrintJob object. It displays the print dialog, creates the device context, and manages a lot of the "global" details (i.e., current page number) of the printing process. Your derived GPrintJob class' job is to coordinate the printing of one or more derived GPrintUnit objects. How many, in what order, and what they print is up to the user and you. Each derived GPrintUnit of yours will typically know how to print only one particular type of document. GPrintUnit itself prints nothing. Instead, it contains the functionality which allows your derived unit to print headers, footers, column headings, and the meat of the document. Each GPrintUnit must always have a parent GPrintJob.

The remainder of this article will point out the major features of these two classes, show some sample code demonstrating how to use them, and highlight some important notes.

Features

  • JRECT, JCUR, and JDC

    As mentioned above, each unit must have a parent job. Without a parent job, the unit wouldn't be able to print anything, as the job is what creates and manages the device context. The parent job is accessed via a GPrintUnit member variable m_pJob which gets passed to the unit in its constructor, or in a call to SetJob(). Instead of the cumbersome syntax m_pJob->m_pDC to access the device context, orm_pJob->m_rectClient to access the page size, the GPrintUnit header file has several macros to make this easy, including:

    C++
    #defineJDC  (*(m_pJob->m_pDC)) // the device context 
    #define     JRECTm_pJob->m_rectClient // the printed page size
    Figure 2. Helper macros.

    These macros can be used only from inside a GPrintUnit class. There are several other macros just like these which give you access to other highly used GPrintJob members.

  • Don't need a print dialog
    Printing some reports requires either your own custom dialog, or no dialog at all. This can be a problem, because the standard CPrintDialog typically creates the device context for you. In these non-standard situations, you can use the GPrintJob::UseDefaults() function to create a default device context for you.
  • Definable columns
    In typical row and column reports, the page is divided into columns, one column for each field in the records being printed. GPrintJob has all-around support for this type of report. You define all of the columns up front with the function InsertPrintColumn(). With this function, you define the column's heading, attributes, and size. The size is defined as a percentage of the printed page width. The column headings can be printed all at once with the function PrintColHeadings(). You can even define multiple sets of column headings in a given unit (each with its own number of columns, titles, and column size) and switch between them on-the-fly using the function SetActiveHeading().
  • Word wrapping
    To print text on the current line in a column of a report, use the function GPrintUnit::PrintCol(). This function will automatically detect if the text will overflow the boundaries of the given cell, and, using a hidden rich edit control, will word wrap the overflow onto the next line(s) (or page even!). Every column of all subsequent rows will be automatically shifted down by the amount of the overflow. For example:
    PART NUMBER DESCRIPTION QTY. COST 123-4567 Binary flip flop 1 $34.45 module, 
    	with 4t5 rating aq4-9909 Overhead flimflam 12 $0.99 b59-123 
            Left-handed gangly 6 $99.99 wrench 
    Figure 3. Word wrap example.
  • Print to file
    After the print dialog has been displayed, GPrintJob checks if the user selected the print to file option. If so, it displays the open file dialog, allowing the user to pick a file name to print to.
  • Overridables
    Both GPrintJob and GPrintUnit have tons of virtual functions, allowing you to tweak each and every stage of the print process to your suit application's particular requirements.
  • Text alignment chars
    Most row and column reports are interspersed with plain lines of text. The function PrintTextLine() was created for just this reason. It prints a line of text at the current cursor location on the page. More importantly, you can embed special control characters in the string, which allows you to control the format of sections of the string. For example, the line of code:
    C++
    PrintTextLine("one two\x1c\x1fthree four\x1efive six");

    Will print:

    "one two…………………………three four five six" 

    where "one two" is left justified (by default), "three four" is centered and separated from "one two" with dots, and "five six" is right justified. This extremely powerful feature lets you justify parts of a single line of text differently, without ever dealing with coordinates. There are macros (HFC_*) for these special formatting characters in gfx_printunit.h. This functionality lends itself wonderfully to printing headers and footers which often are a single line of text, with parts centered, and left and right justified.

  • Index

    An index is a tree like structure which not only shows the break down of a printed report, but also the page number on which each sub-section begins. Building an index couldn't be easier using these classes. In order to print an index, you must first create a GPrintIndexTree, usually declared by value in your job, and at the beginning of the print process, select it as your active tree using the macro GSELECT_PJINDEXTREE(). An index tree is an object of type GPrintIndexTree – a type-safe array of INDEXITEMs. Each INDEXITEM has a string for the title, a bit wise flag field, integer page number, and pointer to a GPrintIndexTree. In this way, the tree can have multiple recursive levels.

    Next, as each unit is printed, it is responsible for adding itself to the active tree using the function GPrintUnit::AddIndexItem(). It passes to this function an INDEXITEM structure which defines its title and starting page number (if applicable). If you never select another tree (other than the one declared in your job) as your active tree, all entries would appear under the root item:

    Introduction..............................................1 
    Languages C++.......................................................2 
    MFC.......................................................3 
    Pascal....................................................4 
    Basic.....................................................4 
    Fortran...................................................5 
    Computers Dell......................................................6 
    Compaq...................................................10 
    Gateway..................................................11 
    Conclusion...............................................15 
    Figure 4. Single level index.

    However, if you want a multilevel index, you must change the active item. In order to do this, use the macro GSELECT_PJINDEXTREE() passing it a pointer to the index item you want to make active/select. The item will then become active for the current "scope". For example, in the below table, the items "Languages", "C++", and "Computers" were all selected when they were added and subsequent items added afterward were automatically nested:

    Introduction..............................................1 
    Languages C++....................................................2 
    MFC.................................................3 
    Pascal.................................................4 
    Basic..................................................4 
    Fortran................................................5 
    Computers Dell...................................................6 
    Compaq................................................10 
    Gateway...............................................11 
    Conclusion...............................................15 
    Figure 5. Multi-level index.

    When the "C++" unit was printed, its index item had to be first created, initialized, and selected:

    C++
    INDEXITEM itemCPP;
    itemCPP.strName = "C++";
    itemCPP.nPage = JINFO.m_nCurPage;
    GSELECT_PJINDEXTREE(&itemCPP.pChildren);
    AddIndexItem(&itemCPP);
    Figure 6. Adding a unit's index entry.

    With these lines of code, any items added during this scope (i.e., "MFC") will get added as a branch off "C++".

    In order to print the index, use the function GPrintUnit::PrintTree(), passing it the index tree member variable you earlier declared as part of your derived job. It will know how to decipher the contents of the tree, will automatically indent nested levels of entries, and, if desired, put dots in between the title and page number. This is typically done as the last step in printing a report.

  • Headers and footers

    As mentioned previously, the function PrintTextLine() can be used to easily print the header and footer text. However, you must tell the unit you want them. You do this by first initializing the unit's m_pum members with their required size:

    C++
    pumHeaderHeight     // the height of the entire header
    pumHeaderLineHeight // the height of an individual line in the header
    pumFooterHeight     // the height of the entire footer
    pumFooterLineHeight // the height of an individual line in the footer
    Figure 7. Print unit metrics.

    When you call GPrintUnit::RealizeMetrics(), the unit will reserve space for them by subtracting their dimensions out of the printed page area, JRECT. Next, override PrintHeader() and PrintFooter() to print the header and footer. They will get called each time StartPage() and EndPage() are called, whether this happens directly or indirectly.

  • Print bitmaps: There is no special functionality built in to the classes for printing bitmaps, I just used this to show that there is no limitation to what can be printed with the GPrintJob and GPrintUnit classes. Because you have access to the printer device context, you can print anything you want, including bitmaps. The same bitmap printing code you use for painting on the screen is just as valid when printing.

Sample Usage

This sample shows how to print the small report listed in the above Figure 2. A derived unit and job are created and several virtual functions are overridden. We start the process by calling the job's function Print() from within handler OnFilePrint():

C++
//////////////////////////////////////////////////////////////////////////
// WARNING: uncompiled code ahead
//////////////////////////////////////////////////////////////////////////
class MyPrintUnit : public GPrintUnit
{
public:
   MyPrintUnit() {;}
   virtual ~MyPrintUnit() {;}
 
   virtual void DefineColHeadings();
   virtual void CreatePrintFonts();
   void InitPrintMetrics()
   virtual BOOL Print();
 
   CFont m_fontHeading;
   CFont m_fontBody;
};
 
void MyPrintUnit::DefineColHeadings()
{
   // define my four columns...percentages should all add up to 1.00
   InsertPrintCol(0, "Part Number", 0.45);
   InsertPrintCol(1, "Description", 0.30);
   InsertPrintCol(2, "Qty.", 0.10);
   InsertPrintCol(3, "Cost", 0.15);
    
   // must call base class
   GPrintUnit::DefineColHeadings();
}
 
void MyPrintUnit::CreatePrintFonts()
{
   m_fontHeading;
   m_fontBody;
   m_fontHeader.CreatePointFont(110, _T("Garamond"), &JDC);//I18nOK
   m_fontFooter.CreatePointFont(90, _T("Garamond"), &JDC);//I18nOK
}
 
BOOL MyPrintUnit::Print()
{
   // must call base class
   GPrintUnit::Print();
 
   StartPage();
 
   PrintColHeadings(DT_LEFT);
   
   struct part
   {
      LPCTSTR lpszPart;
      LPCTSTR lpszDesc;
      LPCTSTR lpszQty;
      LPCTSTR lpszCost;
   };
 
   struct part parts[] = 
 
       {"123-4567", "Binary flip flop module, with 4t5
      rating", "1","$34.45, "aq4-9909", "Overhead flimflam",
      "12", "$0.99", "b59-123","Left-handed gangly wrench",
 
   "6", "$99.99"};  for(int i = 0; i
   <
      sizeof(parts)/sizeof(parts[0]);
 
      i++) { StartRow();  struct
 
      part *pPart=
      &parts[i]; PrintCol(0,pPart->lpszPart);
      PrintCol(1, pPart->lpszDesc);PrintCol(2,
      pPart->lpszQty); PrintCol(3,pPart->lpszCost);
 
      EndRow();
   }
 
   EndPage();
} 
 
//////////////////////////////////////////////////////////////////////////
class MyPrintJob : public GPrintJob
{
public:
   MyPrintJob() {;}
   virtual ~MyPrintJob() {;}
   void OnPrint();
};
 
void MyPrintJob::OnPrint()
{
   My PrintUnit unit(this);
   unit.Print();
}
 
//////////////////////////////////////////////////////////////////////////
void OnFilePrint()
{
   MyPrintJob job;
   job.Print();
}
Figure 8. Sample usage.

The meat of the work is done in the unit's overridden Print() function. Several other functions like the unit's DefineColHeadings(), CreatePrintFonts(), and InitPrintMetrics() are all required overrides used to define the fields to be printed, create the fonts we want to use for our headings and body, and let the unit know the dimensions of each, respectively. Notice how StartPage()/StartRow() are used to begin every page/row, and EndPage()/EndRow() are used to complete and advance to the next page/row. You're exposure to fonts and DCs is kept to a minimum, as all you have to do is create the fonts and tell the dc to use them like you would when drawing on-screen.

Additional Notes

  • Margins
    The GPrintJob class has no direct support for printing margins. However, all you need to do is deflate the drawing rectangle (JRECT) to include your margins. Since all GPrintUnit print functions already use JRECT when printing, there is nothing else left to do.
  • Mapping mode
    The classes GPrintUnit and GPrintJob assume MM_TEXT mapping mode. You might have to do some extra work to use other mapping modes. It's fairly easy to do it with these classes, but this article won't demonstrate how to do this.
  • Print preview
    Print preview is not supported by GPrintUnit and GPrintJob.
  • Helpers
    There are several utility type classes/functions/macros in the file gfx_printunit.cpp and gfx_printunit.h which are not specifically related to the classes GPrintJob and GPrintUnit. If you have any questions, feel free to drop me an email.

License

This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.

A list of licenses authors might use can be found here.


Written By
United States United States
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
QuestionHow to give colour to the table unit Pin
Member 113403071-Aug-19 2:08
Member 113403071-Aug-19 2:08 
QuestionPrinting to non default priner without Cprintdialog Pin
Ian Sutcliffe19-Jun-12 14:02
Ian Sutcliffe19-Jun-12 14:02 
GeneralThis article is 10 years old... Pin
TuPacMansur16-Jun-10 16:12
TuPacMansur16-Jun-10 16:12 
GeneralGfxFontToCharformat - Memory overwrite Pin
hanzzzzzz17-May-10 3:18
hanzzzzzz17-May-10 3:18 
Generalset defaults Pin
general_era22-Jul-08 23:50
general_era22-Jul-08 23:50 
GeneralRe: set defaults Pin
R.A. Tao29-Jul-09 4:12
R.A. Tao29-Jul-09 4:12 
Questionwhat can i do to make it works? where is the creator? Pin
Member 160180617-Mar-08 20:14
Member 160180617-Mar-08 20:14 
GeneralProblem while stopping the print!! Pin
philiptabraham20-Nov-07 20:49
philiptabraham20-Nov-07 20:49 
NewsReportMax Beta Pin
emadns17-Aug-07 8:25
emadns17-Aug-07 8:25 
GeneralCan't make it work - help! Pin
ldsdbomber11-May-07 3:15
ldsdbomber11-May-07 3:15 
Questioncan it print a picture? Pin
mohsen nourian3-Oct-06 0:14
mohsen nourian3-Oct-06 0:14 
Generalstrange results from one printer Pin
Jac du lak6-Apr-06 9:52
Jac du lak6-Apr-06 9:52 
GeneralRe: strange results from one printer Pin
Sven Bruns12-Apr-06 23:09
Sven Bruns12-Apr-06 23:09 
GeneralRe: strange results from one printer Pin
Jac du lak1-Aug-06 10:37
Jac du lak1-Aug-06 10:37 
I have continued investigating this, and the printer that is causing the issues is one that uses the LIDIL. I have come across some references on the internet, but am still not sute this is the issue.

Thanks,

John
Generalhow did you it? Pin
mohsen nourian28-Sep-06 0:09
mohsen nourian28-Sep-06 0:09 
GeneralRe: how did you it? Pin
Jac du lak28-Sep-06 2:07
Jac du lak28-Sep-06 2:07 
GeneralApplication Hangs when using a printer conntected by parallel port. Pin
mgodknecht10-Mar-06 9:16
mgodknecht10-Mar-06 9:16 
GeneralNice article but it is not working. Pin
g_gili26-Feb-06 5:07
g_gili26-Feb-06 5:07 
Generalprinting from CScrollView Pin
vikas amin26-Sep-05 1:00
vikas amin26-Sep-05 1:00 
GeneralRe: printing from CScrollView Pin
georgeraafat26-Feb-06 20:31
georgeraafat26-Feb-06 20:31 
GeneralRe: printing from CScrollView Pin
Gautam Jain2-Aug-06 3:51
Gautam Jain2-Aug-06 3:51 
Generalprinting Pin
Member 21784995-Sep-05 0:29
Member 21784995-Sep-05 0:29 
GeneralRe: printing Pin
onlinewan3-Dec-07 16:19
onlinewan3-Dec-07 16:19 
GeneralGood sample to start a printing project Pin
Belal lehwany12-Dec-04 9:07
Belal lehwany12-Dec-04 9:07 
GeneralSingle Word Wrapping Pin
The Tough Guy7-Oct-04 19:54
The Tough Guy7-Oct-04 19:54 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.