windows编程技术11GDI编程4图元文件与打印.docx
《windows编程技术11GDI编程4图元文件与打印.docx》由会员分享,可在线阅读,更多相关《windows编程技术11GDI编程4图元文件与打印.docx(44页珍藏版)》请在冰点文库上搜索。
windows编程技术11GDI编程4图元文件与打印
第11章GDI编程4—图元文件与打印
图元文件是一种矢量图形文件,用于绘图指令序列的文件存储和播放再现。
与点阵图像相比,图形文件所占的空间小,且可任意缩放而不产生马赛克效应,但是绘制图形需要一定的时间。
图元文件还可用于交互绘图中的图形重绘和图形软件中的图元编辑。
GDI除了支持图形的屏幕绘制外,也支持图形的打印输出。
使用传统的API进行Windows的打印编程,异常复杂和艰难。
而MFC对打印功能的封装,大大简化了程序员的打印编程工作。
本章将对使用MFC进行图元文件和打印编程的具体方法分别做一些简单介绍。
11.1图元文件
GDI从一开始就支持(图)元文件(metafile),早期(1985年起)的版本为WMF(WindowsMetaFile,视窗元文件),主要针对Win16(Win3.x),后来(1990年起)也支持Win32(Win95/98/Me)。
以后(1993年)随WindowsNT推出了改进的图元文件版本——EMF(EnhancedWindowsMetaFile,增强型视窗元文件),只支持Win32(Win95/98/Me/NT/2000/XP/Vista/7)。
现在(2001年起)又随WindowsXP和GDI+推出了加强型图元文件EMF——EMF+,可以同时支持GDI和GDI+。
表11-1图元文件所支持的GDI类型
元文件类型
Win16GDI
Win32GDI
Win32/64GDI+
WMF
√
√
×
EMF
×
√
×
EMF+
×
√
√
本节只介绍GDI中的图元文件格式WMF和EMF,重点介绍在MFC中使用图元文件。
至于GDI+中的图元文件格式EMF+和如何在GDI+编程中使用图元文件及其相关类,将在14.4节中介绍。
11.1.1图元文件格式
下面分别介绍WMF和EMF的具体文件格式。
1.WMF文件格式
●元文件结构——WMF文件由文件头和若干元文件记录组成,参见图11-1。
视窗元文件头
元文件记录
……
图11-1WMF的文件结构
●元文件头——WMF的文件头用结构METAHEADER定义:
typedefstructtagMETAHEADER{
WORDmtType;//元文件类型:
内存=0、磁盘文件=1
WORDmtHeaderSize;//文件头大小的字数
WORDmtVersion;//系统的版本号:
支持DIB=0x0300、不支持DIB=0x0100
DWORDmtSize;//文件大小的字数
WORDmtNoObjects;//同时存在于元文件内的最大对象数
DWORDmtMaxRecord;//元文件中最大记录的字大小
WORDmtNoParameters;//保留字段
}METAHEADER;
●元文件记录——WMF的元文件记录用结构METARECORD定义:
typedefstructtagMETARECORD{
DWORDrdSize;//记录大小的字数
WORDrdFunction;//功能/函数号(记录类型META_XXX)
WORDrdParm[1];//函数参数数组,逆序排列
}METARECORD;
WMF中的图元文件记录类型共有67种,包含所有的绘图设置和操作(DC函数),例如:
设置二元光栅操作(符号常量META_SETROP2,对应数值0x0104)、绘制矩形(META_RECTANGLE,0x041B)和输出文本(META_TEXTOUT,0x0521)等。
2.EMF文件格式
●元文件结构——EMF文件由文件头、可选的描述串和调色板、及若干元文件记录组成,参见图11-2。
增强型元文件头
[描述串]
[调色板]
元文件记录
……
图11-2EMF的文件结构
●元文件头——EMF的文件头用结构ENHMETAHEADER定义(比WMF的复杂得多):
typedefstructtagENHMETAHEADER{
DWORDiType;//记录类型,必须=EMR_HEADER(=1)
DWORDnSize;//结构的字节大小,可能>sizeof(ENHMETAHEADER)
RECTLrclBounds;//边界矩形(设备单位,含右边和底边)
RECTLrclFrame;//边界矩形(0.01毫米单位HIMETRIC,含右边和底边)
DWORDdSignature;//签名,必须=ENHMETA_SIGNATURE(=0x464D4520)
DWORDnVersion;//元文件版本,当前=0x10000
DWORDnBytes;//元文件的字节大小
DWORDnRecords;//元文件中的记录数
WORDnHandles;//元文件句柄表中的句柄数(第0个句柄被保留)
WORDsReserved;//保留,必须=0
DWORDnDescription;//描述字符数组中的字符数,无描述串时必须设为0
DWORDoffDescription;//描述串相对于文件头开始处的偏移量,无描述串时必须设为0
DWORDnPalEntries;//元文件内调色板中的表项数
SIZELszlDevice;//以像素为单位的参考设备分辨率
SIZELszlMillimeters;//以毫米为单位的参考设备分辨率
#if(WINVER>=0x0400)//Win95/WinNT4.0以上
DWORDcbPixelFormat;//像素格式,无像素格式设置时=0、
//开始设置为DC时=size0f(PIXELFORMATDESCRIPTOR)、
//有多个单像素格式设置时=指向最后一个像素格式头的指针
DWORDoffPixelFormat;//像素格式的偏移量
DWORDbOpenGL;//元文件中是否包含OpenGL记录
#endif/*WINVER>=0x0400*/
#if(WINVER>=0x0500)//Win98/Win2000以上
SIZELszlMicrometers;//以微米表示的参考设备大小
#endif/*WINVER>=0x0500*/
}ENHMETAHEADER;
其中:
typedefstruct_RECTL{LONGleft;LONGtop;LONGright;LONGbottom;}RECTL;
typedefstructtagSIZE{LONGcx;LONGcy;}SIZE;typedefSIZESIZEL;
●元文件记录——EMF的元文件记录用结构ENHMETARECORD定义(与WMF的METARECORD类似,只是交换了大小和类型的顺序):
typedefstructtagENHMETARECORD{
DWORDiType;//记录类型(功能/函数号EMR_XXX)
DWORDnSize;//记录的字节大小
DWORDdParm[1];//传递给GDI函数的参数数组
}ENHMETARECORD;
EMF中的图元文件记录类型共有121种(几乎是WMF的两倍),包含Win32GDI的所有绘图设置和操作,还包含若干兼容16位GDI的DC函数。
例如:
设置二元光栅操作(符号常量EMR_SETROP2,对应数值20)、绘制矩形(EMR_RECTANGLE,43)、输出文本(EMR_EXTTEXTOUTW,84)和绘制多边形(EMR_POLYPOLYGON/EMR_POLYPOLYGON16,8/91)等。
11.1.2图元文件的MFC编程
可以直接利用WindowsSDK中的有关API函数进行图元文件编程,但是限于篇幅,这里只介绍如何利用MFC的CMetaFileDC类进行图元文件的编程。
CMetaFileDC是CDC的派生类,是对图元文件功能的封装。
CMetaFileDC类很简单,只有一个默认构造函数——CMetaFileDC、两个具体创建函数——Create(创建WMF)和CreateEnhanced(创建EMF)、两个关闭函数——Close(关闭WMF)和CloseEnhanced(关闭EMF),其他主要是使用其父类CDC的成员函数。
1.创建CMetaFileDC对象
创建CMetaFileDC对象分两步进行,首先调用CMetaFileDC类的唯一(默认)构造函数来构造一个空对象,然后再调用该类的Create成员函数来创建一个WMF图元文件对象、或调用CreateEnhanced成员函数来创建一个EMF图元文件对象。
下面是这些函数的原型:
CMetaFileDC();
BOOLCreate(LPCTSTRlpszFilename=NULL);
BOOLCreateEnhanced(CDC*pDCRef,LPCTSTRlpszFileName,
LPCRECTlpBounds,LPCTSTRlpszDescription);
其中:
●pDCRef——参考DC,一般取为当前视图类的指针。
为NULL时,使用当前显示设备作为参考DC。
●lpszFilename——图元文件名串的指针,如果为NULL,则创建内存图元文件。
●lpBounds——边界矩形(单位是0.01毫米),可以是RECT结构或CRect对象的指针。
为NULL时,取包含用户图形的最小矩形。
●lpszDescription——描述字符串,一般包含应用程序名和图名,也可以为NULL。
例如:
CMetaFileDCmetaDC;//构造元文件DC空对象
//metaDC.Create(NULL);//创建内存WMF元文件DC
//metaDC.Create(L"test.wmf");//创建WMF元文件DC
//创建内存EMF元文件DC
//metaDC.CreateEnhanced(NULL,NULL,NULL,NULL);
//获取屏幕大小
HDChdcRef=GetDC()->m_hDC;
intiWidthMM=GetDeviceCaps(hdcRef,HORZSIZE);//屏幕宽(毫米)
intiHeightMM=GetDeviceCaps(hdcRef,VERTSIZE);//屏幕高(毫米)
intiWidthPels=GetDeviceCaps(hdcRef,HORZRES);//屏幕宽(像素)
intiHeightPels=GetDeviceCaps(hdcRef,VERTRES);//屏幕高(像素)
//获取客户区大小,并将像素坐标转换为0.01毫米坐标
RECTrect;
GetClientRect(&rect);//像素坐标
rect.right=(rect.right*iWidthMM*100)/iWidthPels;
rect.bottom=(rect.bottom*iHeightMM*100)/iHeightPels;
metaDC.CreateEnhanced(GetDC(),L"test.emf",&rect,
L"MFCDrawMixGraphics");//创建EMF元文件DC
2.添加绘图记录
因为CMetaFileDC是CDC的派生类,可以像用普通CDC对象一样地,使用CMetaFileDC来设置绘图环境和执行绘图指令。
例如:
metaDC.SelectObject(pLinePen);
metaDC.MoveTo(point1);
metaDC.LineTo(point2);
……
3.播放图元文件
播放图元文件是指通过重画图元文件中的所有绘图记录(执行对应的绘图函数指令)来再现整个图形。
可以用CMetaFileDC和CDC类的相关成员函数来播放本程序所创建的图元文件,但是需要用WindowsSDK的相关图元函数才能装入和播放磁盘上的图元文件,还可以用CImage类来装入和显示磁盘上的图元文件。
1)播放本程序所创建的图元文件
为了播放在程序中创建的图元文件,必须先关闭图元文件DC,这可交由CMetaFileDC类的成员函数Close或CloseEnhanced来完成,然后用它们的返回值(图元文件的句柄)来调用CDC类的成员函数PlayMetaFile来播放图元文件。
这些函数的原型如下:
HMETAFILEClose();//关闭WMF图元文件DC,出错返回NULL
HENHMETAFILECloseEnhanced();//关闭EMF图元文件DC,出错返回NULL
BOOLPlayMetaFile(HMETAFILEhMF);//播放WMF图元文件
//播放EMF图元文件
BOOLPlayMetaFile(HENHMETAFILEhEnhMetaFile,LPCRECTlpBounds);
由于CDC类的两个PlayMetaFile函数不太听话,可能需要使用下面的SDK函数来代替:
BOOLPlayMetaFile(HDChdc,HMETAFILEhmf);//播放WMF图元文件
BOOLPlayEnhMetaFile(//播放EMF图元文件
HDChdc,//DC句柄
HENHMETAFILEhemf,//增强型图元文件的句柄
CONSTRECT*lpRect//边界矩形(逻辑单位)
);
其中,lpBounds为边界矩形,逻辑单位(默认为像素)。
在使用完图元文件句柄后,应该调用SDK函数:
BOOLDeleteMetaFile(HMETAFILEhmf);
来删除它(对磁盘元文件它只删除句柄,对内存元文件它会删除内存中元文件的所有内容)。
例如:
HENHMETAFILEhemf=metaDC.CloseEnhanced();
//pDC->PlayMetaFile(hemf,&rectPixel);//此函数不听话
PlayEnhMetaFile(GetDC()->m_hDC,hemf,&rect1);//SDK函数
DeleteEnhMetaFile(hemf);
2)装入和播放磁盘上的图元文件
MFC所封装的图元文件DC类CMetaFileDC,只是用于创建新图元文件,并添加绘图记录。
虽然,可以播放自己创建的图元文件,但是却不能播放已经存在的磁盘图元文件。
解决办法有两个:
●使用SDK函数来装入和播放图元文件。
可以利用SDK函数GetEnhMetaFile和PlayEnhMetaFile来装入和播放磁盘上的图元文件,这两个函数的原型为:
HENHMETAFILEGetEnhMetaFile(LPCTSTRlpszMetaFile);//装入图元文件
BOOLPlayEnhMetaFile(//显示图元文件
HDChdc,//DC句柄
HENHMETAFILEhemf,//增强型图元文件的句柄
CONSTRECT*lpRect//边界矩形(逻辑单位)
);
UINTGetEnhMetaFileHeader(//获取元文件头
//缓冲区参数为NULL时返回缓冲区大小,否则返回拷贝到缓冲区中的字节数
HENHMETAFILEhemf,//增强型图元文件的句柄
UINTcbBuffer,//缓冲区大小
LPENHMETAHEADERlpemh//数据缓冲区
);
例如:
HENHMETAFILEhemf=GetEnhMetaFile(L"test.emf");
UINTsize=GetEnhMetaFileHeader(hemf,0,NULL);
ENHMETAHEADER*emHeader=(ENHMETAHEADER*)malloc(size);
GetEnhMetaFileHeader(hemf,size,emHeader);
RECTLrectl=emHeader->rclBounds;//边界矩形
RECTrect={rectl.left,rectl.top,rectl.right,rectl.bottom};
PlayEnhMetaFile(GetDC()->m_hDC,hemf,&rect);
DeleteEnhMetaFile(hemf);
●使用CImage类来装入和播放图元文件。
由于CImage类所支持的图像文件格式中包含了WMF和EMF两种图元文件:
⏹ImageFormatWMF——WMF(WindowsMetaFile,视窗元文件)。
⏹ImageFormatEMF——EMF(EnhancedMetaFile,增强型元文件)。
所以可以利用来装入和播放磁盘上的图元文件,例如:
CImageimg;
img.Load(L"test.emf");
img.Draw(pDC->m_hDC,0,0);
4.动态重放图元文件
只有在OnDraw函数中绘制的图形才能被自动重绘。
而鼠标交互绘图程序中,在左鼠标键松开的响应函数OnLButtonUp中所绘制的图形,并不能被自动重绘。
现在我们可以用图元文件来解决这个问题,下面是相关的例子代码,过程参见图11-3。
图11-3利用图元文件在视图类中实现自动重绘的主要步骤
●在视图类中定义类变量:
RECTrectHimm,rectPixel;
CMetaFileDCmetaDC;
●在视图类的初始化函数OnInitialUpdate中,创建EMF元文件DC:
//获取参考HDC与屏幕大小
HDChdcRef=GetDC()->m_hDC;
intiWidthMM=GetDeviceCaps(hdcRef,HORZSIZE);//屏幕宽(毫米)
intiHeightMM=GetDeviceCaps(hdcRef,VERTSIZE);//屏幕高(毫米)
intiWidthPels=GetDeviceCaps(hdcRef,HORZRES);//屏幕宽(像素)
intiHeightPels=GetDeviceCaps(hdcRef,VERTRES);//屏幕高(像素)
//计算屏幕大小的HIMETRIC单位(0.01毫米)值和逻辑单位(像素)值
rectHimm.left=0;
rectHimm.top=0;
rectHimm.right=iWidthMM*100;
rectHimm.bottom=iHeightMM*100;
rectPixel.left=0;
rectPixel.top=0;
rectPixel.right=iWidthPels;
rectPixel.bottom=iHeightPels;
//创建内存EMF文件DC(也可将首个NULL改成文件的路径名串
//[如L"draw.emf"]来创建磁盘图元文件)
metaDC.CreateEnhanced(GetDC(),NULL,&rectHimm,NULL);
●在视图类的OnLButtonUp等函数中,利用图元文件DC,向图元文件添加各种绘图记录。
如:
metaDC.SelectObject(pLinePen);
metaDC.MoveTo(point1);
metaDC.LineTo(point2);
……
●在视图类的OnDraw函数中,关闭元文件DC,同时获取元文件句柄,播放元文件。
然后再创建新的元文件DC,并将老元文件中现有的记录,通过新的元文件DC的播放,加入到新元文件中,最后删除老元文件的句柄。
如:
//关闭元文件DC,获取句柄
HENHMETAFILEhemf=metaDC.CloseEnhanced();
//播放元文件(SDK函数)
PlayEnhMetaFile(GetDC()->m_hDC,hemf,&rectPixel);
//pDC->PlayMetaFile(hemf,&rectPixel);//此函数不太听话
//创建新元文件DC
metaDC.CreateEnhanced(GetDC(),NULL,&rectHimm,NULL);
//将老元文件的纪录加入到新元文件中
metaDC.PlayMetaFile(hemf,&rectPixel);
DeleteEnhMetaFile(hemf);//删除元文件句柄(SDK函数)
●可以在视图类的某个消息响应函数中利用SDK函数CopyMetaFile或CopyEnhMetaFile来将内存中的图元文件保存到磁盘文件中。
这两个函数的原型为:
HMETAFILECopyMetaFile(HMETAFILEhmfSrc,LPCTSTRlpszFile);
HENHMETAFILECopyEnhMetaFile(HENHMETAFILEhemfSrc,LPCTSTRlpszFile);
例如:
//关闭元文件,获取句柄
HENHMETAFILEhemf=CloseEnhMetaFile(hdcMeta);
CopyEnhMetaFile(hemf,L"draw.emf");//复制到磁盘文件(SDK函数)
DeleteEnhMetaFile(hemf);//删除元文件句柄(SDK函数)
●最后,在视图类的析构函数中,关闭图元文件DC并删除图元文件句柄。
例如:
HENHMETAFILEhemf=metaDC.CloseEnhanced();
DeleteEnhMetaFile(hemf);
5.播放图元文件中的记录
CMetaFileDC类和其父类CDC中都没有播放图元文件记录的成员函数,但是可以利用CMetaFileDC类的Close或CloseEnhanced成员函数所获得的图元文件句柄,再调用SDK的有关函数PlayEnhMetaFileRecord、EnumEnhMetaFile和EnhMetaFileProc等来绘制图元文件中的指定记录。
这些函数的原型为:
BOOLPlayEnhMetaFileRecord(//显示元文件中的记录
HDChdc,//DC句柄
LPHANDLETABLElpHandletable,//元文件句柄表
CONSTENHMETARECORD*lpEnhMetaRecord,//元文件记录
UINTnHandles//句柄计数
);
BOOLEnumEnhMetaFile(//枚举图元