1.2.3 MFC的打印功能分析
1.框架中的打印
MFC的框架内置了功能强大的打印和打印预览功能。首先,分析MFC应用程序框架打印的内在机制,
这样将能更有效地使用打印机。
在视图类及其派生类中,通过重载OnDraw(CDC*pDC)函数,利用它提供的pDC(设备上下文)指针,可以在
屏幕上显示各种图形和数据。CView类的打印是通过OnPrint(CDC *pDC,CPrintInfo*pInfo)这个函数实现
的,OnPrint()函数对打印的实现就是简单的调用OnDraw(CDC*pDC)这个函数,把打印机的设备上下文
指针pDC传递给OnDraw(CDC *pDC)函数。可见CView类对输出到屏幕和输出到打印机的处理都是一样的,
只是换了一个设备上下文而已(输出到屏幕时是通过OnPaint()函数实现的)。
如果重载OnPrint()函数,可以选择根本不调用OnDraw来支持打印逻辑。OnPrint有两个参数,一个是指向
打印机设备环境的指针,一个是打印信息对象CPrintInfo的指针,该信息包括纸张大小、当前页码和最大
页码。对每个需要打印的页,应用程序框架都要调用一次OnPrint(),在OnPrintInfo结构中记录着当前页码。
视图类及其派生类在进行显示和打印之前都会调用virtual void OnParepareDC(CDC *pDC,CPrintInfo* pInfo = null)
这个虚成员函数来准备设备上下文,可以重载这个虚成员函数,进行坐标转换。下面是MFC框架打印的基本流程:
OnPreparePrint---->设置起始和终止页
|
OnBeginPrinting---->创建GDI对象
|
OnPrepareDC(每页)---->设置映射模式,并选择检测打印任务的结尾
|
OnPrint(每页)----->打印输出
|
是否打印完毕
|
y
OnEndPrinting--->删除GDI对象
CDC::GetDeviceCaps 获得指定设备的信息。
int GetDeviceCaps(int nIndex)const;
返回值:如果成功,则返回需要的能力值。
LOGPIXELSX:沿显示宽度方向,每一逻辑单位的像素数。
LOGPIXELSY:沿显示高度方向,每一逻辑单位的像素数。
void CPrintProjView::OnDraw(CDC *pDC)
{
pDC->TextOut(400,200,_T("Hello word"));
}
BOOL CPrintProjView::OnPreparePrinting(CPrintInfo*pInfo)
{
pInfo->SetMaxPage(2);
return DoPreparePrinting(pInfo);
}
void CPrintProjView::OnPrepareDC(CDC *pDC,CPrintInfo *pInfo)
{
CView::OnPrepareDC(pDC,pInfo);
pDC->SetMapMode(MM_ANISOTROPIC); //转换坐标映射方式
CSize size = CSize(800,560);
pDC->SetWindowExt(size);//确定窗口的大小
//得到实际设备每逻辑英寸的像素数量
int xLogPixelPerInch = pDC->GetDeviceCaps(LOGPIXELSX);
int yLogPixelPerInch = pDC->GetDeviceCaps(LOGPIXELSY);
//得到设备坐标和逻辑坐标的比例
long xExt = (long)size.cx * xLogPixelPerInch / 96;
long yExt = (long)size.cy * yLogPixelPerInch / 96;
pDC->SetViewportExt((int)xExt,(int)yExt); //设置视口大小
}
在上面的程序中,首先将坐标映射方式改变为MM_ANISOSTROPIC方式,即各向异性的意思,
在这种坐标方式下,x轴和y轴的逻辑单位可以进行任意的缩放。改变坐标映射方式后,就要
确定窗口大小和视口大小,注意窗口大小就是我们在屏幕上所见的尺寸,,而视口大小是实际
设备,如打印机等,和显示设备每逻辑英寸的像素数量比较所得的比例尺.
通过函数得到显示器和打印机每逻辑英寸的像素数量,然后对视口大小进行相应的缩放,就可以
使屏幕上的显示和打印机的输出是一致了.
编译、运行程序,使用打印预览功能,会看到在两个视图和预览两个页面中"Hello word"的位置是
一样的。
下次在看的时候,将这一部分再串一下。注意下面几点。
1、只有在MM_ANISOTROPIC和MM_ISOTROPIC映射模式下,SetWindowExt和SetViewportExt才起作用。
MM_ANISOTROPIC和MM_ISOTROPIC的区别是:
MM_ANISOTROPIC是不锁定纵横比,也就是x和y各向异性,x和y轴的逻辑单位可以任意缩放。
MM_ISOTROPIC:是锁定纵横比,x和y轴的比值是1:1
2、SetWindowExt()设置窗口的x和y轴范围,也就是窗口的大小
SetViewportExt()设置视口的x和y轴范围,也就是视口的大小
3、窗口大小是指:我们在屏幕上所见的尺寸
视口大小是指:实际设备,如打印机等.
2、框架之外的打印
框架实现了对打印的一些底层支持,直接的打印机制是通过函数StartDoc和EndDoc()来实现的。应用程序
要使用打印机时,它首先使用CreateDC或PrintDlg来获取指向打印机设备环境的一个句柄,这就使得打印机
设备驱动程序库模块被加载到内存(如果还没有加载到内存的话),并进行初始化。然后,程序调用StartDoc
函数,通知一个新文档开始了。StartDoc函数是由GDI模块来处理的。GDI模块调用打印机设备驱动程序中的
control函数告诉打印机准备打印。
打印一个文档的过程以StartDoc调用开始,以EndDoc调用结束。调用StartPage来开始一页,调用EndPage来
结束该页。
下面这段代码在对话框中实现了对打印的支持。
/*
GetPrinterDC 获取设备环境的句柄。
HDC GetPrinterDC()const;
返回值:
如果成功则返回一个打印机设备环境的句柄;否则返回null.
说明:
如果CPrintDialog构造函数的参数bPrintSetupOnly是FALSE(表明显示的是Print对话框,则GetPrinterDC返回一个
打印机设备环境句柄。当你使用完这个设备环境时,你必须调用Windows DeleteDC函数来删除它。)
*/
/*
{
long cbSize,
CString lpszDocName,
CString lpszOutput
} DOCINFO;
对文档进行定义的一个结构。
cbSize:结构的大小
lpszDocName:文档的名字
lpszOutput:输出文档的名字
*/
/*
StartDoc:开始新的打印作业
CDC::StartDoc
int StartDoc(LPDOCINFO lpDocInfo);
返回值:如果出错,例如存储空间不足或指定端口无效,则返回-1否则返回正值。
参数:
lpDocInfo: DOCINFO结构的指针。该结构包含了文档文件和输出文件的名字。
说明:
通知设备的驱动程序开始一个新的打印作业,其后所有的StartPage和EndPage调用处于假
脱机状态,直到EndDoc调用出现。这确保了长于一页的文档不被其它作业中断。
*/
/*
EndDoc 结束由StartDoc成员函数启动的打印作业。
CDC::EndDoc
int EndDoc();
返回值:
如果成功,则返回值大于零或等于零,出错则返回值小于零。下面列出了一般的错误
类型:
SP_ERROR:一般错误。
SP_OUTOFDISK:假脱机所需的磁盘空间不足,没有其它可用的磁盘空间。
SP_OUTOFMEMORY:假脱机所需的内存不足。
SP_USERABORT: 用户在打印管理中中止作业。
说明:
中止由StartDoc成员函数调用的打印作业。在成功完成打印作业后应立即调用。
如果应用遇到打印错误或取消的打印操作,决不可用EndDoc或AbortDoc去中止
操作,GDI在返回错误值之前自动中止操作。
*/
/*
StartPage:通知设备的驱动程序开始新页。
CDC::StartPage
int StartPage
说明:
调用该成员函数使用打印机驱动程序做好准备接收数据。在StartPage和EndPage之间,
ResetDC成员函数不起作用。
*/
/*
EndPage:通知打印机驱动程序打印页结束。
CDC::EndPage
int EndPage()
返回值:如果成功,则返回大于或等于零的值,如果失败则返回如下错误类型:
SP_ERROR:一般错误
SP_APPABORT: 作业终止
SP_USERABORT:用户在打印管理中中止作业。
SP_OUTOFDISK:假脱机所需的磁盘空间不足。
SP_OUTOFMEMORY:假脱机所需的内存不足。
说明:
通知设备已经写完一页。该成员函数通常用在打印机驱动程序开始新的一页。
*/
/*
AbortDoc:终止当前打印任务,擦除自上次调用StartDoc成员函数以业写入设备的任何内容。
CDC::AbortDoc
int AbortDoc();
返回值:如果成功,则返回大于或等于零的值,如果出现错误,则为负值。与EndPage和
EndDoc返回的错误类型值一样。
说明:
终止当前打印任务,并擦除自上次StartDoc以后写入设备的任何任务。
*/
/*
CPrintInfo没有基类。
CPrintInfo存储有关一次打印或打印预览的信息。每次选择Print或PrintPreview命令,框架
就会创建一个CPrintInfo对象。并在命令完成时删除此对象。
CPrintInfo包含打印时的一般信息,例如:要打印页的范围,打印机的状态,当前正在打印的页
这些信息存放在CPintInfo的对象中;此对象还包括在CPrint对话框中输入的值。
在打印期间,一个CPrintInfo对象在框架和视图类之间传递,并且用于两者之间交换信息。例如:
框架通过对CPrintInfo类的m_nCurPgae成员赋值,来通知视图类要打印文档的哪一页,视图类检索
此值,并执行指定页的实际打印。
另一个例子就是文档的长度到打印的时候也不知道多少页。视图类每打印一页都要检测是否到了文档的
末尾。当到达文档的末尾时,视图类将CPrintInfo的m_bContinuePrinting成员设置为FALSE,通知框架
停止打印循环。
*/
/*
Attach:把Windows设备上下文句柄附加在CDC对象上。
CDC::Attach
BOOL Attach(HDC hDC);
返回值:如果成功,返回非零值,否则为0
参数:
hDC:Windows设备上下文。
说明:
使用这个函数把hDC附加到CDC对象上。
*/
/*
Detach:从CDC对象中分离出Windows设备上下文。
CDC::Detach
HDC Detach()
返回值:Windows设备上下文句柄。
说明:
调用该函数将m_hDC从CDC对象中分离出来。并将m_hDC与m_bAttribDC设备为NULL。
*/
void CPrintProj::Print()
{
CDC dc;
CPrintDialog printDlg(FALSE);
//利用CPrintDialog生成打印机设备环境
if(printDlg.DoModual() == IDCANCEL) //让用户选择打印纸张等
return;
dc.Attach(printDlg.GetPrinterDC());//让Handle连接到dc上.
dc.m_bPrinting = TRUE;
CString strTitle;
strTitle.LoadString(AFX_IDS_APP_TITLE);
DOCINFO di; //DOCINFO中有相关的打印信息
::ZeroMemory(&di,sizeof(DOCINFO));
di.cbSize = sizeof(DOCINFO);
di.lpszDocName = strTitle; //设置标题
BOOL bPrintingOK = dc.StartDoc(&di); //开始打印
CPrintInfo Info;
Info.m_rectDraw.SetRect(0,0,dc.GetDeviceCaps(HORZRES),dc.GetDeviceCaps(VERTRES)); //设置范围.
OnBeginPrinting(&dc,&Info); //调用你自定义的打印功能.
fo(UINT page = Info.GetMinPage();page < Info.GetMaxPage() && bPrintOK;page++)
{
Info.m_nCurPage = page;
OnPrint(&dc,&Info); //调用你的"Print page"函数
bPrintOK = dc.EndPage() > 0; //结束页
}
OnEndPrinting(&dc,&Info);//结束打印.
if(bPrintingOK)
dc.EndDoc();
else
dc.AbortDoc();
dc.Detach();
}
说明:其实在Windows环境中是设备无关的.只要有了DC,就可以使用各种GDI函数,而不需要理会是在屏幕或是在
打印机上绘图.