How it Works


Why not MFC



Menu of tutorials

Tutorial 1:   The Simplest Window
Tutorial 2:   Using Classes and Inheritance
Tutorial 3:   Using Messages to Create a Scribble Window
Tutorial 4:   Repainting the Window
Tutorial 5:   Wrapping a Frame around our Scribble Window
Tutorial 6:   Customising Window Creation
Tutorial 7:   Customising the ToolBar
Tutorial 8:   Loading and Saving Files
Tutorial 9:   Printing
Tutorial 10: Finishing Touches

Tutorial 9:  Printing

In this tutorial we will demonstrate how to send a bitmap to a printer. The bitmap in this case will be the one we've been drawing on in the view window. The resulting printout will be resized to match the size of the original drawing. This task can be broken down into several steps:

  • Extract the bitmap from the view window
  • Choose the printer
  • Start the print job
  • Extract the bitmap image data from the bitmap
  • Copy the resized image data to the printer's device context
  • End the print job

Extract the bitmap from the view window 

When we draw on the view window, we are actually drawing on to a bitmap attached to view window's device context. Here we will copy this bitmap to a compatible bitmap.

   // Copy the bitmap from the View window
  CClientDC dcView(GetView());
  CMemDC MemDC(dcView);
  CBitmap bmView;
  bmView.CreateCompatibleBitmap(dcView, Width, Height);
  MemDC.BitBlt(0, 0, Width, Height, dcView, 0, 0, SRCCOPY);

Choose a printer

This step is fairly straight forward. We declare a PRINTDLG struct and use this in the PrintDlg function. The PrintDlg function brings up a dialog, allowing us to choose the printer, and stores its device context in the PRINTDLG struct.

   CPrintDialog PrintDlg;

  // Bring up a dialog to choose the printer
  if (PrintDlg.DoModal(GetView()) == IDOK) // throws exception if there is no default printer

Note: CPrintDialog::DoModal will throw a CResourceException if there is no printer configured, or if the dialog can't be created. A try/catch block should be used to catch an exception thrown by DoModal.

Start the Print Job

The StartDoc function should be called before sending output to a printer. This function ensures that multipage documents are not interspersed with other print jobs. StartPage (and a corresponding EndPage) is then called for each page of printout.

   // Zero and then initialize the members of a DOCINFO structure.
  DOCINFO di = {0};
  di.cbSize = sizeof(DOCINFO);
  di.lpszDocName = _T("Scribble Printout");
  di.lpszOutput = (LPTSTR) NULL;
  di.lpszDatatype = (LPTSTR) NULL;
  di.fwType = 0;

  // Begin a print job by calling the StartDoc function.
  StartDoc(pd.hDC, &di);

  // Inform the driver that the application is about to begin sending data.

Extract the bitmap image data from the bitmap

In order to use StretchDIBits we first need the bitmap image data. This is retrieved by using the GetDIBits. It is called twice in the following code sample, the first time to get the size byte array to hold the data, and the second to fill the byte array.

   // Get the dimensions of the View window
  CRect rcView = GetView().GetClientRect();
  int Width = rcView.Width();
  int Height = rcView.Height();
  // Fill the BITMAPINFOHEADER structure
  ZeroMemory(&bi, sizeof(BITMAPINFOHEADER));
  bi.biSize = sizeof(BITMAPINFOHEADER);
  bi.biHeight = Height;
  bi.biWidth = Width;
  bi.biPlanes = 1;
  bi.biBitCount =  24;
  bi.biCompression = BI_RGB;
  // Note: BITMAPINFO and BITMAPINFOHEADER are the same for 24 bit bitmaps
  // Get the size of the image data
  MemDC.GetDIBits(bmView, 0, Height, NULL, reinterpret_cast(&bi), DIB_RGB_COLORS);

  // Retrieve the image data
  std::vector vBits(bi.biSizeImage, 0);	// a vector to hold the byte array
  byte* pByteArray = &vBits.front();
  MemDC.GetDIBits(bmView, 0, Height, pByteArray, reinterpret_cast(&bi), DIB_RGB_COLORS);	

Copy the resized bitmap to the printer's device context

StretchDIBits is the function used here to copy the bitmap information to the printer's device context because the bitmap needs to be resized in order to retain the same dimensions on the printed page as the original.  The following code segment shows how the scaling factors are calculated and the StretchDIBits  function is called.

   // Determine the scaling factors required retain the bitmap's original proportions.
  float fLogPelsX1 = (float) GetDeviceCaps(ViewDC, LOGPIXELSX);
  float fLogPelsY1 = (float) GetDeviceCaps(ViewDC, LOGPIXELSY);
  float fLogPelsX2 = (float) GetDeviceCaps(pd.hDC, LOGPIXELSX);
  float fLogPelsY2 = (float) GetDeviceCaps(pd.hDC, LOGPIXELSY);
  float fScaleX = MAX(fLogPelsX1, fLogPelsX2) / min(fLogPelsX1, fLogPelsX2);
  float fScaleY = MAX(fLogPelsY1, fLogPelsY2) / min(fLogPelsY1, fLogPelsY2);

  // Compute the coordinates of the upper left corner of the centered bitmap.
  int cWidthPels = GetDeviceCaps(pd.hDC, HORZRES);
  int xLeft = ((cWidthPels / 2) - ((int) (((float) Width) * fScaleX)) / 2);
  int cHeightPels = GetDeviceCaps(pd.hDC, VERTRES);
  int yTop = ((cHeightPels / 2) - ((int) (((float) Height) * fScaleY)) / 2);

  // Use StretchDIBits to scale the bitmap and maintain its original proportions
  StretchDIBits(pd.hDC, xLeft, yTop, (int) ((float) Width * fScaleX), 
               (int) ((float) Height * fScaleY), 0, 0, Width, Height, pByteArray, 
               reinterpret_cast<BITMAPINFO*>(&bi), DIB_RGB_COLORS, SRCCOPY);

End the print job

To finish the print job, EndPage is called to indicate that printing to this page is complete, then EndDoc is called the end the print job.

   // Inform the driver that the page is finished.
  // Inform the driver that document has ended.

Accessing the CView from within CDoc

The CDoc::Print uses GetView to access the CView class. GetView is defined as follows.

const CView& CDoc::GetView() const
  CMainFrame& Frame = GetScribbleApp().GetMainFrame();
  return static_cast(Frame.GetView());

The source code for this tutorial is located within the Tutorial folder of the software available from SourceForge at http://sourceforge.net/projects/win32-framework.