BUG: nCopies Does Not Initialize the Print Common Dialog Box

ID: Q197393

The information in this article applies to:

SYMPTOMS

On Windows 95, an attempt by an application to initialize the Copies edit control in the Print Common Dialog box fails. However, the user of the application can still edit the number of copies in the dialog box that are then returned to the application when the dialog box is dismissed.

RESOLUTION

To work around the problem, an application can use the dmCopies member of the printer's DEVMODE structure. The application should properly initialize the structure and then set the dmCopies member of the DEVMODE structure to the desired number of copies. When the application raises the Print Common Dialog box with the PrintDlg() function, it should pass the DEVMODE using the hDevMode member of the PRINTDLG structure.

Alternatively, an application can use a Print Hook function to override the initialization of the Print common dialog box to set the Copies edit control to the desired value.

For sample source code demonstrating these techniques, see the MORE INFORMATION section of this article.

STATUS

Microsoft has confirmed this to be a bug in the Microsoft products listed at the beginning of this article.

MORE INFORMATION

As documented in the Platform SDK (formerly the Win32 SDK), you can use the nCopies member of the PRINTDLG structure to pre-specify for the Print Dialog how many copies to print. The user specifies and changes the number of copies to print in the Copies area of the Print common dialog box. When the dialog box is dismissed, the printer device handles the number of copies if it supports copies using the DEVMODE structure, or it returns the number of copies to the application using the nCopies member.

However, on Windows 95, any value specified in the nCopies member of the PRINTDLG structure is ignored. The Windows 95 Print Common Dialog contains a bug that always initializes this value in the dialog box to "1" or to the value specified in the dmCopies member of the DEVMODE structure.

Applications typically do not encounter this problem as they are written to expect the default value of "1" for the number of copies. Applications that automatically print multiple copies, as is the case for forms in triplicate and the like, encounter this problem.

To work around this bug you can initialize the number of copies to print using the dmCopies member of the printer device DEVMODE structure.

Steps to Implement dmCopies Workaround

1. Obtain an initialized DEVMODE structure for the default printer.

2. Change the dmCopies member of the DEVMODE structure to the desired

   number of copies.

3. Initialize the hDevMode member of the PRINTDLG structure with a handle
   to global memory pointing to a buffer containing the modified DEVMODE.

4. Call the PrintDlg() function to raise the Print Common Dialog box with
   the initialized structures.

This workaround may fail to work if the target printer (the default printer) does not support device copies using the DEVMODE structure, as is the case with most dot matrix printers.

Sample Code for dmCopies Workaround

/*
      FUNCTION ModifyDevMode

      PURPOSE: Encapsuulate the method of making changes
               to a Printer DEVMODE.

      ASSUMPTIONS:   pDevMode must be a properly initialized
                  DEVMODE from either PrintDlg(),
                  GetPrinter(), or DocumentProperties().
    */ 
   BOOL ModifyDevMode(LPDEVMODE pDevMode, int nCopies)
   {
      LPSTR       pDevice;
      HANDLE      hPrinter;
      DWORD       dwRet;

      // Get the name of the printer from the DEVMODE.
      pDevice = (LPSTR)pDevMode->dmDeviceName;

      // Open the printer so you can call DocumentProperties.
      if (!OpenPrinter( pDevice, &hPrinter, NULL ))
         return FALSE;  // Must be a bad printer name

      /*
       * Make changes to the DevMode that are supported.
       */ 
      if (pDevMode->dmFields & DM_COPIES)
      {
         // Supported, so make the change
          pDevMode->dmCopies = nCopies;
      }
      else
      {
         // Not supported so exit.
          ClosePrinter( hPrinter );
         return FALSE;
      }

      /*
       * Merge the new settings with the old.
       * This gives the driver a chance to update any private
       * portions of the DevMode structure.
       */ 
      dwRet = DocumentProperties( NULL,
          hPrinter,
          pDevice,
          pDevMode,       /* Reuse your buffer for output. */ 
          pDevMode,       /* Pass your changes to the driver. */ 
          DM_IN_BUFFER |  /* Commands to merge your changes */ 
          DM_OUT_BUFFER ); /* and write the result. */ 

      ClosePrinter( hPrinter );

      return TRUE;

   } /* End of function ModifyDevMode. */ 

   /*
      FUNCTION GetUserPrinterDC

      PURPOSE: Launches the Printer Common Dialog and initializes the
               Copies edit control for that dialog. It does this using the
               dmCopies member of the DEVMODE structure to avoid a bug in
               Windows 95 that causes the Copies edit control to not be
               initialized from the PRINTDLG structure.

    */ 
   HDC GetUserPrinterDC(HWND hWnd, int nCopies)
   {
      PRINTDLG    pd;
      LPDEVMODE   pDevMode;
      HDC         hDC = NULL;

      // Initialize the DEVMODE to get the default printer.
      ZeroMemory( &pd, sizeof(pd) );
      pd.lStructSize = sizeof(pd);
      pd.hwndOwner = hWnd;
      pd.Flags = PD_RETURNDEFAULT;

      // Get a DEVMODE for the default printer.
      if (!PrintDlg( &pd ))
         return NULL;

      //Your number of copies goes into DEVMODE.dmCopies.
      pDevMode = (LPDEVMODE)GlobalLock( pd.hDevMode );
      if (!ModifyDevMode( pDevMode, nCopies ))
      {
         // Can't initialize dmCopies, so use PRINTDLG, so the code at
         // least works on other versions of Windows (OSR 2, 98, NT).
         pd.nCopies = nCopies;
      }
      GlobalUnlock( pd.hDevMode );

      // Setup and get the user's Device Context.
      pd.Flags = PD_RETURNDC;
      if (PrintDlg( &pd ))
         hDC = pd.hDC;

      // Cleanup properly.
      if (pd.hDevMode)
         GlobalFree( pd.hDevMode );
      if (pd.hDevNames)
         GlobalFree( pd.hDevNames );

      // Send it back to the caller.
      return hDC;

   } /* End of function GetUserPrinterDC. */ 

An alternative workaround is to use the Print Common Dialog lpfnPrintHook member to set the Copies edit control to the desired value when the dialog is initialized. An application can install a dialog hook procedure using the lpfnPrintHook member of the PRINTDLG structure. Then, when the hook procedure processes the WM_INITDIALOG message for the Print common dialog box, it can set the number of copies in the Copies edit control.

In many ways this is a better workaround than using the DEVMODE's dmCopies member because it works with all printers. However, the application must pass its own version of the number of copies, and the technique relies upon edit control IDs taken from Windows dialog template files and header files.

The application must pass the number of copies separately because the Print Common Dialog provides a version of the PRINTDLG structure to the lpfnPrintHook function which has been initialized to be consistent with the dialog's controls. As a result, the nCopies member of the PRINTDLG structure will have been overwritten when the lpfnPrintHook function is called.

Sample Code for Print Hook Function Workaround

   /*
      FUNCTION PrintHookProc

      PURPOSE: Hook Procedure for the PrintDlg Dialog.
               Used to override the Dialog's initialization processing
               of the Number of Copies edit control.

    */ 
   UINT CALLBACK PrintHookProc(
      HWND hdlg,
      UINT uiMsg,
      WPARAM wParam,
      LPARAM lParam )
   {
      static PRINTDLG *pPD = NULL;
      int      CopiesEditCtlId = edt3;

      // Note: You get the previous edit control ID from the
      // Prnsetup.dlg dialog template. edt3 is defined in
      // Dlgs.h.

      switch ( uiMsg )
      {
      case WM_INITDIALOG:
         {
            // In this message you get a pointer to the
            // PRINTDLG structure.
            pPD = (PRINTDLG *)lParam;

            // Extract the desired value for nCopoies.
            pPD->nCopies = (WORD)pPD->lCustData;

            // Effect it in the edit control.
            SetDlgItemInt( hdlg, CopiesEditCtlId, pPD->nCopies, FALSE );

            return 1;
         }
      }

      // Otherwise, you didn't handle the message.
      return 0;

   } /* End of function PrintHookProc. */ 

   /*
      FUNCTION GetUserPrinterDC

      PURPOSE: Launches the Printer Common Dialog box and
               initializes the Copies edit control for that dialog box.
               Uses a PrintHookProc to avoid a bug in Windows 95 that
               causes the Copies edit control to not be initialized from
               the PRINTDLG structure.

    */ 
   HDC GetUserPrinterDC(HWND hWnd, int nCopies)
   {
      PRINTDLG    pd;
      HDC         hDC = NULL;

      // Set up and get the users Device Context.
      ZeroMemory( &pd, sizeof(pd) );
      pd.lStructSize = sizeof(pd);
      pd.hwndOwner = hWnd;
      pd.nCopies = nCopies;
      pd.Flags = PD_RETURNDC | PD_ENABLEPRINTHOOK;
      // Pass the nCopies using custom data so you have it in the
      // PrintHookProc after the Dialog has setup its own
      // copy of this PRINTDLG structure on WM_INITDIALOG.
      pd.lCustData = nCopies;
      pd.lpfnPrintHook = PrintHookProc;

      if (PrintDlg( &pd ))
         hDC = pd.hDC;

      // Cleanup properly.
      if (pd.hDevMode)
         GlobalFree( pd.hDevMode );
      if (pd.hDevNames)
         GlobalFree( pd.hDevNames );

      // Send it back to the caller.
      return hDC;

   } /* End of function GetUserPrinterDC */ 

Additional query words:
Keywords          : kbCmnDlgPrint kbGDI kbPrinting kbSDKPlatform kbSDKWin32 
Version           : WINDOWS:95
Platform          : WINDOWS
Issue type        : kbbug
Solution Type     : kbfix

Last Reviewed: December 15, 1998