使用VC/MFC打印(Print)

第一部分和第二部分是自己的实现及解决方法,第三部分是参考的文章
一:(打印实现的操作流程)
    在VC中实现打印操作,网上虽然看了很多的说明或例子,但具体实现时还是感觉迷惑,自己研究了下,将经验总结如下,如有错误,欢迎大家指正,免得误人误己。

   自己实现的打印是在CView的派生类(如:CMyView)中,关于MFC内部实现,有很多文章都说明了,这里就不重复了,实现步骤如下:
1:在CMyView中定义个函数入void PrintView(),函数实现调用this->OnFilePrint();即可,以便开始执行打印操作(内部机制是CView实现)。
2:在CMyView中先后实现虚函数OnPreparePrinting, OnBeginPrinting, OnPrint, OnEndPrinting。
3:在OnPreparePrinting函数中必须调用DoPreparePrinting(pInfo);函数,可以return DoPreparePrinting(pInfo);就行。在DoPreparePrinting(pInfo);中,会调用打印的小dialog,如果在小dialog出现后点击了取消或关闭,会返回false,如果在这时候你再调用CView::OnPreparePrinting(pInfo);会出现中断,因为你没有设置完毕,DoPreparePrinting函数没有成功的时候要返回false以取消打印。
4:在OnBeginPrinting函数中,设置最大页数,页眉,页脚等操作,如:pInfo->SetMaxPage(1); //设置最大页数为1页
5:在OnPrint函数中,实现具体的绘图操作,将你要的image或文字信息绘到打印机DC上即可。注:在OnPrint函数中会有系统自动添加的一句话CView::OnPrint(pDC, pInfo);,这句话尽量屏蔽掉,因为在你绘图操作后再调用CView::OnPrint(pDC, pInfo);系统会调用OnPaint操作,会将你的View上显示的内容也绘在打印纸上(我这实现时需要只打印View上的一块内容,不想打印全部view,如果继续调用CView::OnPrint(pDC, pInfo);会出现打印重合的情况,也出现我不想出现的整个view的内容)。
6:在OnEndPrinting函数中,释放申请的资源等,如果在OnBeginPrinting函数中申请了画刷什么的等其他资源在OnEndPrinting函数中释放即可。
 

:(图片多大尽量接近显示的大小,可以缩放但不能超出一张纸的大小,关于打印出的内容在打印纸上占用空间很小,如image打印出来后在打印纸上很小,只在打印纸上的左上角一小块地方)
    原因是打印机的DPI(dot per inch ,像素数)远远大于屏幕上的像素数比例,查看OnPrint(CDC* pDC, CPrintInfo* pInfo)中pInfo->m_rectDraw,会发现其区域很大(0, 0, 4761, 6814),所以在你用pDC绘图时会发现你图片的1280*1024到打印纸上只是很小的一块。在VC知识库中看到篇文章(在VC中所见即所得打印的简易实现),知道了解决方法,也进行了相应的修改以适应不是所谓800*600的机器,因为客户的电脑不一定多大呢,解决方法如下:
1:将下列代码添加到CMyView::OnPrint函数中, 对于设定坐标模式也可以在void CMyView::OnPrepareDC中实现
pDC->SetMapMode(MM_ANISOTROPIC); //转化坐标映射模式
int nScreenWidth = GetSystemMetrics(SM_CXSCREEN); //得到屏幕分辨率的宽和高(我的是1280*1024)
int nScreenHeight = GetSystemMetrics(SM_CYSCREEN);
CSize size = CSize(nScreenWidth, nScreenHeight);
pDC->SetWindowExt(size); //设置视窗大小
int xLogPixPerInch = pDC->GetDeviceCaps(LOGPIXELSX);  //得到设备每逻辑英寸的像素数量
int yLogPixPerInch = pDC->GetDeviceCaps(LOGPIXELSY);
float fWidth = (float)xLogPixPerInch / 96 ; //得到电脑屏幕映射到视窗大小比率
float fHeight = (float)yLogPixPerInch / 96; //一般得到的fWidth = fHeight 

long xExt = (long)(fWidth * size.cx); //得到视窗大小
long yExt = (long)(fHeight * size.cy) ;
pDC->SetViewportExt((int)xExt, (int)yExt); //设置视窗大小(由电脑屏幕映射到视窗大小)

  m_pDoc->PrintDraw(pDC, pInfo, fWidth, fHeight); //m_pDoc->PrintDraw是和当前View关联的Doc(存储数据等其他信 //息), fWidth和fHeight参数用于在绘图到打印机DC(pDC)时进行相应 //的计算,看是否超出了打印纸能打印区域(pInfo->m_rectDraw)边界
2:CMyDoc::PrintDraw()函数实现如下(为防止代码外泄,代码经过了改动,只剩下有意义代码):
//
CBitmap saveBmp, *pOldBmp = NULL;
CRect rectDraw; //得到view中要绘图的区域(可能派生自CScrollView,所以不一定小于
GetDrawRect(rectDraw); //屏幕大小)
 

//得到要进行缩放的比例
float fw = 0.0;
BOOL bw = FALSE;
float fh = 0.0;
BOOL bh = FALSE;
float fr = 1.0;
if ((long)(fWidth * rectDraw.Width()) > pInfo->m_rectDraw.Width()) //如果长度由屏幕映射到打印机后大于打印机的可绘图区域
{ //(pInfo->m_rectDraw), 则需要进行缩放操作,缩放后不是图形变形
fw = fWidth * rectDraw.Width() / pInfo->m_rectDraw.Width();
bw = TRUE;
}
if ((long)(fHeight * rectDraw.Height()) > pInfo->m_rectDraw.Height()) //同上
{
fh = fHeight * rectDraw.Height() / pInfo->m_rectDraw.Height();
bh = TRUE;
}
if (bw || bh) //缩放比率应使纸张能容纳下图片大小,且尽可能的按原图大小显示
fr = fw > fh ? fw : fh;

//Recalculate rect of draw
RecalculateRect(rectDraw, fr, rectDraw); //RecalculateRect函数是重新计算应缩放后的rect区域,以便使打印 //的东西不超出打印纸的可打印范围(pInfo->m_rectDraw)
// Create temp bmp
CDC tmpMemDC;
tmpMemDC.CreateCompatibleDC(m_memDC);
if( !saveBmp.CreateCompatibleBitmap(&m_memDC,
rectDraw.Width(), 
rectDraw.Height()))
goto error_return;
pOldBmp = pDC->SelectObject(&saveBmp);

// Paint background
pDC->FillRect(rectDraw, &CBrush(RGB(255, 255, 255)));

// Copy object to save bitmap
POSITION pos = m_objects.GetHeadPosition(); //我这里需要将一张图片中的几个不确定区域的图片打印出来
while(pos != NULL)
{
CDrawImageObj* pObj = (CDrawImageObj*)m_objects.GetNext(pos);
CRect rectSrc = PToRate(pObj->m_position, TRUE);
CRect rectDst = rectSrc;

//Recalculate rect of every object
RecalculateRect(rectSrc, fr, rectDst);

CBitmap* pTmpOldBmp = pTmpOldBmp = tmpMemDC.SelectObject(&pObj->GetBitmap());
// pDC->BitBlt(rect.left, rect.top, rect.Width(), rect.Height(), &tmpMemDC, 0, 0, SRCCOPY); //缩放的话不能使用BitBlt
pDC->StretchBlt(rectDst.left, rectDst.top, rectDst.Width(), rectDst.Height(),  //将原区域图片缩放到计算后的rect
&tmpMemDC, 0, 0, rectSrc.Width(), rectSrc.Height(), SRCCOPY); //中,这样就不会超出打印纸的大小了

tmpMemDC.SelectObject(pTmpOldBmp);
}

if (pOldBmp != NULL)
pDC->SelectObject(pOldBmp);
tmpMemDC.DeleteDC();
saveBmp.DeleteObject();

return TRUE;

error_return:;
if (pOldBmp != NULL)
pDC->SelectObject(pOldBmp);
tmpMemDC.DeleteDC();
saveBmp.DeleteObject();
return FALSE;
3:下面是重新计算函数,以使屏幕绘图的总大小(rectDraw),各个图片区域(rectSrc)进行合理转化,rect区域位置需要进行定位,其大小也要重新计算:
void CMyDoc::RecalculateRect(const CRect& rectSrc, float fRate, CRect& rectOut)
{
rectOut = rectSrc;
if (fRate == 0.0)
return ;

rectOut.left = (long)((float)rectSrc.left / fRate);
rectOut.top = (long)((float)rectSrc.top / fRate);
rectOut.right = (long)((float)rectSrc.right / fRate);
rectOut.bottom = (long)((float)rectSrc.bottom / fRate);
}


三:参考文章如下:
1:在VC中所见即所得打印的简易实现(http://www.vckbase.com/bbs/prime/viewprime.asp?id=326)
  在VC++6.0中用应用程序向导(AppWizard)生成的单文档或多文档程序提供了对打印功能的实现,但遗憾的是如果对自动生成的框架程序不做任何改进,打印出来的文档或图形和屏幕上的显示相比就会特别小。为什么会这样呢?

  本文对这种现象的原因和MFC的打印机制进行了深入的分析,并提出了一种特别简单的方法,在原有的程序中只需加入几行代码就能解决这一问题,实现所见即所得的打印。 

  首先,分析MFC的打印机制,把原理弄清楚了,就不难明白现象形成的原因和提出解决办法。MFC应用程序的核心是文档对象以及相关的视图窗口的概念,即CDocument类和CView类的构成和关系,简单地说CDocument类负责数据的生成和储存,CView类负责数据的显示和用户交互。输出到屏幕和输出到打印机都是数据的显示,实质上是一样的,所以打印功能也是由CView类来实现的。 

  在CView类中由应用程序向导自动生成的源代码提供了一个OnDraw(CDC* pDC)的函数,通过重载这个函数,利用它提供的pDC(设备上下文)指针,可以在屏幕上显示各种图形和数据。CView类的打印是通过OnPrint(CDC* pDC, CPrintInfo* pInfo)这个函数实现的,应用程序向导自动生成的源代码中没有这个函数的框架,而这个函数对打印的实现就是简单地调用OnDraw(CDC* pDC)这个函数,把打印机的设备上下文指针pDC传递给OnDraw(CDC* pDC)函数。 

  可见CView类对输出到屏幕和输出到打印机的处理都是一样的,只是换了一个设备上下文而已,那么为什么输出到打印机的图像特别小呢? 

  这与VC采用的缺省的坐标映射方式MM_TEXT有关,这种方式的好处是用户图形坐标和设备的象素完全一致。但是在屏幕的象素大小为800*600时,每逻辑英寸包含的屏幕象素为96,而打印机的点数却要多好几倍,如当打印机为HP LaserJet 6L时每逻辑英寸包含的打印机点数为600,也就是说打印机的清晰度比屏幕要高得多。 

  这样的后果就是在屏幕上显示出来的满屏图像在打印出来的纸上却只有一点点大,怎么解决这个问题呢?一种简单的方法就是转换坐标映射方式,使得打印时采用的坐标比例比显示时采用的坐标比例相应地大若干倍,就可以解决这一问题。 

  下面将给出详细的方法。

  注意到CView类在进行显示和打印之前都会调用virtual void OnPrepareDC( CDC* pDC, CPrintInfo* pInfo = NULL )这个虚拟成员函数来准备设备上下文,我们可以在CView类中重载这个虚拟成员函数,进行坐标转换。 

  首先用VC的ClassWizard实现对OnPrepareDC( CDC* pDC, CPrintInfo* pInfo = NULL )函数的重载,ClassWizard生成的源代码如下: 
  void CTempView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo) 
  { // TODO: Add your specialized code here and /or call the base class

       CView::OnPrepareDC(pDC, pInfo);

  } 

  我们只需在源代码中加入以下几行代码即可,如下: 

  void CPrintSameView::OnPrepareDC

  (CDC* pDC, CPrintInfo* pInfo) 

  { CView::OnPrepareDC(pDC, pInfo);

  pDC->SetMapMode(MM_ANISOTROPIC); //转换坐标映射方式 

  CSize size = CSize(800, 560);

  pDC->SetWindowExt(size);

  //确定窗口大小 //得到实际设备每逻辑英寸的象素数量 int xLogPixPerInch = pDC->
GetDeviceCaps(LOGPIXELSX); 

  int yLogPixPerInch = pDC- >GetDeviceCaps(LOGPIXELSY);

  //得到设备坐标和逻辑坐标的比例 long xExt = (long)size.cx * xLogPixPerInch/96 ; 

  long yExt = (long)size.cy * yLogPixPerInch/96 ;

  pDC->SetViewportExt((int)xExt, (int)yExt);

  //确定视口大小 }  
  如上所示,首先将坐标映射方式改变为MM_ANISOTROPIC方式,即各向异性的意思,在这种坐标方式下,X轴和Y轴的逻辑单位可以进行任意的缩放。改变坐标映射方式后,就要确定窗口大小和视口大小,注意窗口大小就是我们在屏幕上所见的尺寸,而视口大小则是实际设备,如打印机等,和显示器设备每逻辑英寸的象素数量比较所得的比例尺寸。通过函数得到显示器和打印机每逻辑英寸的象素数量,然后对视口大小进行相应的缩放,就可以使得屏幕上的显示和打印机的输出是一致的了。 

  这样,只通过几行简单的代码,我们就实现了所见即所得的打印。

2:使用MFC编写打印程序(http://www.vckbase.com/document/viewdoc/?id=1618)
摘要

  本文首先介绍了利用MFC提供的文档视图框架来实现一个打印程序,实现打印预览,在此基础上,同时通过对MFC源代码的深入探讨,提出了利用该方法在对话框上用MFC实现打印功能,结果表明,利用MFC实现打印不仅方便,而且功能很强大,能够根据不同的需求很方便的打印出所需要的格式。本文还实现了一个在对话框中利用MFC实现打印功能的一个框架结构,对于使用者只要使用该结构就可以按照自己的要求打印任何内容。

关键词:Visual C++ ,MFC,对话框, 打印 ,打印预览


引言

  打印程序的编写在windows程序设计中非常有用,针对不同的用户需要,通常用sdk方式实现打印代码量比较大,而且要对打印流程的底层有非常清楚的了解,需要一个程序员有非常深入的打印方面的知识,利用MFC提供的文档视图结构,不但可以实现一些常用的标准界面元素,把数据的处理的界面的处理分离出来,而且其提供的打印功能更是方便快捷,功能强大。打印程序的编写本质是是一种GDI绘图,只是绘图的对象是在打印机的设备描述表,如果对于屏幕的GDI绘图比较熟悉的读者,相信掌握打印程序的编写应该比较容易。
 

//得到要进行缩放的比例
float fw = 0.0;
BOOL bw = FALSE;
float fh = 0.0;
BOOL bh = FALSE;
float fr = 1.0;
if ((long)(fWidth * rectDraw.Width()) > pInfo->m_rectDraw.Width()) //如果长度由屏幕映射到打印机后大于打印机的可绘图区域
{ //(pInfo->m_rectDraw), 则需要进行缩放操作,缩放后不是图形变形
fw = fWidth * rectDraw.Width() / pInfo->m_rectDraw.Width();
bw = TRUE;
}
if ((long)(fHeight * rectDraw.Height()) > pInfo->m_rectDraw.Height()) //同上
{
fh = fHeight * rectDraw.Height() / pInfo->m_rectDraw.Height();
bh = TRUE;
}
if (bw || bh) //缩放比率应使纸张能容纳下图片大小,且尽可能的按原图大小显示
fr = fw > fh ? fw : fh;

//Recalculate rect of draw
RecalculateRect(rectDraw, fr, rectDraw); //RecalculateRect函数是重新计算应缩放后的rect区域,以便使打印 //的东西不超出打印纸的可打印范围(pInfo->m_rectDraw)
// Create temp bmp
CDC tmpMemDC;
tmpMemDC.CreateCompatibleDC(m_memDC);
if( !saveBmp.CreateCompatibleBitmap(&m_memDC,
rectDraw.Width(), 
rectDraw.Height()))
goto error_return;
pOldBmp = pDC->SelectObject(&saveBmp);

// Paint background
pDC->FillRect(rectDraw, &CBrush(RGB(255, 255, 255)));

// Copy object to save bitmap
POSITION pos = m_objects.GetHeadPosition(); //我这里需要将一张图片中的几个不确定区域的图片打印出来
while(pos != NULL)
{
CDrawImageObj* pObj = (CDrawImageObj*)m_objects.GetNext(pos);
CRect rectSrc = PToRate(pObj->m_position, TRUE);
CRect rectDst = rectSrc;

//Recalculate rect of every object
RecalculateRect(rectSrc, fr, rectDst);

CBitmap* pTmpOldBmp = pTmpOldBmp = tmpMemDC.SelectObject(&pObj->GetBitmap());
// pDC->BitBlt(rect.left, rect.top, rect.Width(), rect.Height(), &tmpMemDC, 0, 0, SRCCOPY); //缩放的话不能使用BitBlt
pDC->StretchBlt(rectDst.left, rectDst.top, rectDst.Width(), rectDst.Height(),  //将原区域图片缩放到计算后的rect
&tmpMemDC, 0, 0, rectSrc.Width(), rectSrc.Height(), SRCCOPY); //中,这样就不会超出打印纸的大小了

tmpMemDC.SelectObject(pTmpOldBmp);
}

if (pOldBmp != NULL)
pDC->SelectObject(pOldBmp);
tmpMemDC.DeleteDC();
saveBmp.DeleteObject();

return TRUE;

error_return:;
if (pOldBmp != NULL)
pDC->SelectObject(pOldBmp);
tmpMemDC.DeleteDC();
saveBmp.DeleteObject();
return FALSE;
3:下面是重新计算函数,以使屏幕绘图的总大小(rectDraw),各个图片区域(rectSrc)进行合理转化,rect区域位置需要进行定位,其大小也要重新计算:
void CMyDoc::RecalculateRect(const CRect& rectSrc, float fRate, CRect& rectOut)
{
rectOut = rectSrc;
if (fRate == 0.0)
return ;

rectOut.left = (long)((float)rectSrc.left / fRate);
rectOut.top = (long)((float)rectSrc.top / fRate);
rectOut.right = (long)((float)rectSrc.right / fRate);
rectOut.bottom = (long)((float)rectSrc.bottom / fRate);
}


三:参考文章如下:
1:在VC中所见即所得打印的简易实现(http://www.vckbase.com/bbs/prime/viewprime.asp?id=326)
  在VC++6.0中用应用程序向导(AppWizard)生成的单文档或多文档程序提供了对打印功能的实现,但遗憾的是如果对自动生成的框架程序不做任何改进,打印出来的文档或图形和屏幕上的显示相比就会特别小。为什么会这样呢?

  本文对这种现象的原因和MFC的打印机制进行了深入的分析,并提出了一种特别简单的方法,在原有的程序中只需加入几行代码就能解决这一问题,实现所见即所得的打印。 

  首先,分析MFC的打印机制,把原理弄清楚了,就不难明白现象形成的原因和提出解决办法。MFC应用程序的核心是文档对象以及相关的视图窗口的概念,即CDocument类和CView类的构成和关系,简单地说CDocument类负责数据的生成和储存,CView类负责数据的显示和用户交互。输出到屏幕和输出到打印机都是数据的显示,实质上是一样的,所以打印功能也是由CView类来实现的。 

  在CView类中由应用程序向导自动生成的源代码提供了一个OnDraw(CDC* pDC)的函数,通过重载这个函数,利用它提供的pDC(设备上下文)指针,可以在屏幕上显示各种图形和数据。CView类的打印是通过OnPrint(CDC* pDC, CPrintInfo* pInfo)这个函数实现的,应用程序向导自动生成的源代码中没有这个函数的框架,而这个函数对打印的实现就是简单地调用OnDraw(CDC* pDC)这个函数,把打印机的设备上下文指针pDC传递给OnDraw(CDC* pDC)函数。 

  可见CView类对输出到屏幕和输出到打印机的处理都是一样的,只是换了一个设备上下文而已,那么为什么输出到打印机的图像特别小呢? 

  这与VC采用的缺省的坐标映射方式MM_TEXT有关,这种方式的好处是用户图形坐标和设备的象素完全一致。但是在屏幕的象素大小为800*600时,每逻辑英寸包含的屏幕象素为96,而打印机的点数却要多好几倍,如当打印机为HP LaserJet 6L时每逻辑英寸包含的打印机点数为600,也就是说打印机的清晰度比屏幕要高得多。 

  这样的后果就是在屏幕上显示出来的满屏图像在打印出来的纸上却只有一点点大,怎么解决这个问题呢?一种简单的方法就是转换坐标映射方式,使得打印时采用的坐标比例比显示时采用的坐标比例相应地大若干倍,就可以解决这一问题。 

  下面将给出详细的方法。

  注意到CView类在进行显示和打印之前都会调用virtual void OnPrepareDC( CDC* pDC, CPrintInfo* pInfo = NULL )这个虚拟成员函数来准备设备上下文,我们可以在CView类中重载这个虚拟成员函数,进行坐标转换。 

  首先用VC的ClassWizard实现对OnPrepareDC( CDC* pDC, CPrintInfo* pInfo = NULL )函数的重载,ClassWizard生成的源代码如下: 
  void CTempView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo) 
  { // TODO: Add your specialized code here and /or call the base class

       CView::OnPrepareDC(pDC, pInfo);

  } 

  我们只需在源代码中加入以下几行代码即可,如下: 

  void CPrintSameView::OnPrepareDC

  (CDC* pDC, CPrintInfo* pInfo) 

  { CView::OnPrepareDC(pDC, pInfo);

  pDC->SetMapMode(MM_ANISOTROPIC); //转换坐标映射方式 

  CSize size = CSize(800, 560);

  pDC->SetWindowExt(size);

  //确定窗口大小 //得到实际设备每逻辑英寸的象素数量 int xLogPixPerInch = pDC->
GetDeviceCaps(LOGPIXELSX); 

  int yLogPixPerInch = pDC- >GetDeviceCaps(LOGPIXELSY);

  //得到设备坐标和逻辑坐标的比例 long xExt = (long)size.cx * xLogPixPerInch/96 ; 

  long yExt = (long)size.cy * yLogPixPerInch/96 ;

  pDC->SetViewportExt((int)xExt, (int)yExt);

  //确定视口大小 }  
  如上所示,首先将坐标映射方式改变为MM_ANISOTROPIC方式,即各向异性的意思,在这种坐标方式下,X轴和Y轴的逻辑单位可以进行任意的缩放。改变坐标映射方式后,就要确定窗口大小和视口大小,注意窗口大小就是我们在屏幕上所见的尺寸,而视口大小则是实际设备,如打印机等,和显示器设备每逻辑英寸的象素数量比较所得的比例尺寸。通过函数得到显示器和打印机每逻辑英寸的象素数量,然后对视口大小进行相应的缩放,就可以使得屏幕上的显示和打印机的输出是一致的了。 

  这样,只通过几行简单的代码,我们就实现了所见即所得的打印。

2:使用MFC编写打印程序(http://www.vckbase.com/document/viewdoc/?id=1618)
摘要

  本文首先介绍了利用MFC提供的文档视图框架来实现一个打印程序,实现打印预览,在此基础上,同时通过对MFC源代码的深入探讨,提出了利用该方法在对话框上用MFC实现打印功能,结果表明,利用MFC实现打印不仅方便,而且功能很强大,能够根据不同的需求很方便的打印出所需要的格式。本文还实现了一个在对话框中利用MFC实现打印功能的一个框架结构,对于使用者只要使用该结构就可以按照自己的要求打印任何内容。

关键词:Visual C++ ,MFC,对话框, 打印 ,打印预览


引言

  打印程序的编写在windows程序设计中非常有用,针对不同的用户需要,通常用sdk方式实现打印代码量比较大,而且要对打印流程的底层有非常清楚的了解,需要一个程序员有非常深入的打印方面的知识,利用MFC提供的文档视图结构,不但可以实现一些常用的标准界面元素,把数据的处理的界面的处理分离出来,而且其提供的打印功能更是方便快捷,功能强大。打印程序的编写本质是是一种GDI绘图,只是绘图的对象是在打印机的设备描述表,如果对于屏幕的GDI绘图比较熟悉的读者,相信掌握打印程序的编写应该比较容易。
 

猜你喜欢

转载自blog.csdn.net/zb774095236/article/details/85122510