Click here to Skip to main content
Click here to Skip to main content

Printing with MFC Made Easy

, 1 Dec 1999
Rate this:
Please Sign up or sign in to vote.
Two classes to add advanced print functionality to your MFC application
  • Download demo project - 62 Kb
  • Download source files - 48 Kb
  • Sample Image - printingmadeeasy.jpg

    <!-- Article Starts -->

    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 highlite 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 cumbersomesyntax 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:

              #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:
      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:

      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:

      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():

    //////////////////////////////////////////////////////////////////////////
    // 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

    About the Author

    Dan Pilat

    United States United States
    No Biography provided

    Comments and Discussions

     
    QuestionPrinting to non default priner without Cprintdialog PinmemberIan Sutcliffe19-Jun-12 14:02 
    GeneralThis article is 10 years old... PinmemberTuPacMansur16-Jun-10 16:12 
    GeneralGfxFontToCharformat - Memory overwrite Pinmemberhanzzzzzz17-May-10 3:18 
    Generalset defaults Pinmembergeneral_era22-Jul-08 23:50 
    GeneralRe: set defaults Pinmembertqg29-Jul-09 4:12 
    Questionwhat can i do to make it works? where is the creator? PinmemberMember 160180617-Mar-08 20:14 
    GeneralProblem while stopping the print!! Pinmemberphiliptabraham20-Nov-07 20:49 
    NewsReportMax Beta Pinmemberemadns17-Aug-07 8:25 
    GeneralCan't make it work - help! Pinmemberldsdbomber11-May-07 3:15 
    Questioncan it print a picture? Pinmembermohsen nourian3-Oct-06 0:14 
    Generalstrange results from one printer PinmemberJac du lak6-Apr-06 9:52 
    GeneralRe: strange results from one printer PinmemberSven Bruns12-Apr-06 23:09 
    GeneralRe: strange results from one printer PinmemberJac du lak1-Aug-06 10:37 
    Generalhow did you it? Pinmembermohsen nourian28-Sep-06 0:09 
    GeneralRe: how did you it? PinmemberJac du lak28-Sep-06 2:07 
    GeneralApplication Hangs when using a printer conntected by parallel port. Pinmembermgodknecht10-Mar-06 9:16 
    GeneralNice article but it is not working. Pinmemberg_gili26-Feb-06 5:07 
    Generalprinting from CScrollView Pinmembervikas amin26-Sep-05 1:00 
    GeneralRe: printing from CScrollView Pinmembergeorgeraafat26-Feb-06 20:31 
    GeneralRe: printing from CScrollView PinmemberGautam Jain2-Aug-06 3:51 
    Generalprinting Pinmemberamitrajin02@gmail.com5-Sep-05 0:29 
    GeneralRe: printing Pinmemberonlinewan3-Dec-07 16:19 
    GeneralGood sample to start a printing project PinmemberBelal lehwany12-Dec-04 9:07 
    GeneralSingle Word Wrapping PinmemberThe Tough Guy7-Oct-04 19:54 
    GeneralRe: Single Word Wrapping PinmemberShailesh Halankar5-Apr-06 20:23 

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

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

    | Advertise | Privacy | Mobile
    Web01 | 2.8.140709.1 | Last Updated 2 Dec 1999
    Article Copyright 1999 by Dan Pilat
    Everything else Copyright © CodeProject, 1999-2014
    Terms of Service
    Layout: fixed | fluid