📄 18. metafile.txt
字号:
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
这个程序展示了在使用内存MetaFile时所涉及的4个MetaFile函数的用法。第一个是CreateMetaFile。在WM_CREATE消息处理期间用NULL参数呼叫该函数,并传回MetaFile设备内容的句柄。然后,MetaFile利用这个MetaFileDC来绘制两条直线和一个蓝色椭圆。这些函数呼叫以二进制形式储存在MetaFile中。CloseMetaFile函数传回MetaFile的句柄。因为以后还要用到该MetaFile句柄,所以把它储存在静态变量。
该MetaFile包含GDI函数呼叫的二进制表示码,它们是两个MoveToEx呼叫、两个LineTo呼叫、一个SelectObject呼叫(指定蓝色画刷)和一个Ellipse呼叫。坐标没有指定任何映像方式或转换,它们只是作为数值数据被储存在MetaFile中。
在WM_PAINT消息处理期间,MetaFile设定一种映像方式并呼叫PlayMetaFile在窗口中绘制对象100次。MetaFile中函数呼叫的坐标按照目的设备内容的目前变换方式加以解释。在呼叫PlayMetaFile时,事实上是在重复地呼叫最初在WM_CREATE消息处理期间建立MetaFile时,在CreateMetaFile和CloseMetaFile之间所做的所有呼叫。
和任何GDI对象一样,MetaFile对象也应该在程序终止前被删除。这是在WM_DESTROY消息处理期间用DeleteMetaFile函数处理的工作。
MetaFile程序的结果如图18-1所示。
图18-1 MetaFile程序执行结果显示
将MetaFile储存在磁盘上
在上面的例子中,CreateMetaFile的NULL参数表示要建立储存在内存中的MetaFile。我们也可以建立作为文件储存在磁盘上的MetaFile,这种方法对于大的MetaFile比较合适,因为可以节省内存空间。而另一方面,每次使用磁盘上的MetaFile时,就需要存取磁盘。
要把MetaFile转换为使用MetaFile磁盘文件的程序,必须把CreateMetaFile的NULL参数替换为文件名称。在WM_CREATE处理结束时,可以用MetaFile句柄来呼叫DeleteMetaFile,这样句柄被删除,但是磁盘文件仍然被储存着。
在处理WM_PAINT消息处理期间,可以通过呼叫GetMetaFile来取得此磁盘文件的MetaFile句柄:
hmf = GetMetaFile (szFileName) ;
现在就可以像前面那样显示这个MetaFile。在WM_PAINT消息处理结束时,可以用下面的叙述删除该MetaFile句柄:
DeleteMetaFile (hmf) ;
在开始处理WM_DESTROY消息时,不必删除MetaFile,因为它已经在WM_CREATE消息和每个WM_PAINT消息结束时被删除了,但是仍然需要删除磁盘文件:
DeleteFile (szFileName) ;
当然,除非您想储存该文件。
正如在第十章讨论过的,MetaFile也可以作为使用者自订资源。您可以简单地把它当作数据块加载。如果您有一块包含MetaFile内容的资料,那么您可以使用
hmf = SetMetaFileBitsEx (iSize, pData) ;
来建立MetaFile。SetMetaFileBitsEx有一个对应的函数-GetMetaFileBitsEx,此函数将MetaFile的内容复制到内存块中。
老式MetaFile与剪贴簿
老式MetaFile有个讨厌的缺陷。如果您具有老式MetaFile的句柄,那么,当您在显示MetaFile时如何确定它的大小呢?除非您深入分析MetaFile的内部结构,否则无法得知。
此外,当程序从剪贴簿取得老式MetaFile时,如果MetaFile被定义为在MM_ISOTROPIC或MM_ANISOTROPIC映像方式下显示,则此程序在使用该MetaFile时具有最大程度的灵活性。程序收到该MetaFile后,就可以在显示它之前简单地通过设定视埠的范围来缩放图像。然而,如果MetaFile内的映像方式被设定为MM_ISOTROPIC或MM_ANISOTROPIC,则收到该MetaFile的程序将无法继续执行。程序仅能在显示MetaFile之前或之后进行GDI呼叫,不允许在显示MetaFile当中进行GDI呼叫。
为了解决这些问题,老式MetaFile句柄不直接放入剪贴簿供其它程序取得,而是作为「MetaFile图片」(MetaFilePICT结构型态)的一部分。此结构使得从剪贴簿上取得MetaFile图片的程序能够在显示MetaFile之前设定映像方式和视埠范围。
MetaFilePICT结构的长度为16个字节,定义如下:
typedef struct tagMetaFilePICT
{
LONG mm ; // mapping mode
LONG xExt ; // width of the MetaFile image
LONG yExt ; // height of the MetaFile image
LONG hMF ; // handle to the MetaFile
}
MetaFilePICT ;
对于MM_ISOTROPIC和MM_ANISOTROPIC以外的所有映像方式,图像大小用xExt和yExt值表示,其单位是由mm给出的映像方式的单位。利用这些信息,从剪贴簿复制MetaFile图片结构的程序就能够确定在显示MetaFile时所需的显示空间。建立该MetaFile的程序可以将这些值设定为输入MetaFile的GDI绘制函数中所使用的最大的x坐标和y坐标值。
在MM_ISOTROPIC和MM_ANISOTROPIC映射方式下,xExt和yExt字段有不同的功能。我们在第五章中曾介绍过一个程序,该程序为了在GDI函数中使用与图像实际尺寸无关的逻辑单位而采用MM_ISOTROPIC或MM_ANISOTROPIC映射方式。当程序只想保持纵横比而可以忽略图形显示平面的大小时,采用MM_ISOTROPIC模式;反之,当不需要考虑纵横比时采用MM_ANISOTROPIC模式。您也许还记得,第五章中在程序将映像方式设定为MM_ISOTROPIC或MM_ANISOTROPIC后,通常会呼叫SetWindowExtEx和SetViewportExtEx。SetWindowExtEx呼叫使用逻辑单位来指定程序在绘制时使用的单位,而SetViewportExtEx呼叫使用的设备单位大小则取决于图形显示平面(例如,窗口显示区域的大小)。
如果程序为剪贴簿建立了MM_ISOTROPIC或MM_ANISOTROPIC方式的MetaFile,则该MetaFile本身不应包含对SetViewportExtEx的呼叫,因为该呼叫中的设备单位应该依据建立MetaFile的程序的显示平面,而不是依据从剪贴簿读取并显示MetaFile的程序的显示平面。从剪贴簿取得MetaFile的程序可以利用xExt和yExt值来设定合适的视埠范围以便显示MetaFile。但是当映像方式是MM_ISOTROPIC或MM_ANISOTROPIC时,MetaFile本身包含设定窗口范围的呼叫。MetaFile内的GDI绘图函数的坐标依据这些窗口的范围。
建立MetaFile和MetaFile图片遵循以下规则:
设定MetaFilePICT结构的mm字段来指定映像方式。
对于MM_ISOTROPIC和MM_ANISOTROPIC以外的映像方式,xExt与yExt字段设定为图像的宽和高,单位与mm字段相对应。对于在MM_ISOTROPIC或MM_ANISOTROPIC方式下显示的MetaFile,工作要复杂一些。在MM_ANISOTROPIC模式下,当程序既不对图片大小跟纵横比给出任何建议信息时,xExt和yExt的值均为零。在这两种模式下,如果xExt和yExt的值为正数,它们就是以0.01mm单位(MM_HIMETRIC单位)表示该图像的宽度和高度。在MM_ISOTROPIC方式下,如果xExt和yExt为负值,它们就指出了图像的纵横比而不是大小。
在MM_ISOTROPIC和MM_ANISOTROPIC映像方式下,MetaFile本身含有对SetWindowExtEx的呼叫,也可能有对SetWindowOrgEx的呼叫。亦即,建立MetaFile的程序在MetaFile设备内容中呼叫这些函数。MetaFile一般不会包含对SetMapMode、SetViewportExtEx或SetViewportOrgEx的呼叫。
MetaFile应该是内存MetaFile,而不是MetaFile文件。
这里有一段范例程序代码,它建立MetaFile并将其复制到剪贴簿。如果MetaFile使用MM_ISOTROPIC或MM_ANISOTROPIC映像方式,则该MetaFile的第一个呼叫应该设定窗口范围(在其它模式中,窗口的大小是固定的)。无论在哪种模式下,窗口的位置应如下设定:
hdcMeta = CreateMetaFile (NULL) ;
SetWindowExtEx (hdcMeta, ...) ;
SetWindowOrgEx (hdcMeta, ...) ;
MetaFile绘图函数中的坐标决定于这些窗口范围和窗口原点。当程序使用GDI呼叫在MetaFile设备内容中绘制完成后,关闭MetaFile以得到MetaFile句柄:
hmf = CloseMetaFile (hdcMeta) ;
该程序还需要定义指向MetaFilePICT型态结构的指针,并为此结构配置一块整体内存:
GLOBALHANDLE hGlobal ;
LPMetaFilePICT pMFP ;
其它行程序
hGlobal= GlobalAlloc (GHND | GMEM_SHARE, sizeof (MetaFilePICT)) ;
pMFP = (LPMetaFilePICT) GlobalLock (hGlobal) ;
接着,程序设定该结构的4个字段:
pMFP->mm = MM_... ;
pMFP->xExt = ... ;
pMFP->yExt = ... ;
pMFP->hMF = hmf ;
GlobalUnlock (hGlobal) ;
然后,程序将包含有MetaFile图片的整体内存块传送给剪贴簿:
OpenClipboard (hwnd) ;
EmptyClipboard () ;
SetClipboardData (CF_MetaFilePICT, hGlobal) ;
CloseClipboard () ;
完成这些呼叫后,hGlobal句柄(包含MetaFile图片结构的内存块)和hmf句柄(MetaFile本身)就对建立它们的程序失效了。
现在来看一看难的部分。当程序从剪贴簿取得MetaFile并显示它时,必须完成下列步骤:
程序利用MetaFile图片结构的mm字段设定映像方式。
对于MM_ISOTROPIC或MM_ANISOTROPIC以外的映像方式,程序用xExt和yExt值设定剪贴矩形或简单地设定图像大小。而在MM_ISOTROPIC和MM_ANISOTROPIC映像方式,程序使用xExt和yExt来设定视埠范围。
然后,程序显示MetaFile。
下面程序代码,首先打开剪贴簿,得到MetaFile图片结构句柄并将其锁定:
OpenClipboard (hwnd) ;
hGlobal = GetClipboardData (CF_MetaFilePICT) ;
pMFP = (LPMetaFilePICT) GlobalLock (hGlobal) ;
现在可以储存目前设备内容的属性,并将映像方式设定为结构中的mm值:
SaveDC (hdc) ;
SetMappingMode (pMFP->mm) ;
如果映像方式不是MM_ISOTROPIC或MM_ANISOTROPIC,则可以用xExt和yExt的值设定剪贴矩形。由于这两个值是逻辑单位,必须用LPtoDP将其转换为用于剪贴矩形的设备单位的坐标。也可以简单地储存这些值以掌握图像的大小。
对于MM_ISOTROPIC或MM_ANISOTROPIC映像方式,xExt和yExt用来设定视埠范围。下面有一个用来完成此项任务的函数,如果xExt和yExt没有建议的大小,则该函数假定cxClient和cyClient分别表示MetaFile显示区域的图素高度和宽度。
void PrepareMetaFile ( HDC hdc, LPMetaFilePICT pmfp,
int cxClient, int cyClient)
{
int xScale, yScale, iScale ;
SetMapMode (hdc, pmfp->mm) ;
if (pmfp->mm == MM_ISOTROPIC || pmfp->mm == MM_ANISOTROPIC)
{
if (pmfp->xExt == 0)
SetViewportExtEx (hdc, cxClient, cyClient, NULL) ;
else if (pmfp->xExt > 0)
SetViewportExtEx (hdc,
pmfp->xExt * GetDeviceCaps (hdc, HORZRES) /
GetDeviceCaps (hdc, HORZSIZE) / 100),
pmfp->yExt * GetDeviceCaps (hdc, VERTRES) /
GetDeviceCaps (hdc, VERTSIZE) / 100), NULL) ;
else if (pmfp->xExt < 0)
{
xScale = 100 *cxClient * GetDeviceCaps (hdc, HORZSIZE) /
GetDeviceCaps (hdc, HORZRES) / -pmfp->xExt ;
lScale = 100 *cyClient * GetDeviceCaps (hdc, VERTSIZE) /
GetDeviceCaps (hdc, VERTRES) / -pmfp->yExt ;
iScale = min (xScale, yScale) ;
SetViewportExtEx (hdc, -pmfp->xExt * iScale * GetDeviceCaps (hdc, HORZRES) /
GetDeviceCaps (hdc, HORZSIZE) / 100, -pmfp->yExt * iScale
* GetDeviceCaps (hdc, VERTRES) / GetDeviceCaps (hdc, VERTSIZE) / 100, NULL) ;
}
}
}
上面的程序代码假设xExt和yExt同时都为零、大于零或小于零,这三种状态之一。如果范围为零,表示没有建议大小或纵横比,视埠范围设定为显示MetaFile的区域。如果大于零,则xExt和yExt的值代表图像的建议大小,单位是0.01mm。GetDeviceCaps函数用来确定每0.01mm中包含的图素数,并且该值与MetaFile图片结构的范围值相乘。如果小于零,则xExt和yExt的值表示建议的纵横比而不是建议的大小。iScale的值首先根据对应cxClient和cyClient的毫米表示的纵横比计算出来,该缩放因子用于设定图素单位的视端口范围。
完成了上述工作后,可以设定视埠原点,显示MetaFile,并恢复设备内容:
PlayMetaFile (pMFP->hMF) ;
RestoreDC (hdc, -1) ;
然后,对内存块解锁并关闭剪贴簿:
GlobalUnlock (hGlobal) ;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -