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

Windows程式开发设计指南(十八)Metafile

2013年12月13日 ⁄ 综合 ⁄ 共 24741字 ⁄ 字号 评论关闭

18. Metafile

Metafile和向量图形的关系,就像点阵图和位元映射图形的关系一样。点阵图通常来自实际的图像,而metafile则大多是通过电脑程式人为建立的。Metafile由一系列与图形函式呼叫相同的二进位记录组成,这些记录一般用於绘制直线、曲线、填入的区域和文字等。

「画图(paint)」程式建立点阵图,而「绘图(draw)」程式建立metafile。在优秀的绘图程式中,能轻易地「抓住」某个独立的图形物件(例如一条直线)并将它移动到其他位置。这是因为组成图形的每个成员都是以单独的记录储存的。在画图程式中,这是不可能的-您通常都会局限於删除或插入点阵图矩形块。

由於metafile以图形绘制命令描述图像,因此可以对图像进行缩放而不会失真。点阵图则不然,如果以二倍大小来显示点阵图,您却无法得到二倍的解析度,而只是在水平和垂直方向上重复点阵图的位元。

Metafile可以转换为点阵图,但是会丢失一些资讯:组成metafile的图形物件将不再是独立的,而是被合并进大的图像。将点阵图转换为metafile要艰难得多,一般仅限於非常简单的图像,而且它需要大量处理来分析边界和轮廓。而metafile可以包含绘制点阵图的命令。

虽然metafile可以作为图片剪辑储存在磁片上,但是它们大多用於程式通过剪贴簿共用图片的情况。由於metafile将图片描述为图像函式呼叫的集合,因而它们既比点阵图占用更少的空间,又比点阵图更与装置无关。

Microsoft Windows支援两种metafile格式和支援这些格式的两组函式。我首先讨论从Windows 1.0到目前的32位元Windows版本都支援的metafile函式,然後讨论为32位元Windows系统开发的「增强型metafile」。增强型metafile在原有metafile的基础上有了一些改进,应该尽可能地加以利用。

旧的metafile格式

 

Metafile既能够暂时储存在记忆体中,也能够以档案的形式储存在磁片上。对应用程式来说,两者区别不大,尤其是由Windows来处理磁片上储存和载入metafile资料的档案I/O时,更是如此。

记忆体metafile的简单利用

 

如果呼叫CreateMetaFile函式来建立metafile装置内容,Windows就会以早期的格式建立一个metafile,然後您可以使用大部分GDI绘图函式在该metafile装置内容上进行绘图。这些GDI呼叫并不在任何具体的装置上绘图,相反地,它们被储存在metafile中。当关闭metafile装置内容时,会得到metafile的代号。这时就可以在某个具体的装置内容上「播放」这个metafile,这与直接执行metafile中GDI函式的效果等同。

CreateMetaFile只有一个参数,它可以是NULL或档案名称。如果是NULL,则metafile储存在记忆体中。如果是档案名称(以.WMF作为「Windows Metafile」的副档名),则metafile储存在磁片档案中。

程式18-1中的METAFILE显示了在WM_CREATE讯息处理期间建立记忆体metafile的方法,并在WM_PAINT讯息处理期间将图像显示100遍。

 程式18-1  METAFILE
METAFILE.C
/*-------------------------------------------------------------------------
   	METAFILE.C -- 		Metafile Demonstration Program
                 							(c) Charles Petzold, 1998
--------------------------------------------------------------------------*/

#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    							PSTR szCmdLine, int iCmdShow)
{
     	static TCHAR szAppName [] = TEXT ("Metafile") ;
     	HWND         							hwnd ;
     	MSG          							msg ;
     	WNDCLASS     						wndclass ;

     	wndclass.style         					= CS_HREDRAW | CS_VREDRAW ;
     	wndclass.lpfnWndProc   			= WndProc ;
     	wndclass.cbClsExtra    			= 0 ;
     	wndclass.cbWndExtra    			= 0 ;
     	wndclass.hInstance     			= hInstance ;
     	wndclass.hIcon         					= LoadIcon (NULL, IDI_APPLICATION) ;
     	wndclass.hCursor       				= LoadCursor (NULL, IDC_ARROW) ;
     	wndclass.hbrBackground 		= (HBRUSH) GetStockObject (WHITE_BRUSH) ;
     	wndclass.lpszMenuName  		= NULL ;
     	wndclass.lpszClassName 		= szAppName ;
     
     	if (!RegisterClass (&wndclass))
	{
          		MessageBox (	NULL, TEXT ("This program requires Windows NT!"),
                      							szAppName, MB_ICONERROR) ;
          		return 0 ;
 	}
     
     	hwnd = CreateWindow (	szAppName, TEXT ("Metafile Demonstration"),
                         WS_OVERLAPPEDWINDOW,
                         CW_USEDEFAULT, CW_USEDEFAULT,
                         CW_USEDEFAULT, CW_USEDEFAULT,
                         NULL, NULL, hInstance, NULL) ;
     
     	ShowWindow (hwnd, iCmdShow) ;
     	UpdateWindow (hwnd) ;
     
     	while (GetMessage (&msg, NULL, 0, 0))
     {
          		TranslateMessage (&msg) ;
          		DispatchMessage (&msg) ;
     	}
     	return msg.wParam ;
}

LRESULT CALLBACK WndProc (	HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
{
     	static HMETAFILE 		hmf ;
     	static int       				cxClient, cyClient ;
     	HBRUSH           					hBrush ;
     	HDC              						hdc, hdcMeta ;
     	int              						x, y ;
     	PAINTSTRUCT      				ps ;
     
     	switch (message)
     	{
     	case 	WM_CREATE:
          			hdcMeta	= 	CreateMetaFile (NULL) ;
          			hBrush	=	CreateSolidBrush (RGB (0, 0, 255)) ;
          			Rectangle 	(hdcMeta, 0, 0, 100, 100) ;
          
          			MoveToEx 		(hdcMeta,	0,	0, 	NULL) ;
          			LineTo   		(hdcMeta, 	100,	100) 	;
          			MoveToEx 		(hdcMeta,	0,	100, 	NULL) ;
          			LineTo   		(hdcMeta,	100,	0) 	;
          
          			SelectObject (hdcMeta, hBrush) ;
          			Ellipse (hdcMeta, 20, 20, 80, 80) ;
          
          			hmf = CloseMetaFile (hdcMeta) ;
          
          			DeleteObject (hBrush) ;
          			return 0 ;
          
     	case 	WM_SIZE:
          			cxClient = LOWORD (lParam) ;
          			cyClient = HIWORD (lParam) ;
          			return 0 ;
          
     	case 	WM_PAINT:
          			hdc = BeginPaint (hwnd, &ps) ;
          
          			SetMapMode (hdc, MM_ANISOTROPIC) ;
          			SetWindowExtEx (hdc, 1000, 1000, NULL) ;
          			SetViewportExtEx (hdc, cxClient, cyClient, NULL) ;
          
          			for (x = 0 ; x < 10 ; x++)
          			for (y = 0 ; y < 10 ; y++)
          			{
               					SetWindowOrgEx (hdc, -100 * x, -100 * y, NULL) ;
               					PlayMetaFile (hdc, hmf) ;
          			}
          			EndPaint (hwnd, &ps) ;
          			return 0 ;
               
     	case 	WM_DESTROY:
          			DeleteMetaFile (hmf) ;
          			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并显示它时,必须完成下列步骤:

  1. 程式利用metafile图片结构的mm栏位设定映射方式。
  2. 对於MM_ISOTROPIC或MM_ANISOTROPIC以外的映射方式,程式用xExt和yExt值设定剪贴矩形或简单地设定图像大小。而在MM_ISOTROPIC和MM_ANISOTROPIC映射方式,程式使用xExt和yExt来设定视埠范围。
  3. 然後,程式显示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) ;
CloseClipboard () ;

如果程式使用增强型metafile就可以省去这项工作。当某个应用程式将这些格式放入剪贴簿而另一个程式却要求从剪贴簿中获得其他格式时,Windows剪贴簿会自动在老式metafile和增强型metafile之间进行格式转换。

增强型metafile

 

「增强型metafile」 格式是在32位元Windows版本中发表的。它包含一组新的函式呼叫、一对新的资料结构、新的剪贴簿格式和新的档案副档名.EMF。

这种新的metafile格式最重要的改进是加入可通过函式呼叫取得的更丰富的表头资讯,这种表头资讯可用来帮助应用程式显示metafile图像。

有些增强型metafile函式使您能够在增强型metafile(EMF)格式和老式metafile格式(也称作Windows metafile(WMF)格式)之间来回转换。当然,这种转换很可能遇到麻烦,因为老式metafile格式并不支援某些,例如GDI绘图路径等,新的32位元图形功能。

基本程序

 

程式18-2所示的EMF1建立并显示增强型metafile。

 程式18-2  EMF1
EMF1.C
/*----------------------------------------------------------------------------
   	EMF1.C --	Enhanced Metafile Demo #1
             					(c) Charles Petzold, 1998
-----------------------------------------------------------------------------*/

#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    							LPSTR lpszCmdLine, int nCmdShow)
{
     	static TCHAR 	szAppName[] = TEXT ("EMF1") ;
     	HWND         				hwnd ;
     	MSG          				msg ;
     	WNDCLASS     			wndclass ;
     
     	wndclass.style         					= CS_HREDRAW | CS_VREDRAW ;
     	wndclass.lpfnWndProc   			= WndProc ;
     	wndclass.cbClsExtra    			= 0 ;
     	wndclass.cbWndExtra    			= 0 ;
     	wndclass.hInstance     			= hInstance ;
     	wndclass.hIcon         			= LoadIcon (NULL, IDI_APPLICATION) ;
     	wndclass.hCursor       		= LoadCursor (NULL, IDC_ARROW) ;
  	wndclass.hbrBackground 		= GetStockObject (WHITE_BRUSH) ;
     	wndclass.lpszMenuName  		= NULL ;
     	wndclass.lpszClassName 		= szAppName ;

     	if (!RegisterClass (&wndclass))
     	{
          			MessageBox (	NULL, TEXT ("This program requires Windows NT!"),
                      								szAppName, MB_ICONERROR) ;
          			return 0 ;
     }
     
     	hwnd = CreateWindow (	szAppName, TEXT ("Enhanced Metafile Demo #1"),
                          		WS_OVERLAPPEDWINDOW,
                          		CW_USEDEFAULT, CW_USEDEFAULT,
                          		CW_USEDEFAULT, CW_USEDEFAULT,
                          		NULL, NULL, hInstance, NULL) ;
     
     	ShowWindow (hwnd, nCmdShow) ;
     	UpdateWindow (hwnd) ;
     
     	while (GetMessage (&msg, NULL, 0, 0))
     	{
          			TranslateMessage (&msg) ;
          			DispatchMessage (&msg) ;
     }
     	return msg.wParam ;
}

LRESULT CALLBACK WndProc (	HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
{
     	static HENHMETAFILE 	hemf ;
     	HDC                 							hdc, hdcEMF ;
     	PAINTSTRUCT         					ps ;
     	RECT                							rect ;
     
     	switch (message)
     {
     	case 	WM_CREATE:
          			hdcEMF = CreateEnhMetaFile (NULL, NULL, NULL, NULL) ;

          			Rectangle 	(hdcEMF, 100, 100, 200, 200) ;
          
          			MoveToEx  		(hdcEMF, 100, 100, NULL) ;
          			LineTo  		(hdcEMF, 200, 200) ;
          
          			MoveToEx 		(hdcEMF, 200, 100, NULL) ;
          			LineTo  		(hdcEMF, 100, 200) ;

          			hemf = CloseEnhMetaFile (hdcEMF) ;
          			return 0 ;
          
     	case 	WM_PAINT:
          			hdc = BeginPaint (hwnd, &ps) ;
          
          			GetClientRect (hwnd, &rect) ;
          
          			rect.left   		=     			rect.right 		/ 4 ;
          			rect.right  		= 3 * 	rect.right		/ 4 ;
          			rect.top    		=     			rect.bottom		/ 4 ;
          			rect.bottom 		= 3 * 	rect.bottom		/ 4 ;
          
          			PlayEnhMetaFile (hdc, hemf, &rect) ;
          
          			EndPaint (hwnd, &ps) ;
          			return 0 ;
          
     	case 	WM_DESTROY:
          			DeleteEnhMetaFile (hemf) ;
          
          			PostQuitMessage (0) ;
          			return 0 ;
     }
     	return DefWindowProc (hwnd, message, wParam, lParam) ;
}

在EMF1的视窗讯息处理程式处理WM_CREATE讯息处理期间,程式首先通过呼叫CreateEnhMetaFile来建立增强型metafile。该函式有4个参数,但可以把它们都设为NULL。稍候我将说明这4个参数在非NULL情况下的使用方法。

和CreateMetaFile一样,CreateEnhMetaFile函式传回特定的装置内容代号。该程式利用这个代号绘制一个矩形和该矩形的两条对角线。这些函式呼叫及其参数被转换为二进位元的形式并储存在metafile中。

最後通过对CloseEnhMetaFile函式的呼叫结束了增强型metafile的建立并传回指向它的代号。该档案代号储存在HENHMETAFILE型态的静态变数中。

在WM_PAINT讯息处理期间,EMF1以RECT结构取得程式的显示区域视窗大小。通过调整结构中的4个栏位,使该矩形的长和宽为显示区域视窗长和宽的一半并位於视窗的中央。然後EMF1呼叫PlayEnhMetaFile,该函式的第一个参数是视窗的装置内容代号,第二个参数是该增强型metafile的代号,第三个参数是指向RECT结构的指标。

在metafile的建立程序中,GDI得出整个metafile图像的尺寸。在本例中,图像的长和宽均为100个单位。在metafile的显示程序中,GDI将图像拉伸以适应PlayEnhMetaFile函式指定的矩形大小。EMF1在Windows下执行的三个执行实体如图18-2所示。



 


图18-2 EMF1得萤幕显示

最後,在WM_DESTROY讯息处理期间,EMF1呼叫DeleteEnhMetaFile删除metafile。

让我们总结一下从EMF1程式学到的一些东西。

首先,该程式在建立增强型metafile时,画矩形和直线的函式所使用的座标并不是实际意义上的座标。您可以将它们同时加倍或都减去某个常数,而其结果不会改变。这些座标只是在定义图像时说明彼此间的对应关系。

其次,为了适於在传递给PlayEnhMetaFile函式的矩形中显示,图像大小会被缩放。因此,如图18-2所示,图像可能会变形。尽管metafile座标指出该图像是正方形的,但一般情况下我们却得不到这样的图像。而在某些时候,这又正是我们想要得到的图像。例如,将图像嵌入一段文书处理格式的文字中时,可能会要求使用者为图像指定矩形,并且确保整个图像恰好位於矩形中而不浪费空间。这样,使用者可通过适当调整矩形的大小来得到正确的纵横比。

然而有时候,您也许希望保留图像最初的纵横比,因为这一点对於表现视觉资讯尤为重要。例如,警察的嫌疑犯草图既不能比原型胖也不能比原型瘦。或者您希望保留原来图像的度量尺寸,图像必须是两英寸高,否则就不能正常显示。在这种情况下,保留图像的原来尺寸就非常重要了。

同时也要注意metafile中画出的那些对角线似乎没有与矩形顶点相交。这是由於Windows在metafile中储存矩形座标的方式造成的。稍後,会说明解决这个问题的方法。

揭开内幕

 

如果看一看metafile的内容会对metafile工作的方式有一个更好的理解。如果您有一个metafile档案,这将很容易做到,程式18-3中的EMF2程式建立了一个metafile。

 程式18-3  EMF2
EMF2.C
/*-----------------------------------------------------------------------------
   	EMF2.C --	Enhanced Metafile Demo #2
             					(c) Charles Petzold, 1998
-----------------------------------------------------------------------------*/

#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    							LPSTR lpszCmdLine, int nCmdShow)
{
     	static TCHAR 		szAppName[] = TEXT ("EMF2") ;
     	HWND         					hwnd ;
     	MSG          					msg ;
     	WNDCLASS     				wndclass ;
     	wndclass.style        					= CS_HREDRAW | CS_VREDRAW ;
     	wndclass.lpfnWndProc   			= WndProc ;
     	wndclass.cbClsExtra    			= 0 ;
     	wndclass.cbWndExtra    			= 0 ;
     	wndclass.hInstance     			= hInstance ;
     	wndclass.hIcon         			= LoadIcon (NULL, IDI_APPLICATION) ;
     	wndclass.hCursor       		= LoadCursor (NULL, IDC_ARROW) ;
     	wndclass.hbrBackground 		= GetStockObject (WHITE_BRUSH) ;
     	wndclass.lpszMenuName  		= NULL ;
     	wndclass.lpszClassName 		= szAppName ;
     
     	if (!RegisterClass (&wndclass))
     	{
          		MessageBox (	NULL, TEXT ("This program requires Windows NT!"),
                      							szAppName, MB_ICONERROR) ;
          		return 0 ;
     	}
     
     	hwnd = CreateWindow (szAppName, TEXT ("Enhanced Metafile Demo #2"),
                          	WS_OVERLAPPEDWINDOW,
                          	CW_USEDEFAULT, CW_USEDEFAULT,
                          	CW_USEDEFAULT, CW_USEDEFAULT,
                          	NULL, NULL, hInstance, NULL) ;
     
     	ShowWindow (hwnd, nCmdShow) ;
     	UpdateWindow (hwnd) ;
     
     	while (GetMessage (&msg, NULL, 0, 0))
     	{
          		TranslateMessage (&msg) ;
          		DispatchMessage (&msg) ;
     	}
     	return msg.wParam ;
}

LRESULT CALLBACK WndProc (	HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
     	HDC          				hdc, hdcEMF ;
     	HENHMETAFILE 	hemf ;
     	PAINTSTRUCT  		ps ;
     	RECT         				rect ;
     
     	switch (message)
     	{
     	case 	WM_CREATE:
          	hdcEMF = CreateEnhMetaFile (NULL, TEXT ("emf2.emf"), NULL,
            TEXT ("EMF2\0EMF Demo #2\0")) ;

          			if (!hdcEMF)
               			return 0 ;

          			Rectangle 	(hdcEMF, 100, 100, 200, 200) ;

          			MoveToEx  		(hdcEMF, 100, 100, NULL) ;
          			LineTo    		(hdcEMF, 200, 200) ;
          
          			MoveToEx  		(hdcEMF, 200, 100, NULL) ;
          			LineTo    		(hdcEMF, 100, 200) ;
          
          			hemf = CloseEnhMetaFile (hdcEMF) ;
          
          			DeleteEnhMetaFile (hemf) ;
          			return 0 ;
          
     	case 	WM_PAINT:
          			hdc = BeginPaint (hwnd, &ps) ;
          
          			GetClientRect (hwnd, &rect) ;
          
          			rect.left		= 	    rect.right      / 4 ;
          			rect.right  	= 3 * 	rect.right		/ 4 ;
          			rect.top    	=       rect.bottom		/ 4 ;
          			rect.bottom 	= 3 *	rect.bottom		/ 4 ;
          
          			if (hemf = GetEnhMetaFile (TEXT ("emf2.emf")))
          			{
               				PlayEnhMetaFile (hdc, hemf, &rect) ;
               				DeleteEnhMetaFile (hemf) ;
          			}
          			EndPaint (hwnd, &ps) ;
          			return 0 ;
          
     	case 	WM_DESTROY:
          			PostQuitMessage (0) ;
          			return 0 ;
     	}
     	return DefWindowProc (hwnd, message, wParam, lParam) ;
}

在EMF1程式中,CreateEnhMetaFile函式的所有参数均被设定为NULL。在EMF2中,第一个参数仍旧设定为NULL,该参数还可以是装置内容代号。GDI使用该参数在metafile表头中插入度量资讯,很快我会讨论它。如果该参数为NULL,则GDI认为度量资讯是由视讯装置内容决定的。

CreateEnhMetaFile函式的第二个参数是档案名称。如果该参数为NULL(在EMF1中为NULL,但在EMF2中不为NULL),则该函式建立记忆体metafile。EMF2建立名为EMF2.EMF的metafile档案。

函式的第三个参数是RECT结构的位址,它指出了以0.01mm为单位的metafile的总大小。这是metafile表头资料中极其重要的资讯(这是早期的Windows metafile格式的缺陷之一)。如果该参数为NULL,GDI会计算出尺寸。我比较喜欢让作业系统替我做这些事,所以将该参数设定为NULL。当应用程式对性能要求比较严格时,就需要使用该参数以避免让GDI处理太多东西。

最後的参数是描述该metafile的字串。该字串分为两部分:第一部分是以NULL字元结尾的应用程式名称(不一定是程式的档案名称),第二部分是描述视觉图像内容的说明,以两个NULL字元结尾。例如用C中的符号「\0」作为NULL字元,则该描述字串可以是「LoonyCad V6.4\0Flying Frogs\0\0」。由於在C中通常会在使用的字串末尾放入一个NULL字元,所以如EMF2所示,在末尾仅需一个「\0」。

建立完metafile後,与EMF1一样,EMF2也透过利用由CreateEnhMetaFile函式传回的装置内容代号进行一些GDI函式呼叫。然後程式呼叫CloseEnhMetaFile删除装置内容代号并取得完成的metafile的代号。

然後,在WM_CREATE讯息还没处理完毕时,EMF2做了一些EMF1没有做的事情:在获得metafile代号之後,程式呼叫DeleteEnhMetaFile。该操作释放了用於储存metafile的所有记忆体资源。然而,metafile档案仍然保留在磁碟机中(如果愿意,您可以使用如DeleteFile的档案删除函式来删除该档案)。注意metafile代号并不像EMF1中那样储存在静态变数中,这意味著在讯息之间不需要储存它。

现在,为了使用该metafile,EMF2需要存取磁片档案。这是在WM_PAINT讯息处理期间透过呼叫GetEnhMetaFile进行的。Metafile的档案名称是该函式的唯一参数,该函式传回metafile代号。和EMF1一样,EMF2将这个档案代号传递给PlayEnhMetaFile函式。该metafile图像在PlayEnhMetaFile函式的最後一个参数所指定的矩形中显示。与EMF1不同的是,EMF2在WM_PAINT讯息结束之前就删除该metafile。此後每次处理WM_PAINT讯息时,EMF2都会再次读取metafile,显示并删除它。

要记住,对metafile的删除操作仅是释放了用以储存metafile的记忆体资源而已,磁片metafile甚至在程式执行结束後还保留在磁片上。

由於EMF2留下了metafile档案,您可以看一看它的内容。图18-3显示了该程式建立的EMF2.EMF档案的一堆十六进位代码。

0000  	01 00 00 00 88 00 00 00 64 00 00 00 64 00 00 00	    ........d...d...
0010  	C8 00 00 00 C8 00 00 00 35 0C 00 00 35 0C 00 00  	........5...5...
0020  	6A 18 00 00 6A 18 00 00 20 45 4D 46 00 00 01 00  	j...j...EMF....
0030  	F4 00 00 00 07 00 00 00 01 00 00 00 12 00 00 00  	................
0040  	64 00 00 00 00 00 00 00 00 04 00 00 00 03 00 00  	d...............
0050  	40 01 00 00 F0 00 00 00 00 00 00 00 00 00 00 00  	@...............
0060  	00 00 00 00 45 00 4D 00 46 00 32 00 00 00 45 00  	....E.M.F.2...E.
0070  	4D 00 46 00 20 00 44 00 65 00 6D 00 6F 00 20 00  	M.F..D.e.m.o..
0080  	23 00 32 00 00 00 00 00 2B 00 00 00 18 00 00 00  	#.2.....+.......
0090  	63 00 00 00 63 00 00 00 C6 00 00 00 C6 00 00 00  	c...c...........
00A0  	1B 00 00 00 10 00 00 00 64 00 00 00 64 00 00 00  	........d...d...
00B0  	36 00 00 00 10 00 00 00 C8 00 00 00 C8 00 00 00  	6...............
00C0  	1B 00 00 00 10 00 00 00 C8 00 00 00 64 00 00 00  	............d...
00D0  	36 00 00 00 10 00 00 00 64 00 00 00 C8 00 00 00  	6.......d.......
00E0  	0E 00 00 00 14 00 00 00 00 00 00 00 10 00 00 00  	................
00F0  	14 00 00 00									....

图18-3 EMF2.EMF的十六进位代码

图18-3所示的metafile是EMF2在Microsoft Windows NT 4下,视讯显示器的解析度为1024×768时建立的。同一程式在Windows 98下建立的metafile会比前者少12个位元组,这一点将在稍後讨论。同样地,视讯显示器的解析度也影响metafile表头的某些资讯。

增强型metafile格式使我们对metafile的工作方式有更深刻的理解。增强型metafile由可变长度的记录组成,这些记录的一般格式由ENHMETARECORD结构说明,它在WINGDI.H表头档案中定义如下:

typedef struct tagENHMETARECORD
{
     	DWORD iType ;         				// record type
     	DWORD nSize ;         				// record size
     	DWORD dParm [1] ;     		        // parameters
}
ENHMETARECORD ;

当然,那个只有一个元素的阵列指出了阵列元素的变数。参数的数量取决於记录型态。iType栏位可以是定义在WINGDI.H档案中以字首EMR_开始的近百个常数之一。nSize栏位是总记录的大小,包括iType和nSize栏位以及一个或多个dParm栏位。

有了这些知识後,让我们看一下图18-3。第一个栏位型态为0x00000001,大小为0x00000088,所以它占据档案的前136个位元组。记录型态为1表示常数EMR_HEADER。我们不妨把对表头纪录的讨论往後搁,先跳到位於第一个记录末尾的偏移量0x0088处。

後面的5个记录与EMF2建立metafile之後的5个GDI函式呼叫有关。该记录在偏移量0x0088处有一个值为0x0000002B的型态代码,这代表EMR_RECTANGLE,很明显是用於Rectangle呼叫的metafile记录。它的长度为0x00000018 (十进位24)位元组,用以容纳4个32位元参数。实际上Rectangle函式有5个参数,但是第一个参数,也就是装置内容代号并未储存在metafile中,因为它没有实际意义。尽管在EMF2的函式呼叫中指定了矩形的顶点座标分别是(100,100)和(200,200),但4个参数中的2个是0x00000063
(99),另外2个是0x000000C6 (198)。EMF2程式在Windows 98下建立的metafile显示出前两个参数是0x00000064 (100),後2个参数是0x000000C7(199)。显然,在Rectangle参数储存到metafile之前,Windows对它们作了调整,但没有保持一致。这就是对角线端点与矩形顶点不能重合的原因。

其次,有4个16位元记录与2个MoveToEx (0x0000001B或EMR_MOVETOEX)和LineTo (0x00000036或EMR_LINETO)呼叫有关。位於metafile中的参数与传递给函式的参数相同。

Metafile以20个位元组长的型态代码为0x0000000E或EMR_EOF(「end of file」)的记录结尾。

增强型metafile总是以表头纪录开始。它对应於ENHMETAHEADER型态的结构,定义如下:

typedef struct tagENHMETAHEADER
{
     	DWORD iType ;       // EMR_HEADER = 1
     	DWORD nSize ;       // structure size 
     	RECTL rclBounds ;    // bounding rectangle in pixels
     	RECTL rclFrame ;     // size of image in 0.01 millimeters
     	DWORD dSignature ;  // ENHMETA_SIGNATURE = " EMF"
     	DWORD nVersion ;    // 0x00010000
     	DWORD nBytes ;     // file size in bytes
     	DWORD nRecords ;   // total number of records
     	WORD  nHandles ;   // number of handles in handle table
     	WORD  sReserved ;
     	DWORD nDescription ;    // character length of description string
     	DWORD offDescription ;   // offset of description string in file
     	DWORD nPalEntries ;     // number of entries in palette
     	SIZEL szlDevice ;        // device resolution in pixels
     	SIZEL szlMillimeters ; 	   // device resolution in millimeters
     	DWORD cbPixelFormat ;  // size of pixel format
     	DWORD offPixelFormat ;  // offset of pixel format
     	DWORD bOpenGL ;      // FALSE if no OpenGL records
}
ENHMETAHEADER ;

这种表头纪录的存在可能是增强型metafile格式对早期Windows metafile所做的最为重要的改进。不需要对metafile档案使用档案I/O函式来取得这些表头资讯。如果具有metafile代号,就可以使用GetEnhMetaFileHeader函式:

GetEnhMetaFileHeader (hemf, cbSize, &emh) ;

第一个参数是metafile代号。最後一个参数是指向ENHMETAHEADER结构的指标。第二个参数是该结构的大小。可以使用类似的GetEnh-MetaFileDescription函式取得描述字串。

如上面所定义的,ENHMETAHEADER结构有100位元组长,但在MF2.EMFmetafile中,记录的大小包括描述字串,所以大小为0x88,即136位元组。而Windows 98metafile的表头纪录不包含ENHMETAHEADER结构的最後3个栏位,这一点解释了12个位元组的差别。

rclBounds栏位是指出图像大小的RECT结构,单位是图素。将其从十六进位转换过来,我们看到该图像正如我们希望的那样,其左上角位於(100,100),右下角位於(200,200)。

rclFrame栏位是提供相同资讯的另一个矩形结构,但它是以0.01毫米为单位。在这种情况下,该档案显示两对角顶点分别位於(0x0C35,0x0C35)和(0x186A,0x186A),用十进位表示为(3125,3125)和(6250,6250)的矩形。这些数字是怎么来的?我们很快就会明白。

dSignature栏位始终为值ENHMETA_SIGNATURE或0x464D4520。这看上去是一个奇怪的数字,但如果将位元组的排列顺序倒过来(就像Intel处理器在记忆体中储存多位元组数那样)并转换成ASCII码,就变成字串" EMF"。dVersion栏位的值始终是0x00010000。

其後是nBytes栏位,该栏位在本例中是0x000000F4,这是该metafile的总位元组数。nRecords栏位(在本例中是0x00000007)指出了记录数-包括表头纪录、5个GDI函式呼叫和档案结束记录。

下面是两个十六位元的栏位。nHandles栏位为0x0001。该栏位一般指出metafile所使用的图形物件(如画笔、画刷和字体)的非内定代号的数量。由於没有使用这些图形物件,您可能会认为该栏位为零,但实际上GDI自己保留了第一个栏位。我们将很快见到代号储存在metafile中的方式。

下两个栏位指出描述字串的字元个数,以及描述字串在档案中的偏移量,这里它们分别为0x00000012(十进位数字18)和0x00000064。如果metafile没有描述字串,则这两个栏位均为零。

nPalEntries栏位指出在metafile的调色盘表中条目的个数,本例中没有这种情况。

接著表头纪录包括两个SIZEL结构,它们包含两个32位栏位,cx和cy。szlDevice栏位 (在metafile中的偏移量为0x0040)指出了以图素为单位的输出设备大小,szlMillimeters栏位(偏移量为0x0050)指出了以毫米为单位的输出设备大小。在增强型metafile文件中,这个输出设备被称作「参考设备(reference
device)」。它是依据作为第一个参数传递给CreateEnhMetaFile呼叫的代号所指出的装置内容。如果该参数设为NULL,则GDI使用视讯显示器。当EMF2建立上面所示的metafile时,正巧是在Windows NT上以1024×768显示模式工作,因此这就是GDI使用的参考设备。

GDI通过呼叫GetDeviceCaps取得此资讯。EMF2.EMF中的szlDevice栏位是0x0400×0x0300(即1024×768),它是以HORZRES和VERTRES作为参数呼叫GetDeviceCaps得到的。szlMillimeters栏位是0x140×0xF0,或320×240,是以HORZSIZE和VERTSIZE作为参数呼叫GetDeviceCaps得到的。

通过简单的除法就可以得出图素为0.3125mm高和0.3125mm宽,这就是前面描述的GDI计算rclFrame矩形尺寸的方法。

在metafile中,ENHMETAHEADER结构後跟一个描述字串,该字串是CreateEnhMetaFile函式的最後一个参数。在本例中,该字串由後跟一个NULL字元的「EMF2」字串和後跟两个NULL字元的「EMF Demo #2」字串组成。总共18个字元,要是以Unicode方式储存则为36个字元。无论建立metafile的程式执行在Windows
NT还是Windows 98下,该字串始终以Unicode方式储存。

metafile与GDI物件

 

我们已经知道了GDI绘图命令储存在metafile中方式,现在看一下GDI物件的储存方式。程式18-4 EMF3除了建立用於绘制矩形和直线的非内定画笔和画刷以外,与前面介绍的EMF2程式很相似。该程式也对Rectangle座标的问题提出了一点修改。EMF3程式使用GetVersion来确定执行环境是Windows 98还是Windows NT,并适当地调整参数。

 程式18-4  EMF3
EMF3.C
/*----------------------------------------------------------------------------
   	EMF3.C -- 	Enhanced Metafile Demo #3
             					(c) Charles Petzold, 1998
-----------------------------------------------------------------------------*/

#include <windows.h>
LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM) ;
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    							PSTR szCmdLine, int iCmdShow)
{
     	static TCHAR szAppName[] = TEXT ("EMF3") ;
     	HWND         							hwnd ;
     	MSG          							msg ;
     	WNDCLASS     						wndclass ;
     
     	wndclass.style         					= CS_HREDRAW | CS_VREDRAW ;
     	wndclass.lpfnWndProc   			= WndProc ;
     	wndclass.cbClsExtra    			= 0 ;
     	wndclass.cbWndExtra    			= 0 ;
     	wndclass.hInstance     			= hInstance ;
     	wndclass.hIcon         					= LoadIcon (NULL, IDI_APPLICATION) ;
     	wndclass.hCursor       				= LoadCursor (NULL, IDC_ARROW) ;
     	wndclass.hbrBackground	 	= GetStockObject (WHITE_BRUSH) ;
     	wndclass.lpszMenuName  		= NULL ;
     	wndclass.lpszClassName 		= szAppName ;
     
     	if (!RegisterClass (&wndclass))
     	{
          		MessageBox (	NULL, TEXT ("This program requires Windows NT!"),
                      							szAppName, MB_ICONERROR) ;
          		return 0 ;
     }
     
	hwnd = CreateWindow (	szAppName, TEXT ("Enhanced Metafile Demo #3"),
                          	WS_OVERLAPPEDWINDOW,
                          	CW_USEDEFAULT, CW_USEDEFAULT,
                          	CW_USEDEFAULT, CW_USEDEFAULT,
                          	NULL, NULL, hInstance, NULL) ;
     
     	ShowWindow (hwnd, iCmdShow) ;
     	UpdateWindow (hwnd) ;
     
     	while (GetMessage (&msg, NULL, 0, 0))
     	{
          		TranslateMessage (&msg) ;
          		DispatchMessage (&msg) ;
     	}
	return msg.wParam ;
}

LRESULT CALLBACK WndProc (	HWND hwnd, UINT message, WPARAM wParam,LPARAM lParam)
{
     	LOGBRUSH     				lb ;
     	HDC          					hdc, hdcEMF ;
     	HENHMETAFILE 		hemf ;
     	PAINTSTRUCT  			ps ;
     	RECT         					rect ;
     	switch (message)
     {
     	case 	WM_CREATE:
          	hdcEMF = CreateEnhMetaFile (NULL, TEXT ("emf3.emf"), NULL,
            TEXT ("EMF3\0EMF Demo #3\0")) ;
          
          			SelectObject (hdcEMF, CreateSolidBrush (RGB (0, 0, 255))) ;
          
          			lb.lbStyle = BS_SOLID ;
          			lb.lbColor = RGB (255, 0, 0) ;
          			lb.lbHatch = 0 ;

          			SelectObject (hdcEMF, 
               		ExtCreatePen (PS_SOLID | PS_GEOMETRIC, 5, &lb, 0, NULL)) ;
          
          			if (GetVersion () & 0x80000000) // Windows 98
               		Rectangle (hdcEMF, 100, 100, 201, 201) ;
          			else    
				   // Windows NT
               					Rectangle (hdcEMF, 101, 101, 202, 202) ;
          
          			MoveToEx  		(hdcEMF, 100, 100, NULL) ;
          			LineTo    		(hdcEMF, 200, 200) ;
          
          			MoveToEx  		(hdcEMF, 200, 100, NULL) ;
          			LineTo    		(hdcEMF, 100, 200) ;
          
          			DeleteObject (SelectObject (hdcEMF, GetStockObject (BLACK_PEN))) ;
          			DeleteObject (SelectObject (hdcEMF, GetStockObject (WHITE_BRUSH))) ;
          
          			hemf = CloseEnhMetaFile (hdcEMF) ;
          
          					DeleteEnhMetaFile (hemf) ;
        					return 0 ;
          
     			case 	WM_PAINT:
        					hdc = BeginPaint (hwnd, &ps) ;
          
       					GetClientRect (hwnd, &rect) ;
          
        					rect.left   		=     	rect.right  		/ 4 ;
          					rect.right  		= 3 * 	rect.right  		/ 4 ;
          					rect.top    		=     	rect.bottom 		/ 4 ;
          					rect.bottom 		= 3 * 	rect.bottom 		/ 4 ;
          
          					hemf = GetEnhMetaFile (TEXT ("emf3.emf")) ;
          
          					PlayEnhMetaFile (hdc, hemf, &rect) ;
          					DeleteEnhMetaFile (hemf) ;
          					EndPaint (hwnd, &ps) ;
          					return 0 ;
          
     			case 	WM_DESTROY:
          					PostQuitMessage (0) ;
          					return 0 ;
     	}
     	return DefWindowProc (hwnd, message, wParam, lParam) ;
}

如我们所看到的,当利用CreateEnhMetaFile传回的装置内容代号来呼叫GDI函式时,这些函式呼叫被储存在metafile中而不是直接输出到萤幕或印表机上。然而,一些GDI函式根本不涉及特定的装置内容。其中有关建立画笔和画刷等图形物件的GDI函式十分重要。虽然逻辑画笔和画刷的定义储存在由GDI保留的记忆体中,但是在建立这些物件时,这些抽象的定义并未与任何特定的装置内容相关。

EMF3呼叫CreateSolidBrush和ExtCreatePen函式。因为这些函式不需要装置内容代号,`所以GDI不会把这些呼叫储存在metafile里。当呼叫它们时

抱歉!评论已关闭.