首先介绍两个术语。
dpi(dot per inch):每英寸的点数,用于表示打印机的分辨率。比如600dpi,则是横向、纵向都打600个点,一共可打360000个点。dpi的值越大,表示打印机的打印精度越高。我接触过的有203dpi、300dpi、600dpi。要想让打印效果好一些,最好用300dpi以上的打印机。
ppm(papers per minute):每分钟最多可以打印多少页,用于衡量打印机的打印速度。这个参数也很重要,比如我们有一个项目,需要边生产边给产品贴标,如果打印标签的速度过慢,就会跟不上生产。
Windows系统为打印功能提供了一些API,MFC又对这些API进行了封装。这些都为程序员开发打印机程序提供了便利。
MFC的SDI(单文档)和MDI(多文档)框架,都内置了功能很强的打印和预览功能。但我自己的项目很少使用它们,都是一些基于Dialog的MFC程序。所以打算从一些更底层的打印机制学起。
下面先写一段最简单的打印程序。
首先要创建一个打印机设备环境(DeviceContext),以下简称DC。就像在窗口上绘图或输出文本时,也要先为显示设备创建一个DC一样,还记得CDC、CWindowDC、CClientDC、CPaintDC这些吗?
然后调用DC类的StartDoc()函数开启一个打印作业。接着调用StretchBlt()函数向打印机DC绘制了一幅图片,图片是从本地bmp文件加载过来的。最后调用DC类的EndDoc()函数结束打印。
主要代码如下:
void CPrint01Dlg::Print() { // Display the Windows Print dialog box with "All" radio button // initially selected. All other radio buttons are disabled. CPrintDialog printDlg(FALSE); if (IDCANCEL == printDlg.DoModal()) return; // 利用CPrintDialog生成打印机设备环境 CDC dc; dc.Attach(printDlg.GetPrinterDC()); dc.m_bPrinting = TRUE; DOCINFO di; ::ZeroMemory(&di, sizeof(DOCINFO)); di.cbSize = sizeof(DOCINFO); di.lpszDocName = _T("sample"); // Start a print job. bool bPrintingOK = dc.StartDoc(&di); CPrintInfo info; // Set the number of the last page of the document. info.SetMaxPage(1); // Specifies the usable drawing area of the page in logical coordinates. info.m_rectDraw.SetRect(0, 0, dc.GetDeviceCaps(HORZRES), dc.GetDeviceCaps(VERTRES)); OnPrint(&dc, &info); if (bPrintingOK) dc.EndDoc(); // End a print job else dc.AbortDoc(); // Abort job. dc.Detach(); // Detach the printer DC } void CPrint01Dlg::OnPrint(CDC *pdc, CPrintInfo *pInfo) { // Load a bitmap. HANDLE file = ::LoadImage(NULL, _T("D:\\sample.bmp"), IMAGE_BITMAP, 0, 0, LR_LOADFROMFILE); if (NULL == file) return; CBitmap bmp; // Attach a bitmap handle to a CBitmap object. if (!bmp.Attach(file)) return; BITMAP bmpInfo; bmp.GetBitmap(&bmpInfo); CDC memDC; memDC.CreateCompatibleDC(pdc); HBITMAP hOldBmp = (HBITMAP)memDC.SelectObject(&bmp); CRect destRect = pInfo->m_rectDraw; pdc->StretchBlt(destRect.left, destRect.top, destRect.Width(), destRect.Height(), &memDC, 0, 0, bmpInfo.bmWidth, bmpInfo.bmHeight, SRCCOPY); memDC.SelectObject(hOldBmp); memDC.DeleteDC(); bmp.Detach(); }
下面来看看这段代码。除了上面提到的为打印机设备创建的DC对象和 它的几个成员函数:StartDoc()、StretchBlt()、EndDoc()、StretchBlt()之外,还创建了一个memDC。用过“双缓冲”绘图的朋友应该对这里的memDC很熟悉。我们经常用一张图片作为对话框的背景,主要是为了美化窗口。但在窗口大小发生变化,或者窗口频繁切换时,会发现背景图片有闪烁的现象,这个时候就需要采用“双缓冲”机制。创建一个与显示DC兼容的内存DC,然后将图片绘制到内存DC中,再从内存DC绘制到显示DC,这样就可以避免闪烁现象。
那这里为什么要用到memDC呢?Windows中的位图分两种:GDI位图和DIB位图。GDI位图是设备相关的,不能够直接将其选入到显示设备环境或者打印设备环境中。必须通过CDC::CreateCompatibleDC()函数为位图创建一个特殊的内存设备环境,然后再从内存设备环境绘制到目标设备环境。
最后贴一张这段程序的打印效果图片: