现在的位置: 首页 > 综合 > 正文

MFC打印

2013年09月05日 ⁄ 综合 ⁄ 共 9549字 ⁄ 字号 评论关闭

 

 

 部分来自 阿飞家园(sohu博客)加入自己整理内容

 

MFC的文档/视图结构使我们的打印工作变得更加便捷,为了便于明晰打印流程,让我们了解一下MFC在背后“偷偷”为我们做了哪些工作。

1> 创建打印设备描述表并在打印结束后删除它

2> 调用StartDocEndDoc来开始和结束打印工作

3> 调用StartPageEndPage来开始和结束每一页

在文档/视图结构的程序中实现打印的关键是一组CView类的虚函数,主框架要在打印处理的不同阶段调用他们,实际使用时我们可以根据需要覆盖这些函数,这里我建议用MFCClass Wizard把下边的5个函数全部覆盖。

1> OnPreparePrinting

            在设置打印参数时调用,可以给主框架提供打印页数和其他与打印有关的信息

2> OnBeginPrinting

            在打印开始前调用,可以用来分配字体和其他打印需要的资源

3> OnPrepareDC

在每一页打印之前调用,可以用来指定视口原点位置并在OnDraw打印下页之前设置剪贴区域

4> OnPrint

在每一页打印时调用,可以用来打印页眉、页角以及其他没有用OnDraw或者不依赖OnDraw打印的页面元素

5> OnEndPrinting

            打印结束后调用,可以用来释放OnBeginPrinting中分配的资源

实现MFC应用程序打印功能有两种方法,一种是让OnDraw函数既处理屏幕输出又处理打印输出;另一种是让OnDraw处理屏幕输出而让OnPrint处理打印输出。用OnDraw函数处理打印的话必然会添加很多打印特有的代码段,而且为了让打印功能更加完备,通常必需要在OnPrint函数中添加打印页眉、页角等代码,使得程序看起来比较凌乱。依我个人的经验,打印和屏幕输出有很多应该是不同的,比如在屏幕输出时我们可以设置很多背景和线条颜色,如果打印也这么设置的话必然会造成打印输出缓慢(要打印的象素多),有时打印机是黑白的,再多色彩打出来也就那么两三种颜色,得不偿失。当然打印图片得程序是个例外。我们通常更实际得把打印机输出得程序段放在OnPrint中,把屏幕输出程序段放在OnDraw中,我们这里仅介绍这种方法。

1. OnPreparePinting函数

   BOOL OnPreparePrinting(CPrintInfo* pInfo)

   {

       pInfo->SetMaxPage(10);

       pInfo->m_bPreview = True(预览,不显示对话框)/False(打印,显示打印对话框);

       return DoPreparePrinting(pInfo);

   }

   参数pInfo包含了很多打印工作参数的信息,包括最小和最大页数,默认为10xFFFF0xFFFF告诉主框架最大页数还不知道。MFC会在打印对话框的To框中显示最大页编码。CPrintInfo类有很多函数设置打印参数,具体可参考MSDN

   注意:GetFromPageGetToPage返回用户在“打印”对话框中选择的页码范围,由于是在DoPreparePrinting之后显示打印对话框,所以要注意在DoPreparePrinting函数之后调用他们。

 

2. OnBeginPrintingOnEndPrinting函数

   实际上打印的页数通常由页面大小来决定,而我们调用OnPreparePinting函数时通常是不知道的,如果在   OnPreparePinting函数中没有设置最大页数,可以在OnBeginPrinting函数中设置,传给OnBeginPrinting函数的是一个已经初始化完成的CPrintInfo结构,在OnBeginPrinting函数中可以调用CDC::GetDeviceCaps两次来确定可打印页区域的尺寸,一次用HORZRES参数调用,一次用VERTRES参数调用。

   void CMyView::OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo)

   {

       CPrintDemoDoc* pDoc = GetDocument();

            ASSERT_VALID(pDoc);

            int npageheight = pDC->GetDeviceCaps(VERTRES);

            int npagewidth = pDC->GetDeviceCaps(HORZRES);

            int nmaxpage = max(1,(pDoc->m_nDocLength+npageheight-1)/npageheight);

            pInfo->SetMaxPage(nmaxpage);

   }

   注意:OnBeginPrinting中的pInfo结构中有一个m_rectDraw描述了可打印页面区域,但是OnBeginPrinting函数返回之前,该变量是不会被初始化的,也就是说不能在OnBeginPrinting函数中使用该变量。

   此外,最好在OnBeginPrinting中创建字体和打印中要用到的其他GDI资源,OnEndPrinting函数时和OnBeginPrinting函数配对使用的,用来释放OnBeginPrinting函数创建的资源。例子,见后文.

  

3. OnPrepareDC函数

如果在OnBeginPrinting中仍然无法知道要打印文档的页数,可以在打印每页前所调用的OnPrepareDC函数中设CPrintInfo结构的m_bContinuePrinting变量的值来执行打印时分页。m_bContinuePrinting=TRUE时继续打印,m_bContinuePrinting=FALSE时结束打印工作。如果不调用SetMaxPage函数设置最大页数,系统默认文档只有一页。所以如果由多页文档且页数不确定,我们就必须用m_bContinuePrinting的值来控制多页打印。也就是说,我们重载这个函数后,判断文档是否结束,如果结束 将该值赋为FALSE,这样框架就会停止打印.

       打印文档的每一页之前都会调用OnPrepareDC函数,另一个需要再该函数完成的功能是根据当前页编号计算新的视口原点以便OnDraw函数可以将当前页输出到打印机上,这里我们不做过多描述。

 

4. OnPrint函数

       每一页打印时框架自动调用OnPrint函数,如果调用OnDraw函数完成打印输出,可以在此编写打印页眉和页角的代码;如果用OnPring函数来完成打印输出,就需要包含输出打印内容的程序代码。要想知道当前打印的是哪一页,可查询CPrintInfom_nCurPage

 

 

 

 

例子:来源与河源下载网

VC5.0 为Windows 的程序员提供了一个很好的C++ 开发环境,减少了很多编程负担,但同时也为我们在程序中加入自己的思想增加了难度。本人在一软件开发中,想控制文字打印时的字体,使字体大小缩小一倍,以节省打印纸。经过一段时间的摸索,终于解决了这一问题,下面分几步向大家做一介绍。

---- 一、对VC5 自动生成的程序框架进行改进

---- 这里用VC5 自动创建一个例子程序Test,单文档界面,注意在最后一步修改view 的继承类为CEditView。

---- 在view 类中,VC5 已经自动创建了三个用于支持打印的函数:OnPreparePrinting,OnBeginPrinting,OnEndPrinting。为了实现我们的功能,需要再继承以下几个函数:OnPrepareDC,OnPrint。并将OnPrepareDC 和OnEndPrinting 改为如下实现:

// OnPrepareDC()
void CTestView::OnPrepareDC
(CDC* pDC, CPrintInfo* pInfo)
{
CView::OnPrepareDC(pDC, pInfo);
}

// OnEndPrinting()
void CTestView::OnEndPrinting
(CDC* pDC, CPrintInfo* pInfo)
{
CView::OnEndPrinting(pDC, pInfo);
}
---- 用CView 来替代原来的CEditView,用以避免CEidtView 对打印的控制。控制字体及输出的功能主要在OnBeginPrinting 和OnPrint 两个函数来实现。

---- 二、实现OnBeginPrinting 函数

---- 根据VC5 编程机制,在OnBeginPrinting 函数实现打印前的准备工作,包括设置打印字体,根据打印机当前页面尺寸计算所需页数等。下面的程序是对打印字体的重新设置和计算所需打印纸页数。

---- 程序中首先取得打印机的横向和纵向分辨率,再得到当前打印字体的大小,然后计算出新的字体大小,为默认字体的一半。读者可以根据需要设定自己的打印字体大小。

---- 接着,取得当前打印纸的宽度和高度,再根据新字体的宽度和高度计算出每行的最大字符数和每页的最大行数。

---- 由于打印文件中有些行的宽度可能超过每行的最大字符数,所以程序中调用函数RedealTextData() 对打印文件进行重新整理,函数的实现在下面介绍。

---- 最后,程序中计算并设置所需的打印页数。

OnBeginPrinting()函数实现如下:
//====================================
// OnBeginPrinting
//====================================
void CTestView::OnBeginPrinting(CDC* pDC,
CPrintInfo* pInfo)
{
//设置新的? 字体////////////////

//取打印机的横方向和纵方向的分辨率
//即每英寸点数
short cxInch = pDC- >GetDeviceCaps(LOGPIXELSX);
short cyInch = pDC- >GetDeviceCaps(LOGPIXELSY);

// 取当前字体大小
CFont *curFont = pDC- >GetCurrentFont();
LOGFONT curLogFont;
LOGFONT newLogFont;

curFont- >GetLogFont( &curLogFont );
long NewFontWidth = curLogFont.lfWidth;
long NewFontHeight = curLogFont.lfHeight;
newLogFont = curLogFont;

//计算新的字体大小--缩小一倍
newLogFont.lfWidth =(long)((float)NewFontWidth/2.0
* ((float)cxInch / 72.0));
newLogFont.lfHeight =(long)((float)NewFontHeight/2.0
* ((float)cyInch / 72.0));

//创建并设置新的字体,保留以前的字体
CFont newFont;
CFont *oldFont;

newFont.CreateFontIndirect(&newLogFont);
oldFont = pDC- >SelectObject(&newFont );
/////////////////////////////////
//根据字体宽度、高度计算
//每行最大字数及每页最大行数

//取打印纸张高度和宽度
int nPageHeight, nPageWidth;
nPageHeight = pDC- >GetDeviceCaps(VERTRES);
nPageWidth = pDC- >GetDeviceCaps(HORZRES);

TEXTMETRIC TextM;
pDC- >GetTextMetrics(&TextM);
//字体高度
m_LineHeight = (unsigned short)TextM.tmHeight;
//字体平均宽度
m_CharWidth=(unsigned short)
TextM.tmAveCharWidth;

//每行最大字数
m_MaxLineChar = nPageWidth / m_CharWidth - 8;
//每页最大行数
m_LinesPerPage = nPageHeight/ m_LineHeight;
//根据每行最大字数对文字进行重新调整
RedealTextData();
//////////////////////////////////////
//计算所需打印纸张数目
int nPrintableLineCount = INT_MAX/m_LineHeight;

// m_lines为文件总行数
if (m_lines < nPrintableLineCount)
nPrintableLineCount = m_lines;
unsigned short MaxPage = (nPrintableLineCount
+ m_LinesPerPage - 1)
/ m_LinesPerPage;
//设置所需打印纸张数目
pInfo- >SetMaxPage(MaxPage);
pInfo- >m_nCurPage = 1;

//////////////////////////////////////////
//最后不要忘记将字体还原,这一句是必需的
pDC- >SelectObject(oldFont );
}
---- RedealTextData 函数根据每行最大宽度对文件进行重新调整。主要是计算文件中每行的宽度,如果超过最大宽度则加入换行符(0x0d,0x0a)。函数实现如下:

//=======================================
// RedealTextData
//注:
//pDoc- >buffer为文件缓冲区
//pDoc- >file_length为文件字节长度
//pDoc- >TextLines为文件原行数
//pDoc- >MaxLineLength为文件原最大行字节宽度
//=======================================
void CTextView::RedealTextData()
{
CDocViewDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);

short LineLengthMax = m_MaxLineChar;
unsigned short lines=0;
unsigned long i,j;

//申请新的缓冲区保存调整后的文件
long size = pDoc- >file_length + pDoc- >TextLines*
(pDoc- >MaxLineLength/m_MaxLineChar+1);
m_newBuffer = new char [size ];
LPSTR newTempPtr = m_newBuffer;
m_file_length =pDoc- >file_length;
//保存文件新的行数
m_lines = 1;
i = 0;
//记录当前行的宽度
short theLineLength=0;
//记录当前行中汉字字节数,
//以防止将一半汉字分为两行
unsigned short halfChinese=0;

while(i < pDoc- >file_length)
{
*newTempPtr++ = pDoc- >buffer[i];

j=i+1;
if( (pDoc- >buffer[i] == 0x0d && pDoc- >buffer[j] == 0x0a))
{
m_lines++;
theLineLength = 0;
}
else
{
//如果是TAB字符,宽度加8
if(pDoc- >buffer[i] == VK_TAB)
theLineLength += 8;
else
{
//大于0xa1的字节为汉字字节
if((unsigned char)pDoc- >buffer[i] >= 0xa1)
halfChinese++;
theLineLength++;
}
//如果行宽大于每行最大宽度,进行特殊处理
if(theLineLength > LineLengthMax)
{
char buff[256];
short m=255;
newTempPtr--;
if((unsigned

char )*newTempPtr < 0xa1)
{
//如果当前字符的前一个字符是数字、
//字母或一些特殊的前置符号时,
//指针循环向前取,
//以防止将一个单词分为两行。
while((*newTempPtr >=0 && *newTempPtr< =9)||
(*newTempPtr >=a && *newTempPtr < = z) ||
(*newTempPtr >=A && *newTempPtr < = Z) ||
*newTempPtr == _ || *newTempPtr == * ||
*newTempPtr == ^ || *newTempPtr == ~ )
buff[m--] = *newTempPtr--;
}
else //汉字
{
//防止将一个汉字分为两行。
if(halfChinese%2)
buff[m--] = *newTempPtr--;
}
newTempPtr++;
//加入换行符,分为两行
*newTempPtr++ = 0x0d;
*newTempPtr++ = 0x0a;
for(short k=m+1; k< 256; k++)
*newTempPtr++ = buff[k];

m_lines++;
theLineLength = 0;
m_file_length += 2;
}
}
i++;
}
}
---- 三、实现OnPrint 函数

---- 在OnPrint 函数中实现真正的文字输出,主要功能包括设置打印字体大小,计算当前页号文字输出位置,以及文字的输出打印。

---- 程序中首先计算打印区域,文字在这个打印区域内输出。然后设置新的打印字体。

---- 由于OnPrint 函数是每打印一页被调用一次,所以需要根据当前打印页号计算出当前页的文字在整个文字缓冲区的起始偏移量和终止偏移量。这里程序中调用了函数GetOffset(),此函数在下面介绍。

---- 最后调用Windows 的DrawText() 函数实现文字的输出。

OnPrint()函数实现如下:
//====================================
// OnPrint
//========================================
void CTestView::OnPrint(CDC* pDC,
CPrintInfo* pInfo)
{
//计算打印区域//////////////////
long yTopOfPage =(pInfo- >m_nCurPage -1) *
m_LinesPerPage * m_LineHeight;

//左边空出两个字符宽度
pDC- >SetViewportOrg(m_CharWidth * 2,
-yTopOfPage);

int nPageWidth = pDC- >GetDeviceCaps(HORZRES);
CRect rectClip = CRect(0,
yTopOfPage,
nPageWidth,
yTopOfPage + m_LinesPerPage *
m_LineHeight);

/////设置缩小字体///////////////////
//取打印机的横方向和纵方向的分辨率
//即每英寸点数
short cxInch=pDC- >GetDeviceCaps(LOGPIXELSX);
short cyInch= DC- >GetDeviceCaps(LOGPIXELSY);

//取当前字体大小
CFont *curFont = pDC- >GetCurrentFont();
LOGFONT curLogFont;
LOGFONT newLogFont;

curFont- >GetLogFont( &curLogFont );
long NewFontWidth = curLogFont.lfWidth;
long NewFontHeight = curLogFont.lfHeight;
newLogFont = curLogFont;

//计算新的字体大小--缩小一倍
newLogFont.lfWidth = (long)((float)NewFontWidth/2.0
* ((float)cxInch / 72.0));
newLogFont.lfHeight = (long)((float)NewFontHeight/2.0
* ((float)cyInch / 72.0));

//创建并设置新的字体,保留以前的字体
CFont newFont;
CFont *oldFont;
newFont.CreateFontIndirect(&newLogFont);
oldFont = pDC- >SelectObject(&newFont );

/////文字打印输出/////////////////
unsigned short CurrentStartLine ,
CurrentEndLine;
long StartPrintOffset,
EndPrintOffset,
PrintSize;
LPSTR tempPtr;
RECT rect1,rect2;
//根据当前打印页号计算文字起始行
CurrentStartLine=(pInfo- >m_nCurPage-1) * m_LinesPerPage;
//文字终止行
CurrentEndLine = CurrentStartLine+m_LinesPerPage;

if(CurrentEndLine > m_lines)
CurrentEndLine = m_lines;
//计算打印文字的起始位置和终止位置
StartPrintOffset=GetOffset(m_newBuffer,
m_file_length, CurrentStartLine);
EndPrintOffset = GetOffset(m_newBuffer,
m_file_length,CurrentEndLine);

PrintSize = EndPrintOffset - StartPrintOffset;

tempPtr = m_newBuffer + StartPrintOffset;
//文字输出
pDC- >DrawText(tempPtr, PrintSize,
&rectClip,
DT_NOCLIP |DT_NOPREFIX
|DT_EXPANDTABS);

//还原旧的打印字体
pDC- >SelectObject(oldFont );
}
---- 程序中的GetOffset 函数是根据给定的行号计算文字的位置,其实现如下:

//========================================
// GetOffset ()
//========================================
long CTestView::GetOffset(LPSTR buffer,
long buffer_length,
unsigned short StartLine)
{
if(StartLine == 0) return 0;

unsigned short lines=0;
long i,j;

i = 0;
while(i < buffer_length)
{
j=i+1;
if( (buffer[i++] == 0x0d && buffer[j] == 0x0a))
{
lines++;
if(lines == StartLine)
return i;
}
}
return buffer_length;
}
---- 以上是本人在编程中的一点心得,欢迎和大家共同交流。

抱歉!评论已关闭.