图形基础-GDI映像模式( 下)
2008-11-17 11:33
183 查看
设备坐标和逻辑坐标
您也许会问:如果使用MM_LOENGLISH映射方式,是不是将会得到以百分之一英寸为单位的WM_SIZE消息呢?绝对不会。Windows对所有消息(如WM_MOVE、WM_SIZE和WM_MOUSEMOVE),对所有非GDI函数,甚至对一些GDI函数,永远使用设备坐标。可以这样来考虑:由于映像方式是一种设备内容属性,所以,只有对需要设备内容句柄作参数的GDI函数,映像方式才会起作用。GetSystemMetrics不是GDI函数,所以它总是以设备单位(即图素)为量度来传回大小的。尽管GetDeviceCaps是GDI函数,需要一个设备内容句柄作为参数,但是Windows仍然对HORZRES和VERTRES以设备单位作为传回值,因为该函数的目的之一就是给程序提供以图素为单位的设备大小。
不过,从GetTextMetrics呼叫中传回的TEXTMETRIC结构的值是使用逻辑单位的。如果在进行此呼叫时映像方式为MM_LOENGLISH,则GetTextMetrics将以百分之一英寸为单位提供字符的宽度和高度。在呼叫GetTextMetrics以取得关于字符的宽度和高度信息时,映像方式必须设定成根据这些信息输出文字时所使用的映像方式,这样就可以简化工作。
设备坐标系
Windows将GDI函数中指定的逻辑坐标映像为设备坐标。在讨论以各种不同的映像方式使用逻辑坐标系之前,我们先来看一下Windows为视讯显示器区域定义的不同的设备坐标系。尽管我们大多数时间在窗口的显示区域内工作,但Windows在不同的时间使用另外两种设备坐标区域。所有设备坐标系都以图素为单位,水平轴(即x轴)上的值从左到右递增,垂直轴(即y轴)上的值从上到下递增。
当我们使用整个屏幕时,就根据「屏幕坐标」进行操作。屏幕的左上角为(0,0)点,屏幕坐标用在WM_MOVE消息(对于非子窗口)以及下列Windows函数中:CreateWindow和MoveWindow(都是对于非子窗口)、GetMessagePos、GetCursorPos、SetCursorPos、GetWindowRect以及WindowFromPoint(这不是全部函数的列表)。它们或者是与窗口无关的函数(如两个光标函数),或者是必须相对于某个屏幕点来移动(或者寻找)窗口的函数。如果以DISPLAY为参数呼叫CreateDC,以取得整个屏幕的设备内容,则内定情况下GDI呼叫中指定的逻辑坐标将被映像为屏幕坐标。
「全窗口坐标」以程序的整个窗口为基准,如标题列、菜单、滚动条和窗口框都包括在内。而对于普通窗口,点(0,0)是缩放边框的左上角。全窗口坐标在Windows中极少使用,但是如果用GetWindowDC取得设备内容,GDI函数中的逻辑坐标就会转换为显示区域坐标。
第三种坐标系是我们最常使用的「显示区域坐标系」。点(0,0)是显示区域的左上角。当使用GetDC或BeginPaint取得设备内容时,GDI函数中的逻辑坐标就会内定转换为显示区域坐标。
用函数ClientToScreen和ScreenToClient可以将显示区域坐标转换为屏幕坐标,或者反过来,将屏幕坐标转换为显示区域坐标。也可以使用GetWindowRect函数取得屏幕坐标下的整个窗口的位置和大小。这三个函数为一种设备坐标转换为另一种提供了足够的信息。
视端口和窗口
映像方式定义了Windows如何将GDI函数中指定的逻辑坐标映像为设备坐标,这里的设备坐标系取决于您用哪个函数来取得设备内容。要继续讨论映像方式,我们需要一些术语:映像方式用于定义从「窗口」(逻辑坐标)到「视端口」(设备坐标)的映像。
「窗口」和「视端口」这两个词用得并不恰当。在其它图形接口语言中,视端口通常包含有剪裁区域的意思,并且,我们已经用窗口来指程序在屏幕上占据的区域。在这里的讨论中,我们必须把关于这些词的先入之见丢到一边。
「视端口」是依据设备坐标(图素)的。通常,视端口和显示区域相同,但是,如果您已经用GetWindowDC或CreateDC取得了一个设备内容,则视端口也可以是指整窗口坐标或者屏幕坐标。点(0,0)是显示区域(或者整个窗口或屏幕)的左上角,x的值向右增加,y的值向下增加。
「窗口」是依据逻辑坐标的,逻辑坐标可以是图素、毫米、英寸或者您想要的任何其它单位。您在GDI绘图函数中指定逻辑窗口坐标。
但是在真正的意义上,视端口和窗口仅是数学上的概念。对于所有的映像方式,Windows都用下面两个公式来将窗口(逻辑)坐标转化为视埠(设备)坐标:
其中,(xWindow,yWindow)是待转换的逻辑点,(xViewport,yViewport)是转换后的设备坐标点,一般情形下差不多就是显示区域坐标了。
这两个公式使用了分别指定窗口和视端口「原点」的点:(xWinOrg,yWinOrg)是逻辑坐标的窗口原点;(xViewOrg,yViewOrg)是设备坐标的视端口原点。在内定的设备内容中,这两个点均被设定为(0,0),但是它们可以改变。此公式意味着,逻辑点(xWinOrg,yWinOrg)总被映像为设备点(xViewOrg,yViewOrg)。如果窗口和视端口的原点是默认值(0,0),则公式简化为:
此公式还使用了两点来指定「范围」:(xWinExt,yWinExt)是逻辑坐标的窗口范围;(xViewExt,yViewExt)是设备坐标的窗口范围。在多数映像方式中,范围是映像方式所隐含的,不能够改变。每个范围自身没有什么意义,但是视端口范围与窗口范围的比例是逻辑单位转换为设备单位的换算因子。
例如,当您设定MM_LOENGLISH映像方式时,Windows将xViewExt设定为某个图素数而将xWinExt设定为xViewExt图素占据的一英寸内有几百图素的长度。比值给出了一英寸内有几百个图素的数值。为了提高转换效能,换算因子表示为整数比而不是浮点数。
范围可以为负,也就是说,逻辑x轴上的值不一定非得在向右时增加;逻辑y轴上的值不一定非得在向下时增加。
Windows也能将视埠(设备)坐标转换为窗口(逻辑)坐标:
Windows提供了两个函数来让您将设备点转换为逻辑点以及将逻辑点转换为设备点。下面的函数将设备点转换为逻辑点:
其中,pPoints是一个指向POINT结构数组的指针,而iNumber是要转换的点的个数。您会发现这个函数对于将GetClientRect(它总是使用设备单位)取得的显示区域大小转换为逻辑坐标很有用:
下面的函数将逻辑点转换为设备点:
处理MM_TEXT
对于MM_TEXT映像方式,内定的原点和范围如下所示:
窗口原点:(0, 0) 可以改变
视埠原点:(0, 0) 可以改变
窗口范围:(1, 1) 不可改变
视埠范围:(1, 1) 不可改变
视端口范围与窗口范围的比例为1,所以不用在逻辑坐标与设备坐标之间进行缩放。上面所给出的公式可以简化为:
这种映像方式称为「文字」映像方式,不是因为它对于文字最适合,而是由于轴的方向。我们读文字是从左至右,从上至下的,而MM_TEXT以同样的方向定义轴上值的增长方向:
Windows提供了函数SetViewportOrgEx和SetWindowOrgEx,用来改变视端口和窗口的原点,这些函数都具有改变轴的效果,以致(0,0)不再指左上角。一般来说,您会使用SetViewportOrgEx或SetWindowOrgEx之一,但不会同时使用二者。
我们来看一看这些函数有何效果:如果将视埠原点改变为(xViewOrg,yViewOrg),则逻辑点(0.0)就会映像为设备点(xViewOrg,yViewOrg)。如果将窗口原点改变为(xWinOrg,yWinOrg),则逻辑点(xWinOrg,yWinOrg)将会映像为设备点(0,0),即左上角。不管对窗口和视端口原点作什么改变,设备点(0,0)始终是显示区域的左上角。
例如,假设显示区域为cxClient个图素宽和cyClient个图素高。如果想将逻辑点(0,0)定义为显示区域的中心,可进行如下呼叫:
SetViewportOrgEx的参数总是使用设备单位。现在,逻辑点(0,0)将映像为设备点(cxClient/2,cyClient/2),而显示区域的坐标系变成如下形状:
逻辑x轴的范围从-cxClient/2到+cxClient/2,逻辑y轴的范围从-cyClient/2到+cyClient/2,显示区域的右下角为逻辑点(cxClient/2,cyClient/2)。如果您想从显示区域的左上角开始显示文字。则需要使用负坐标:
用下面的SetWindowOrgEx叙述可以获得与上面使用SetViewportOrgEx同样的效果:
SetWindowOrgEx的参数总是使用逻辑单位。在这个呼叫之后,逻辑点(-cxClient / 2,-cyClient / 2)映像为设备点(0,0),即显示区域的左上角。
您不会将这两个函数一起用,除非您知道这么做的结果:
这意味着逻辑点(-cxClient/2,-cyClient/2)将映像为设备点(cxClient/2, cyClient/2),结果是如下所示的坐标系:
您可以使用下面两个函数取得目前视端口和窗口的原点:
其中pt是POINT结构。由GetViewportOrgEx传回的值是设备坐标,而由GetWindowOrgEx传回的值是逻辑坐标。
您可能想改变视端口或者窗口的原点,以改变窗口显示区域内的显示输出-例如,响应使用者在滚动条内的输入。但是,改变视端口和窗口原点并不能立即改变显示输出,而必须在改变原点之后更新输出。例如,在第四章的SYSMETS2程序中,我们使用了iVscrollPos值(垂直滚动条的目前位置)来调整显示输出的y坐标:
我们可以使用SetWindowOrgEx获得同样的效果:
现在,TextOut函数的y坐标的计算不需要iVscrollPos的值。这意味着您可以将文字输出函数放到一个例程中,不用将iVscrollPos值传给该例程,因为我们是通过改变窗口原点来调整文字显示的。
如果您有使用直角坐标系(即笛卡尔坐标系)的经验,那么将逻辑点(0,0)移到显示区域的中央(像我们上面所说的那样)的确值得考虑。但是,对于MM_TEXT映像方式来说,还存在着一个小小的问题:笛卡尔坐标系中,y值是随着上移而增加的,而MM_TEXT定义为下移时y值增加。从这一点来看,MM_TEXT有点古怪,而下面这五种映射方式都使用通常的增值方法。
「度量」映像方式
Windows包含五种以实际尺寸来表示逻辑坐标的映像方式。由于x轴和y轴的逻辑坐标映像为相同的实际单位,这些映像方式能使您画出不变形的圆和矩形。
这五种「度量」映像方式在表5-6中列出,按照从低精度到高精度的顺序排列。右边的两列分别给出了以英寸和毫米为单位时逻辑单位的大小,以便比较。
内定窗口及视端口的原点和范围如下所示:
窗口原点:(0, 0) 可以改变
视埠原点:(0, 0) 可以改变
窗口范围:(1, 1) 不可改变
视埠范围:(1, 1) 不可改变
问号表示窗口和视端口的范围依赖于映像方式和设备的分辨率。前面已经提到过,这些范围本身并不重要,但是表示比例时就必须知道。下面是窗口坐标到视端口坐标的转换公式:
例如,对于MM_LOENGLISH,Windows计算的范围如下:
Windows使用这些来自GetDeviceCaps的有用信息设定范围。只是在Windows 98和Windows NT之间有一点差别。
首先,来看看Windows 98是如何做的:假设您使用「控制台」的「显示」程序选择了96 dpi的系统字体。GetDeviceCaps对于LOGPIXELSX和LOGPIXELSY索引都将传回值96。Windows为视埠范围使用这些值并以表5-7的方式设定视端口和窗口的范围。
这样,对MM_LOENGLISH来说,96除以100的比值是0.01英寸中的图素数。对MM_LOMETRIC来说,96除以254的比值是0.1毫米中的图素数。
Windows NT使用不同的方法设定视端口和窗口的范围(与早期16位版本的Windows一致的方法)。视端口范围依据屏幕的图素尺寸。可以使用HORZRES和VERTRES索引从GetDeviceCaps取得这种信息。窗口范围依据假定的显示大小,它是您使用HORZSIZE和VERTSIZE索引时由GetDeviceCaps传回的。我在前面提到过,这些值一般是320和240毫米。如果您将显示器的图素尺寸设定为1024×768,则表5-8就是Windows NT报告的视端口和窗口范围的值。
这些窗口范围表示包含显示器全部宽度和高度的逻辑单位元数值。320毫米宽的屏幕也为1260 MM_LOENGLISH单位或12.6英寸(320除以25.4毫米/英寸)。
范围中,y前面的负号表示改变了轴的方向。对于这五种映像方式,y值随上升而增加,然而注意内定的窗口和视端口原点均为(0,0)。这个事实有一个有趣的结果。当一开始改变为五种映像方式之一时,坐标系如下:
要想在显示区域显示任何东西,必须使用负的y值。例如下面的程序代码:
将把文字显示在距离显示区域左边和上边各一英寸的地方。
为了使自己保持头脑清醒,您可能想避免这样做。一种解决办法是将逻辑的(0,0)点设为显示区域的左下角,您可以通过呼叫SetViewportOrgEx来完成(假设cyClient是以图素为单位的显示区域的高度):
此时的坐标系如下:
这是直角坐标系的右上象限。
另一种方法是将逻辑(0,0)点设为显示区域的中心:
此时的坐标系如下所示:
现在,我们有了一个真正的4象限笛卡尔坐标系,在x轴和y轴上有相等的按英寸、毫米或twip计算的逻辑单位。
您还可以使用SetWindowOrgEx函数来改变逻辑(0,0)点,但是这稍微困难一些,因为SetWindowOrgEx的参数必须使用逻辑单位,先要将(cxClient,cyClient)用DPtoLP函数转换为逻辑坐标。假设变量pt是型态为POINT的结构,下面的代码将逻辑(0,0)点改变到显示区域的中央:
「自行决定」的映像方式
剩下的两种映像方式为MM_ISOTROPIC和MM_ANISOTROPIC。只有这两种映像方式可以让您改变视端口和窗口范围,也就是说可以改变Windows用来转换逻辑和设备坐标的换算因子。「isotropic」的意思是「同方向性」;「anisotropic」的意思是「异方向性」。与上面所讨论的度量映射方式相似,MM_ISOTROPIC使用相同的轴,x轴上的逻辑单位与y轴上的逻辑单位的实际尺寸相等。这对您建立纵横比与显示比无关的图像是有帮助的。
MM_ISOTROPIC与度量映像方式之间的区别是,使用MM_ISOTROPIC,您可以控制逻辑单位的实际尺寸。如果愿意,您可以根据显示区域的大小来调整逻辑单位的实际尺寸,从而使所画的图像总是包含在显示区域内,并相应地放大或缩小。例如, 第八章的两个时钟程序就是方向同性的例子。在您改变窗口大小时,时钟也相应地调整。
Windows程序完全可以通过调整窗口和视端口范围来处理图像大小的变化。因此,不管窗口尺寸怎样变,程序都可以在绘图函数中使用相同的逻辑单位。
有时候MM_TEXT和度量映像方式称为「完全局限性」映像方式,这就是说,您不能改变窗口和视端口的范围以及Windows将逻辑坐标换算为设备坐标的方法。MM_ISOTROPIC是一种「半局限性」的映像方式,Windows允许您改变窗口和视端口范围,但只是调整它们,以便x和y逻辑单位代表同样的实际尺寸。MM_ANISOTROPIC映像方式是「非局限性」的,您可以改变窗口和视端口范围,但是Windows不调整这些值。
MM_ISOTROPIC映像方式
如果想要在使用任意的轴时都保证两个轴上的逻辑单位相同,则MM_ISOTROPIC映像方式就是理想的映像方式。这时,具有相同逻辑宽度和高度的矩形显示为正方形,具有相同逻辑宽度和高度的椭圆显示为圆。
当您刚开始将映像方式设定为MM_ISOTROPIC时,Windows使用与MM_LOMETRIC同样的窗口和视端口范围(但是,不要对此有所依赖)。区别在于,您现在可以呼叫SetWindowExtEx和SetViewportExtEx来根据自己的偏好改变范围了,然后,Windows将调整范围的值,以便两条轴上的逻辑单位有相同的实际距离。
一般说来,您可以用所期望的逻辑窗口的逻辑尺寸作为SetWindowExtEx的参数,用显示区域的实际宽和高作为SetViewportExtEx的参数。Windows在调整这些范围时,必须让逻辑窗口适应实际窗口,这就有可能导致显示区域的一段落到了逻辑窗口的外面。必须在呼叫SetViewportExtEx之前呼叫SetWindowExtEx,以便最有效地使用显示区域中的空间。
例如,假设您想要一个「传统的」单象限虚拟坐标系,其中(0,0)在显示区域的左下角,宽度和高度的范围都是从0到32,767,并且希望x和y轴的单位具有同样的实际尺寸。以下就是所需的程序:
如果其后用GetWindowExtEx和GetViewportExtEx函数获得了窗口和视端口的范围,可以发现,它们并不是先前指定的值。Windows将根据显示设备的纵横比来调整范围,以便两条轴上的逻辑单位表示相同的实际尺寸。
如果显示区域的宽度大于高度(以实际尺寸为准),Windows将调整x的范围,以便逻辑窗口比显示区域视端口窄。这样,逻辑窗口将放置在显示区域的左边:
Windows 98不允许在显示区域的右边超越x轴的范围之外显示任何东西,因为这需要一个大于16位所能表示的坐标。Windows NT使用全32位坐标,您可以在超出右边显示一些东西。
如果显示区域的高度大于宽度(以实际尺寸为准),那么Windows将调整y的范围。这样,逻辑窗口将放置在显示区域的下边:
Windows 98不允许在显示区域的顶部显示任何东西。
如果您希望逻辑窗口总是放在显示区域的左上部,那么将前面给出的程序代码改为:
在呼叫SetWindowOrgEx中,我们要求将逻辑点(0, 32767)映像为设备点(0,0)。现在,如果显示区域的高大于宽,则坐标系将安排为:
对于时钟程序,您也许想要使用一个四象限的笛卡尔坐标系,四个方向的坐标尺度可以任意指定,(0,0) 必须居于显示区域的中央。如果您想要每条轴的范围从0到1000,则可以使用以下程序代码:
如果显示区域的宽度大于高度,则逻辑坐标系形如:
如果显示区域的高度大于宽度,那么逻辑坐标也会居中:
记住,窗口或者视端口范围并不意味着要进行剪裁。在呼叫GDI函数时,您仍然对以随便地使用小于-1000和大于1000的x和y值。根据显示区域的外形,这些点可能看得见,也可能看不见。
在MM_ISOTROPIC映像方式下,可以使逻辑单位大于图素。例如,假设您想要一种映像方式,使点(0,0)显示在屏幕的左上角,y的值向下增长(和MM_TEXT相似),但是逻辑坐标单位为1/16英寸。以下是一种方法:
SetWindowExtEx函数的参数指出了每一英寸中逻辑单位数。SetViewportExtEx函数的参数指出了每一英寸中实际单位数(图素)。
然而,这种方法与Windows NT中的度量映像方式不一致。这些映射方式使用显示器的图素大小和公制大小。要与度量映像方式保持一致,可以这样做:
在这个程序代码中,视埠范围设定为按图素计算的整个屏幕的大小,窗口范围则必须设定为以1/16英寸为单位的整个屏幕的大小。GetDeviceCaps以HORZRES和VERTRES为参数,传回以毫米为单位的设备尺寸。如果我们使用浮点数,将把毫米数除以25.4,转换为英寸,然后,再乘以16以转换为l/16英寸。但是,由于我们使用的是整数,所以先乘以160,再除以254。
当然,这种坐标系会使逻辑单位大于实际单位。在设备上输出的所有东西都将映像为按1/16英寸增量的坐标值。当然,这样就不能画两条间隔l/32英寸的水平直线,因为这样将需要小数逻辑坐标。
MM_ANISOTROPIC:根据需要放缩图像
在MM_ISOTROPIC映像方式下设定窗口和视端口范围时,Windows会调整范围,以便两条轴上的逻辑单位具有相同的实际尺度。在MM_ANISOTROPIC映射方式下,Windows不对您所设定的值进行调整,这就是说,MM_ANISOTROPIC不需要维持正确的纵横比。
使用MM_ANISOTROPIC的一种方法是对显示区域使用任意坐标,就像我们对MM_ISOTROPIC所做的一样。下面的程序代码将点(0,0)设定为显示区域的左下角,x轴和y轴都从0到32,767:
在MM_ISOTROPIC方式下,相似的程序代码导致显示区域的一部分在轴的范围之外。但是对于MM_ANISOTROPIC,不论其尺度多大,显示区域的右上角总是(32767, 32767)。如果显示区域不是正方形的,则逻辑x和y的单位具有不同的实际尺度。
前一节在MM_ISOTROPIC映像方式下,我们讨论了在显示区域中画一个类似时钟的图像,x和y轴的范围都是从-1000到+1000。对于MM_ANISOTROPIC,也可以写出类似的程序:
与MM_ANISOTROPIC方式不同的是,这个时钟一般是椭圆形的,而不是圆形的。
另一种使用MM_ANISOTROPIC的方法是将x和y轴的单位固定,但其值不相等。例如,如果有一个只显示文字的程序,您可能想根据单个字符的高度和宽度设定一种粗刻度的坐标:
当然,这里假设cxChar和cyChar分别是那种字体的字符宽度和高度。现在,您可以按字符行和列指定坐标。下面的叙述在距离显示区域左边三个字符,上边二个字符处显示文字:
如果您使用固定大小的字体时会更加方便,就像下面的WHATSIZE程序所示的那样。
当您第一次设定MM_ANISOTROPIC映像方式时,它总是继承前面所设定的映像方式的范围,这会很方便。可以认为MM_ANISOTROPIC不「锁定」范围;也就是说,它允许您任意改变窗口范围。例如,假设您想用MM_LOENGLISH映像方式,因为希望逻辑单位为0.01英寸,但您不希望y轴的值向上增加,喜欢如MM_TEXT那样的方向,即y轴的值向下增加,可以使用如下的代码:
其它行程序
我们首先将映像方式设定为MM_LOENGLISH,然后,通过将映像方式设定为MM_ANISOTROPIC让范围可以自由改变。GetViewportExtEx取得视埠范围并放到一个SIZE结构中,然后,我们使用范围来呼叫SetViewportExtEx,只是要将y范围取反。
WHATSIZE程序
Windows的小历史:第一篇如何写作Windows程序的介绍文章出现在《Microsoft Systems Journal》1986年12月号上。在那篇文章中,范例程序叫做WSZ(「what size:什么尺寸」),它以图素、英寸和毫米为单位显示了显示区域的大小。那个程序的更简易版本是WHATSIZE,如程序5-6所示。程序显示了以五种度量映像方式显示的窗口显示区域的大小。
为了便于用TextOut函数显示信息,WHATSIZE使用了一种固定间距的字体。下面一条简单的叙述就可以切换为固定间距的字体(在Windows 3.0中它是优先使用的):
有两个同样的函数用于选取画笔和画刷。像前面提到的,WHATSIZE也使用MM_ANISTROPIC映像方式将逻辑单位设定为字符大小。
当WHATSIZE需要取得六种映像方式之一的显示区域的大小时,它保存目前的设备内容,设定一种新的映像方式,取得显示区域坐标,将它们转换为逻辑坐标,然后在显示信息之前,恢复原映像方式。底下这些程序代码在WHATSIZE的Show函数里:
图5-19显示了WHATSIZE的典型输出。
您也许会问:如果使用MM_LOENGLISH映射方式,是不是将会得到以百分之一英寸为单位的WM_SIZE消息呢?绝对不会。Windows对所有消息(如WM_MOVE、WM_SIZE和WM_MOUSEMOVE),对所有非GDI函数,甚至对一些GDI函数,永远使用设备坐标。可以这样来考虑:由于映像方式是一种设备内容属性,所以,只有对需要设备内容句柄作参数的GDI函数,映像方式才会起作用。GetSystemMetrics不是GDI函数,所以它总是以设备单位(即图素)为量度来传回大小的。尽管GetDeviceCaps是GDI函数,需要一个设备内容句柄作为参数,但是Windows仍然对HORZRES和VERTRES以设备单位作为传回值,因为该函数的目的之一就是给程序提供以图素为单位的设备大小。
不过,从GetTextMetrics呼叫中传回的TEXTMETRIC结构的值是使用逻辑单位的。如果在进行此呼叫时映像方式为MM_LOENGLISH,则GetTextMetrics将以百分之一英寸为单位提供字符的宽度和高度。在呼叫GetTextMetrics以取得关于字符的宽度和高度信息时,映像方式必须设定成根据这些信息输出文字时所使用的映像方式,这样就可以简化工作。
设备坐标系
Windows将GDI函数中指定的逻辑坐标映像为设备坐标。在讨论以各种不同的映像方式使用逻辑坐标系之前,我们先来看一下Windows为视讯显示器区域定义的不同的设备坐标系。尽管我们大多数时间在窗口的显示区域内工作,但Windows在不同的时间使用另外两种设备坐标区域。所有设备坐标系都以图素为单位,水平轴(即x轴)上的值从左到右递增,垂直轴(即y轴)上的值从上到下递增。
当我们使用整个屏幕时,就根据「屏幕坐标」进行操作。屏幕的左上角为(0,0)点,屏幕坐标用在WM_MOVE消息(对于非子窗口)以及下列Windows函数中:CreateWindow和MoveWindow(都是对于非子窗口)、GetMessagePos、GetCursorPos、SetCursorPos、GetWindowRect以及WindowFromPoint(这不是全部函数的列表)。它们或者是与窗口无关的函数(如两个光标函数),或者是必须相对于某个屏幕点来移动(或者寻找)窗口的函数。如果以DISPLAY为参数呼叫CreateDC,以取得整个屏幕的设备内容,则内定情况下GDI呼叫中指定的逻辑坐标将被映像为屏幕坐标。
「全窗口坐标」以程序的整个窗口为基准,如标题列、菜单、滚动条和窗口框都包括在内。而对于普通窗口,点(0,0)是缩放边框的左上角。全窗口坐标在Windows中极少使用,但是如果用GetWindowDC取得设备内容,GDI函数中的逻辑坐标就会转换为显示区域坐标。
第三种坐标系是我们最常使用的「显示区域坐标系」。点(0,0)是显示区域的左上角。当使用GetDC或BeginPaint取得设备内容时,GDI函数中的逻辑坐标就会内定转换为显示区域坐标。
用函数ClientToScreen和ScreenToClient可以将显示区域坐标转换为屏幕坐标,或者反过来,将屏幕坐标转换为显示区域坐标。也可以使用GetWindowRect函数取得屏幕坐标下的整个窗口的位置和大小。这三个函数为一种设备坐标转换为另一种提供了足够的信息。
视端口和窗口
映像方式定义了Windows如何将GDI函数中指定的逻辑坐标映像为设备坐标,这里的设备坐标系取决于您用哪个函数来取得设备内容。要继续讨论映像方式,我们需要一些术语:映像方式用于定义从「窗口」(逻辑坐标)到「视端口」(设备坐标)的映像。
「窗口」和「视端口」这两个词用得并不恰当。在其它图形接口语言中,视端口通常包含有剪裁区域的意思,并且,我们已经用窗口来指程序在屏幕上占据的区域。在这里的讨论中,我们必须把关于这些词的先入之见丢到一边。
「视端口」是依据设备坐标(图素)的。通常,视端口和显示区域相同,但是,如果您已经用GetWindowDC或CreateDC取得了一个设备内容,则视端口也可以是指整窗口坐标或者屏幕坐标。点(0,0)是显示区域(或者整个窗口或屏幕)的左上角,x的值向右增加,y的值向下增加。
「窗口」是依据逻辑坐标的,逻辑坐标可以是图素、毫米、英寸或者您想要的任何其它单位。您在GDI绘图函数中指定逻辑窗口坐标。
但是在真正的意义上,视端口和窗口仅是数学上的概念。对于所有的映像方式,Windows都用下面两个公式来将窗口(逻辑)坐标转化为视埠(设备)坐标:
其中,(xWindow,yWindow)是待转换的逻辑点,(xViewport,yViewport)是转换后的设备坐标点,一般情形下差不多就是显示区域坐标了。
这两个公式使用了分别指定窗口和视端口「原点」的点:(xWinOrg,yWinOrg)是逻辑坐标的窗口原点;(xViewOrg,yViewOrg)是设备坐标的视端口原点。在内定的设备内容中,这两个点均被设定为(0,0),但是它们可以改变。此公式意味着,逻辑点(xWinOrg,yWinOrg)总被映像为设备点(xViewOrg,yViewOrg)。如果窗口和视端口的原点是默认值(0,0),则公式简化为:
此公式还使用了两点来指定「范围」:(xWinExt,yWinExt)是逻辑坐标的窗口范围;(xViewExt,yViewExt)是设备坐标的窗口范围。在多数映像方式中,范围是映像方式所隐含的,不能够改变。每个范围自身没有什么意义,但是视端口范围与窗口范围的比例是逻辑单位转换为设备单位的换算因子。
例如,当您设定MM_LOENGLISH映像方式时,Windows将xViewExt设定为某个图素数而将xWinExt设定为xViewExt图素占据的一英寸内有几百图素的长度。比值给出了一英寸内有几百个图素的数值。为了提高转换效能,换算因子表示为整数比而不是浮点数。
范围可以为负,也就是说,逻辑x轴上的值不一定非得在向右时增加;逻辑y轴上的值不一定非得在向下时增加。
Windows也能将视埠(设备)坐标转换为窗口(逻辑)坐标:
Windows提供了两个函数来让您将设备点转换为逻辑点以及将逻辑点转换为设备点。下面的函数将设备点转换为逻辑点:
DPtoLP (hdc, pPoints, iNumber) ;
其中,pPoints是一个指向POINT结构数组的指针,而iNumber是要转换的点的个数。您会发现这个函数对于将GetClientRect(它总是使用设备单位)取得的显示区域大小转换为逻辑坐标很有用:
GetClientRect (hwnd, &rect) ; DPtoLP (hdc, (PPOINT) &rect, 2) ;
下面的函数将逻辑点转换为设备点:
LPtoDP (hdc, pPoints, iNumber) ;
处理MM_TEXT
对于MM_TEXT映像方式,内定的原点和范围如下所示:
窗口原点:(0, 0) 可以改变
视埠原点:(0, 0) 可以改变
窗口范围:(1, 1) 不可改变
视埠范围:(1, 1) 不可改变
视端口范围与窗口范围的比例为1,所以不用在逻辑坐标与设备坐标之间进行缩放。上面所给出的公式可以简化为:
这种映像方式称为「文字」映像方式,不是因为它对于文字最适合,而是由于轴的方向。我们读文字是从左至右,从上至下的,而MM_TEXT以同样的方向定义轴上值的增长方向:
Windows提供了函数SetViewportOrgEx和SetWindowOrgEx,用来改变视端口和窗口的原点,这些函数都具有改变轴的效果,以致(0,0)不再指左上角。一般来说,您会使用SetViewportOrgEx或SetWindowOrgEx之一,但不会同时使用二者。
我们来看一看这些函数有何效果:如果将视埠原点改变为(xViewOrg,yViewOrg),则逻辑点(0.0)就会映像为设备点(xViewOrg,yViewOrg)。如果将窗口原点改变为(xWinOrg,yWinOrg),则逻辑点(xWinOrg,yWinOrg)将会映像为设备点(0,0),即左上角。不管对窗口和视端口原点作什么改变,设备点(0,0)始终是显示区域的左上角。
例如,假设显示区域为cxClient个图素宽和cyClient个图素高。如果想将逻辑点(0,0)定义为显示区域的中心,可进行如下呼叫:
SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ;
SetViewportOrgEx的参数总是使用设备单位。现在,逻辑点(0,0)将映像为设备点(cxClient/2,cyClient/2),而显示区域的坐标系变成如下形状:
逻辑x轴的范围从-cxClient/2到+cxClient/2,逻辑y轴的范围从-cyClient/2到+cyClient/2,显示区域的右下角为逻辑点(cxClient/2,cyClient/2)。如果您想从显示区域的左上角开始显示文字。则需要使用负坐标:
TextOut (hdc, -cxClient / 2, -cyClient / 2, "Hello", 5) ;
用下面的SetWindowOrgEx叙述可以获得与上面使用SetViewportOrgEx同样的效果:
SetWindowOrgEx (hdc, -cxClient / 2, -cyClient / 2, NULL) ;
SetWindowOrgEx的参数总是使用逻辑单位。在这个呼叫之后,逻辑点(-cxClient / 2,-cyClient / 2)映像为设备点(0,0),即显示区域的左上角。
您不会将这两个函数一起用,除非您知道这么做的结果:
SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ;SetWindowOrgEx (hdc, -cxClient / 2, -cyClient / 2, NULL) ;
这意味着逻辑点(-cxClient/2,-cyClient/2)将映像为设备点(cxClient/2, cyClient/2),结果是如下所示的坐标系:
您可以使用下面两个函数取得目前视端口和窗口的原点:
GetViewportOrgEx (hdc, &pt) ; GetWindowOrgEx (hdc, &pt) ;
其中pt是POINT结构。由GetViewportOrgEx传回的值是设备坐标,而由GetWindowOrgEx传回的值是逻辑坐标。
您可能想改变视端口或者窗口的原点,以改变窗口显示区域内的显示输出-例如,响应使用者在滚动条内的输入。但是,改变视端口和窗口原点并不能立即改变显示输出,而必须在改变原点之后更新输出。例如,在第四章的SYSMETS2程序中,我们使用了iVscrollPos值(垂直滚动条的目前位置)来调整显示输出的y坐标:
case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; for (i = 0 ; i < NUMLINES ; i++) { y = cyChar * (i - iVscrollPos) ; // 显示文字 } EndPaint (hwnd, &ps) ; return 0 ;
我们可以使用SetWindowOrgEx获得同样的效果:
case WM_PAINT: hdc = BeginPaint (hwnd, &ps) ; SetWindowOrgEx (hdc, 0, cyChar * iVscrollPos) ; for (i = 0 ; i < NUMLINES ; i++) { y = cyChar * i ; // 显示文字 } EndPaint (hwnd, &ps) ; return 0 ;
现在,TextOut函数的y坐标的计算不需要iVscrollPos的值。这意味着您可以将文字输出函数放到一个例程中,不用将iVscrollPos值传给该例程,因为我们是通过改变窗口原点来调整文字显示的。
如果您有使用直角坐标系(即笛卡尔坐标系)的经验,那么将逻辑点(0,0)移到显示区域的中央(像我们上面所说的那样)的确值得考虑。但是,对于MM_TEXT映像方式来说,还存在着一个小小的问题:笛卡尔坐标系中,y值是随着上移而增加的,而MM_TEXT定义为下移时y值增加。从这一点来看,MM_TEXT有点古怪,而下面这五种映射方式都使用通常的增值方法。
「度量」映像方式
Windows包含五种以实际尺寸来表示逻辑坐标的映像方式。由于x轴和y轴的逻辑坐标映像为相同的实际单位,这些映像方式能使您画出不变形的圆和矩形。
这五种「度量」映像方式在表5-6中列出,按照从低精度到高精度的顺序排列。右边的两列分别给出了以英寸和毫米为单位时逻辑单位的大小,以便比较。
表5-6 |
映像方式 | 逻辑单位 | 英寸 | 毫米 |
MM_LOENGLISH | 0.01 in. | 0.01 | 0.254 |
MM_LOMETRIC | 0.1 mm. | 0.00394 | 0.1 |
MM_HIENGLISH | 0.001 in. | 0.001 | 0.0254 |
MM_TWIPS | 1/1400 in. | 0.000694 | 0.0176 |
MM_HIMETRIC | 0.01 mm. | 0.000394 | 0.01 |
窗口原点:(0, 0) 可以改变
视埠原点:(0, 0) 可以改变
窗口范围:(1, 1) 不可改变
视埠范围:(1, 1) 不可改变
问号表示窗口和视端口的范围依赖于映像方式和设备的分辨率。前面已经提到过,这些范围本身并不重要,但是表示比例时就必须知道。下面是窗口坐标到视端口坐标的转换公式:
例如,对于MM_LOENGLISH,Windows计算的范围如下:
Windows使用这些来自GetDeviceCaps的有用信息设定范围。只是在Windows 98和Windows NT之间有一点差别。
首先,来看看Windows 98是如何做的:假设您使用「控制台」的「显示」程序选择了96 dpi的系统字体。GetDeviceCaps对于LOGPIXELSX和LOGPIXELSY索引都将传回值96。Windows为视埠范围使用这些值并以表5-7的方式设定视端口和窗口的范围。
表5-7 |
映像方式 | 视埠范围(x,y) | 窗口范围(x,y) |
MM_LOMETRIC | (96, 96) | (254, -254) |
MM_HIMETRIC | (96, 96) | (2540, -2540) |
MM_LOENGLISH | (96, 96) | (100, -100) |
MM_HIENGLISH | (96, 96) | (1000, -1000) |
MM_TWIPS | (96, 96) | (1440, -1440) |
Windows NT使用不同的方法设定视端口和窗口的范围(与早期16位版本的Windows一致的方法)。视端口范围依据屏幕的图素尺寸。可以使用HORZRES和VERTRES索引从GetDeviceCaps取得这种信息。窗口范围依据假定的显示大小,它是您使用HORZSIZE和VERTSIZE索引时由GetDeviceCaps传回的。我在前面提到过,这些值一般是320和240毫米。如果您将显示器的图素尺寸设定为1024×768,则表5-8就是Windows NT报告的视端口和窗口范围的值。
表5-8 |
映像方式 | 视埠范围(x,y) | 窗口范围(x,y) |
MM_LOMETRIC | (1024, -768) | (3,200, 2,400) |
MM_HIMETRIC | (1024, -768) | (32,000, 24,000) |
MM_LOENGLISH | (1024, -768) | (1,260, 945) |
MM_HIENGLISH | (1024, -768) | (12,598, 9,449) |
MM_TWIPS | (1024, -768) | (18,142, 13,606) |
范围中,y前面的负号表示改变了轴的方向。对于这五种映像方式,y值随上升而增加,然而注意内定的窗口和视端口原点均为(0,0)。这个事实有一个有趣的结果。当一开始改变为五种映像方式之一时,坐标系如下:
要想在显示区域显示任何东西,必须使用负的y值。例如下面的程序代码:
SetMapMode (hdc, MM_LOENGLISH) ; TextOut (hdc, 100, -100, "Hello", 5) ;
将把文字显示在距离显示区域左边和上边各一英寸的地方。
为了使自己保持头脑清醒,您可能想避免这样做。一种解决办法是将逻辑的(0,0)点设为显示区域的左下角,您可以通过呼叫SetViewportOrgEx来完成(假设cyClient是以图素为单位的显示区域的高度):
SetViewportOrgEx (hdc, 0, cyClient, NULL) ;
此时的坐标系如下:
这是直角坐标系的右上象限。
另一种方法是将逻辑(0,0)点设为显示区域的中心:
SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ;
此时的坐标系如下所示:
现在,我们有了一个真正的4象限笛卡尔坐标系,在x轴和y轴上有相等的按英寸、毫米或twip计算的逻辑单位。
您还可以使用SetWindowOrgEx函数来改变逻辑(0,0)点,但是这稍微困难一些,因为SetWindowOrgEx的参数必须使用逻辑单位,先要将(cxClient,cyClient)用DPtoLP函数转换为逻辑坐标。假设变量pt是型态为POINT的结构,下面的代码将逻辑(0,0)点改变到显示区域的中央:
pt.x = cxClient ; pt.y = cyClient ; DptoLP (hdc, &pt, 1) ; SetWindowOrgEx (hdc, -pt.x / 2, -pt.y / 2, NULL) ;
「自行决定」的映像方式
剩下的两种映像方式为MM_ISOTROPIC和MM_ANISOTROPIC。只有这两种映像方式可以让您改变视端口和窗口范围,也就是说可以改变Windows用来转换逻辑和设备坐标的换算因子。「isotropic」的意思是「同方向性」;「anisotropic」的意思是「异方向性」。与上面所讨论的度量映射方式相似,MM_ISOTROPIC使用相同的轴,x轴上的逻辑单位与y轴上的逻辑单位的实际尺寸相等。这对您建立纵横比与显示比无关的图像是有帮助的。
MM_ISOTROPIC与度量映像方式之间的区别是,使用MM_ISOTROPIC,您可以控制逻辑单位的实际尺寸。如果愿意,您可以根据显示区域的大小来调整逻辑单位的实际尺寸,从而使所画的图像总是包含在显示区域内,并相应地放大或缩小。例如, 第八章的两个时钟程序就是方向同性的例子。在您改变窗口大小时,时钟也相应地调整。
Windows程序完全可以通过调整窗口和视端口范围来处理图像大小的变化。因此,不管窗口尺寸怎样变,程序都可以在绘图函数中使用相同的逻辑单位。
有时候MM_TEXT和度量映像方式称为「完全局限性」映像方式,这就是说,您不能改变窗口和视端口的范围以及Windows将逻辑坐标换算为设备坐标的方法。MM_ISOTROPIC是一种「半局限性」的映像方式,Windows允许您改变窗口和视端口范围,但只是调整它们,以便x和y逻辑单位代表同样的实际尺寸。MM_ANISOTROPIC映像方式是「非局限性」的,您可以改变窗口和视端口范围,但是Windows不调整这些值。
MM_ISOTROPIC映像方式
如果想要在使用任意的轴时都保证两个轴上的逻辑单位相同,则MM_ISOTROPIC映像方式就是理想的映像方式。这时,具有相同逻辑宽度和高度的矩形显示为正方形,具有相同逻辑宽度和高度的椭圆显示为圆。
当您刚开始将映像方式设定为MM_ISOTROPIC时,Windows使用与MM_LOMETRIC同样的窗口和视端口范围(但是,不要对此有所依赖)。区别在于,您现在可以呼叫SetWindowExtEx和SetViewportExtEx来根据自己的偏好改变范围了,然后,Windows将调整范围的值,以便两条轴上的逻辑单位有相同的实际距离。
一般说来,您可以用所期望的逻辑窗口的逻辑尺寸作为SetWindowExtEx的参数,用显示区域的实际宽和高作为SetViewportExtEx的参数。Windows在调整这些范围时,必须让逻辑窗口适应实际窗口,这就有可能导致显示区域的一段落到了逻辑窗口的外面。必须在呼叫SetViewportExtEx之前呼叫SetWindowExtEx,以便最有效地使用显示区域中的空间。
例如,假设您想要一个「传统的」单象限虚拟坐标系,其中(0,0)在显示区域的左下角,宽度和高度的范围都是从0到32,767,并且希望x和y轴的单位具有同样的实际尺寸。以下就是所需的程序:
SetMapMode (hdc, MM_ISOTROPIC) ;
SetWindowExtEx (hdc, 32767, 32767, NULL) ;
SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ;
SetViewportOrgEx (hdc, 0, cyClient, NULL) ;
如果其后用GetWindowExtEx和GetViewportExtEx函数获得了窗口和视端口的范围,可以发现,它们并不是先前指定的值。Windows将根据显示设备的纵横比来调整范围,以便两条轴上的逻辑单位表示相同的实际尺寸。
如果显示区域的宽度大于高度(以实际尺寸为准),Windows将调整x的范围,以便逻辑窗口比显示区域视端口窄。这样,逻辑窗口将放置在显示区域的左边:
Windows 98不允许在显示区域的右边超越x轴的范围之外显示任何东西,因为这需要一个大于16位所能表示的坐标。Windows NT使用全32位坐标,您可以在超出右边显示一些东西。
如果显示区域的高度大于宽度(以实际尺寸为准),那么Windows将调整y的范围。这样,逻辑窗口将放置在显示区域的下边:
Windows 98不允许在显示区域的顶部显示任何东西。
如果您希望逻辑窗口总是放在显示区域的左上部,那么将前面给出的程序代码改为:
SetMapMode (MM_ISOTROPIC) ; SetWindowExtEx (hdc, 32767, 32767, NULL) ; SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ; SetWindowOrgEx (hdc, 0, 32767, NULL) ;
在呼叫SetWindowOrgEx中,我们要求将逻辑点(0, 32767)映像为设备点(0,0)。现在,如果显示区域的高大于宽,则坐标系将安排为:
对于时钟程序,您也许想要使用一个四象限的笛卡尔坐标系,四个方向的坐标尺度可以任意指定,(0,0) 必须居于显示区域的中央。如果您想要每条轴的范围从0到1000,则可以使用以下程序代码:
SetMapMode (hdc, MM_ISOTROPIC) ;
SetWindowExtEx (hdc, 1000, 1000, NULL) ;
SetViewportExtEx (hdc, cxClient / 2, -cyClient / 2, NULL) ;
SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ;
如果显示区域的宽度大于高度,则逻辑坐标系形如:
如果显示区域的高度大于宽度,那么逻辑坐标也会居中:
记住,窗口或者视端口范围并不意味着要进行剪裁。在呼叫GDI函数时,您仍然对以随便地使用小于-1000和大于1000的x和y值。根据显示区域的外形,这些点可能看得见,也可能看不见。
在MM_ISOTROPIC映像方式下,可以使逻辑单位大于图素。例如,假设您想要一种映像方式,使点(0,0)显示在屏幕的左上角,y的值向下增长(和MM_TEXT相似),但是逻辑坐标单位为1/16英寸。以下是一种方法:
SetMapMode (hdc, MM_ISOTROPIC) ; SetWindowExtEx (hdc, 16, 16, NULL) ; SetViewportExtEx (hdc, GetDeviceCaps (hdc, LOGPIXELSX), GetDeviceCaps (hdc, LOGPIXELSY), NULL) ;
SetWindowExtEx函数的参数指出了每一英寸中逻辑单位数。SetViewportExtEx函数的参数指出了每一英寸中实际单位数(图素)。
然而,这种方法与Windows NT中的度量映像方式不一致。这些映射方式使用显示器的图素大小和公制大小。要与度量映像方式保持一致,可以这样做:
SetMapMode (hdc, MM_ISOTROPIC) ; SetWindowExtEx (hdc, 160 * GetDeviceCaps (hdc, HORZSIZE) / 254, 160 * GetDeviceCaps (hdc, VERTSIZE) / 254, NULL) ; SetViewportExtEx (hdc, GetDeviceCaps (hdc, HORZRES), GetDeviceCaps (hdc, VERTRES), NULL) ;
在这个程序代码中,视埠范围设定为按图素计算的整个屏幕的大小,窗口范围则必须设定为以1/16英寸为单位的整个屏幕的大小。GetDeviceCaps以HORZRES和VERTRES为参数,传回以毫米为单位的设备尺寸。如果我们使用浮点数,将把毫米数除以25.4,转换为英寸,然后,再乘以16以转换为l/16英寸。但是,由于我们使用的是整数,所以先乘以160,再除以254。
当然,这种坐标系会使逻辑单位大于实际单位。在设备上输出的所有东西都将映像为按1/16英寸增量的坐标值。当然,这样就不能画两条间隔l/32英寸的水平直线,因为这样将需要小数逻辑坐标。
MM_ANISOTROPIC:根据需要放缩图像
在MM_ISOTROPIC映像方式下设定窗口和视端口范围时,Windows会调整范围,以便两条轴上的逻辑单位具有相同的实际尺度。在MM_ANISOTROPIC映射方式下,Windows不对您所设定的值进行调整,这就是说,MM_ANISOTROPIC不需要维持正确的纵横比。
使用MM_ANISOTROPIC的一种方法是对显示区域使用任意坐标,就像我们对MM_ISOTROPIC所做的一样。下面的程序代码将点(0,0)设定为显示区域的左下角,x轴和y轴都从0到32,767:
SetMapMode (hdc, MM_ANISOTROPIC) ;
SetWindowExtEx (hdc, 32767, 32767, NULL) ;
SetViewportExtEx (hdc, cxClient, -cyClient, NULL) ;
SetViewportOrgEx (hdc, 0, cyClient, NULL) ;
在MM_ISOTROPIC方式下,相似的程序代码导致显示区域的一部分在轴的范围之外。但是对于MM_ANISOTROPIC,不论其尺度多大,显示区域的右上角总是(32767, 32767)。如果显示区域不是正方形的,则逻辑x和y的单位具有不同的实际尺度。
前一节在MM_ISOTROPIC映像方式下,我们讨论了在显示区域中画一个类似时钟的图像,x和y轴的范围都是从-1000到+1000。对于MM_ANISOTROPIC,也可以写出类似的程序:
SetMapMode (hdc, MM_ANISOTROPIC) ;
SetWindowExtEx (hdc, 1000, 1000, NULL) ;
SetViewportExtEx (hdc, cxClient / 2, -cyClient / 2, NULL) ;
SetViewportOrgEx (hdc, cxClient / 2, cyClient / 2, NULL) ;
与MM_ANISOTROPIC方式不同的是,这个时钟一般是椭圆形的,而不是圆形的。
另一种使用MM_ANISOTROPIC的方法是将x和y轴的单位固定,但其值不相等。例如,如果有一个只显示文字的程序,您可能想根据单个字符的高度和宽度设定一种粗刻度的坐标:
SetMapMode (hdc, MM_ANISOTROPIC) ; SetWindowExtEx (hdc, 1, 1, NULL) ; SetViewportExtEx (hdc, cxChar, cyChar, NULL) ;
当然,这里假设cxChar和cyChar分别是那种字体的字符宽度和高度。现在,您可以按字符行和列指定坐标。下面的叙述在距离显示区域左边三个字符,上边二个字符处显示文字:
TextOut (hdc, 3, 2, TEXT ("Hello"), 5) ;
如果您使用固定大小的字体时会更加方便,就像下面的WHATSIZE程序所示的那样。
当您第一次设定MM_ANISOTROPIC映像方式时,它总是继承前面所设定的映像方式的范围,这会很方便。可以认为MM_ANISOTROPIC不「锁定」范围;也就是说,它允许您任意改变窗口范围。例如,假设您想用MM_LOENGLISH映像方式,因为希望逻辑单位为0.01英寸,但您不希望y轴的值向上增加,喜欢如MM_TEXT那样的方向,即y轴的值向下增加,可以使用如下的代码:
SIZE size ;
其它行程序
SetMapMode (hdc, MM_LOENGLISH) ; SetMapMode (hdc, MM_ANISOTROPIC) ; GetViewportExtEx (hdc, &size) ; SetViewportExtEx (hdc, size.cx, -size.cy, NULL) ;
我们首先将映像方式设定为MM_LOENGLISH,然后,通过将映像方式设定为MM_ANISOTROPIC让范围可以自由改变。GetViewportExtEx取得视埠范围并放到一个SIZE结构中,然后,我们使用范围来呼叫SetViewportExtEx,只是要将y范围取反。
WHATSIZE程序
Windows的小历史:第一篇如何写作Windows程序的介绍文章出现在《Microsoft Systems Journal》1986年12月号上。在那篇文章中,范例程序叫做WSZ(「what size:什么尺寸」),它以图素、英寸和毫米为单位显示了显示区域的大小。那个程序的更简易版本是WHATSIZE,如程序5-6所示。程序显示了以五种度量映像方式显示的窗口显示区域的大小。
程序5-6 WHATSIZE
WHATSIZE.C
/*------------------------------------------------------------
WHATSIZE.C -- What Size is the Window?
(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 ("WhatSize") ;
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 ("What Size is the Window?"),
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 ;
}
void Show (HWND hwnd, HDC hdc, int xText, int yText, int iMapMode,
TCHAR * szMapMode)
{
TCHAR szBuffer [60] ;
RECT rect ;
SaveDC (hdc) ;
SetMapMode (hdc, iMapMode) ;
GetClientRect (hwnd, &rect) ; DPtoLP (hdc, (PPOINT) &rect, 2) ;
RestoreDC (hdc, -1) ;
TextOut ( hdc, xText, yText, szBuffer,
wsprintf (szBuffer, TEXT ("%-20s %7d %7d %7d %7d"), szMapMode,
rect.left, rect.right, rect.top, rect.bottom)) ;
}
LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
static TCHAR szHeading [] =
TEXT ("Mapping Mode Left Right Top Bottom") ;
static TCHAR szUndLine [] =
TEXT ("------------ ---- ----- --- ------") ;
static int cxChar, cyChar ;
HDC hdc ;
PAINTSTRUCT ps ;
TEXTMETRIC tm ;
switch (message)
{
case WM_CREATE:
hdc = GetDC (hwnd) ;
SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;
GetTextMetrics (hdc, &tm) ;
cxChar = tm.tmAveCharWidth ;
cyChar = tm.tmHeight + tm.tmExternalLeading ;
ReleaseDC (hwnd, hdc) ;
return 0 ;
case WM_PAINT:
hdc = BeginPaint (hwnd, &ps) ;
SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;
SetMapMode (hdc, MM_ANISOTROPIC) ; SetWindowExtEx (hdc, 1, 1, NULL) ; SetViewportExtEx (hdc, cxChar, cyChar, NULL) ;
TextOut (hdc, 1, 1, szHeading, lstrlen (szHeading)) ;
TextOut (hdc, 1, 2, szUndLine, lstrlen (szUndLine)) ;
Show (hwnd, hdc, 1, 3, MM_TEXT, TEXT ("TEXT (pixels)")) ;
Show (hwnd, hdc, 1, 4, MM_LOMETRIC, TEXT ("LOMETRIC (.1 mm)")) ;
Show (hwnd, hdc, 1, 5, MM_HIMETRIC, TEXT ("HIMETRIC (.01 mm)")) ;
Show (hwnd, hdc, 1, 6, MM_LOENGLISH, TEXT ("LOENGLISH (.01 in)")) ;
Show (hwnd, hdc, 1, 7, MM_HIENGLISH,TEXT ("HIENGLISH (.001 in)")) ;
Show (hwnd, hdc, 1, 8, MM_TWIPS, EXT ("TWIPS (1/1440 in)")) ;
EndPaint (hwnd, &ps) ;
return 0 ;
case WM_DESTROY:
PostQuitMessage (0) ;
return 0 ;
}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}
为了便于用TextOut函数显示信息,WHATSIZE使用了一种固定间距的字体。下面一条简单的叙述就可以切换为固定间距的字体(在Windows 3.0中它是优先使用的):
SelectObject (hdc, GetStockObject (SYSTEM_FIXED_FONT)) ;
有两个同样的函数用于选取画笔和画刷。像前面提到的,WHATSIZE也使用MM_ANISTROPIC映像方式将逻辑单位设定为字符大小。
当WHATSIZE需要取得六种映像方式之一的显示区域的大小时,它保存目前的设备内容,设定一种新的映像方式,取得显示区域坐标,将它们转换为逻辑坐标,然后在显示信息之前,恢复原映像方式。底下这些程序代码在WHATSIZE的Show函数里:
SaveDC (hdc) ; SetMapMode (hdc, iMapMode) ; GetClientRect (hwnd, &rect) ; DptoLP (hdc, (PPOINT) &rect, 2) ; RestoreDC (hdc, -1) ;
图5-19显示了WHATSIZE的典型输出。
图5-19 典型的WHATSIZE显示 |
相关文章推荐
- (2)第5章--图形基础--GDI结构
- __86__C#_Graphics_GDI图形编程基础
- 五、GDI图形基础
- GDI基础(1):绘制线条和图形
- windows基础编程----第四篇(调用GDI绘制出相关图形)
- 图形编程基础_GDI对象基类
- GDI基础函数使用绘制线条和图形
- GDI+编程基础(一)GDI+ Vs GDI
- GDI+编程基础 GDI+ Vs GDI
- 蓝桥杯--基础练习 字母图形
- 3D数学基础图形与游戏开发之 坐标系
- 第26周-window程序设计(基础篇)-第5章(图形基础)-边界框及LINDEMO.C
- 第26周-window程序设计(基础篇)-第5章(图形基础)-使用现有画笔Stock Pens
- 游戏编程基础(三)GDI游戏动画基础
- GDI学习笔记 输出文字与图形
- OpenGL基础图形编程 - OpenGL辅助库的基本使用
- 蓝桥杯 BASIC_03 基础练习 字母图形
- 图形开发基础(2)
- Win32图形设计框架基础知识
- 安卓自定义View基础05-Canvas之基础图形绘制,点,线,矩形,圆,椭圆,弧形等