Community Pick: Many members of our community have endorsed this article.

How to set "Pages Per Sheet" Programmatically for "N-Up" Printing

DanRollins
CERTIFIED EXPERT
Published:
This article describes how to programmatically preset the "Pages per Sheet" option that's available with most printer drivers.   This setting lets you do "n-Up" printing, where two, four, or more pages are printed on each sheet of paper.

If your program provides printed output, you may want to be able to preset some settings for automatic printing so that your user does not need to monkey around in the "Preferences" or "Advanced" settings of the common printing dialog.  For instance, in one of my application programs, I let the user select a certain type of wide-format report, and when it came time to print that report, the program would force a small font and preset for Landscape output.  

The Pages per Sheet setting, sometimes called N-up printing, lets your user conserve paper by shrinking the page and printing two pages side-by-side.  I've found that the text in 2-up printing, though small, is still readable.  If your eyes are good (or the text is rendered in a large font), even 4-up printing is usually viable.  I often use it for C++ source code printing.
6-up printing of a five-page fileBeyond that, Windows actually lets you select 6-, 9- and 16-up printing.  Though the output is usually too small to be readable, this option could be used in a "pre-print" run of a long document... each page on the sheet would act as a sort of thumbnail image to help identify gross formatting problems, "widows and orphans," and other printing artifacts that you would like to avoid before sacrificing several trees to a full-sized run.

Presetting for Landscape Printing
Programmatically setting for Landscape printing is well documented in the Microsoft literature.  For instance, see:
    How to Change Default Printer Settings in an MFC Application
...and the simpler version:
   CWinApp::GetPrinterDeviceDefaults
...for some example code.

If you can get a HANDLE to a default DEVMODE structure, then you can change settings in it.  All you need to do is GlobalLock() it to get a pointer, make changes, and then GlobalUnlock() it.  The DEVMODE.dmOrientation field controls landscape printing.  Set it to DMORIENT_LANDSCAPE and the page will print sideways.

MFC thoughtfully provides an m_hDevMode handle that your CWinApp-derived program can use.   It's a private member, but there are several ways to access it; for instance, you can make your "Set_N_Up_Mode()" function a member of your CWinApp-derived object.  Or you can use the simple technique in the example code below -- shown as an action in a CView-derived object.

Setting Pages Per Sheet
As this is entirely undocumented, I had to do some investigative work:  In a simple MFC SDI application, I brought up the Print Dialog, changed the Pages Per Sheet setting and then used the debugger to look at the DEVMODE structure for changes.  Alas, No joy.  

But there is a printer-driver private area that directly follows the "formal" devmode record.  By displaying that area before and after changing the setting, I discovered the following:

At offset 24 (0x18) after the end of the DEVMODE structure, there is a byte that directly controls this setting.  Its valid values are:
    0 = 1-up
                          1 = 2-up
                          2 = 4-up
                          3 = 6-up
                          4 = 9-up
                          5 = 16-up

Open in new window

(a fact I later confirmed by digging around in the Printer Driver DDK).

So, you need only punch the right value into the right place, at the right time.   You'll want to make the change to the application-global DEVMODE right before printing.  I decided to put it in the easily-accessible OnPreparePrinting function of my View class.
BOOL CMyView::OnPreparePrinting(CPrintInfo* pInfo)
                      {
                          PRINTDLG rPD;
                          BOOL fRet= AfxGetApp()->GetPrinterDeviceDefaults( &rPD );
                      
                          LPDEVMODE pDM= (LPDEVMODE)::GlobalLock( rPD.hDevMode );
                      
                          PBYTE pbDriverData= (PBYTE)(((BYTE *)pDM)+(pDM->dmSize));// right after the end
                      
                          pbDriverData[24]= 2;  // set to 4-up (1=2-up, 3=6-up, etc.)
                      
                          ::GlobalUnlock( rPD.hDevMode );
                      
                          // default handling (as added by the ClassWizard)
                          return( DoPreparePrinting(pInfo) );
                      }

Open in new window

Notes:
The above uses MFC-specific code, but the general technique can be applied to any programming language.  With very few exceptions, all Windows printing goes through the Common Print dialog.  If you can figure out how to get to the hDevMode member of the PRINTDLG structure, then the rest should be easy.
Not all printer drivers support N-up printing.  You should check:
    DEVMODE.dmNup
If it has a value of 1 (DMNUP_SYSTEM) then the driver supports this.  If it has a value of 2 (DMNUP_ONEUP) then you should probably not try to modify the data that follows the DEVMODE structure.
The printer driver will typically support an option for drawing a border (thin box) around each page.  The default for 1-up is no border, but the default for 2-or-more-up is to add the border.  Some additional experimentation led me to this setting as well.  You can use code like the following to set or clear the "Draw Borders" checkbox:
if ( pDM->dmDriverExtra > 564 ) { pbDriverData[564]= 0; // 1= no border lines; 0= draw borders }
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=
If you liked this article and want to see more from this author, please click the Yes button near the:
      Was this article helpful?
label that is just below and to the right of this text.   Thanks!
=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=  
2
14,098 Views
DanRollins
CERTIFIED EXPERT

Comments (2)

Commented:
I do not obtain to read the code!
Robert BerkeConsultant
CERTIFIED EXPERT

Commented:
vba code can be found at

https://www.experts-exchange.com/questions/27427865/Does-anybody-have-vba-code-for-macros-to-PrintDuplex4up-PrintSimplex1up.html?anchorAnswerId=37115656#a37115656

call printanywhere("n4d") will print  4up duplex (word or excel document) to a printer named "North". Tested with office 2003 under windows 7 pro.

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.