📄 见招拆招 windows 程序设计 (四) .txt
字号:
见招拆招 Windows 程序设计 (四)
相关的例子:下载>>> 作者:Zoologist 于2007-12-16上传
--------------------------------------------------------------------------------
图形设计,算是“不正经”的命题吧?初中我和师傅学习信息学竞赛的时候就热衷于这些,如何绘制漂亮的图形,如何实现简单的动画效果。那时候用的是Pascal语言,资料也非常少基本上只能自己摸索。后来偶然得到一本《Pascal大全》高兴得不得了。
不过自从操作系统从DOS进化到Windows,图形是用户交互的最好手段,图形方面的知识也成为编程者必须掌握的知识----对于大多数,“正常”的用户来说,在控制台方式下输入命令完成各种操作远比点击执行操作复杂的多。
这一期的内容就是在介绍图形方面的Windows编程,只是一点皮毛而已。其中的一些描述部分,我仍然保持C语言,因为如果用汇编语言描述可能会写得很长让人忘记了程序段的目的,不过整个的程序仍然是使用汇编语言进行描述的。
图形基础
图形设备接口(GDI:Graphics Device Interface)是Windows的子系统,它负责在显示器和打印机上显示图形。正如您所认为的那样,GDI是Windows非常重要的部分。不只您为Windows编写的应用系统在显示视觉信息时使用GDI,就连Windows本身也使用GDI来显示使用者接口对象,诸如菜单、滚动条、图标和鼠标光标。
不幸的是,如果要对GDI进行全面的讲述,将需要一整本书----当然更不是几期简单的文章。(推荐《Windows图形编程》,虽然俺没有看过,不过从各方面得到的信息来看是一本好书)在本章中,我只是想向您提供画线和填入区域的基本知识,这对于理解下面几章的GDI已经足够了。在后面几期中会讲述GDI支持的位图、metafile以及格式化文字。
GDI 的结构
从程序写作者的观点来看,GDI由几百个函数呼叫和一些相关的数据结构、宏和结构组成。但是在开始讲述这些函数的细节之前,让我们先从宏观上了解一下GDI的整体结构。
GDI原理
Windows 98和Microsoft Windows NT/XP中的图形主要由GDI32.DLL动态链接库输出的函数来处理。在Windows 98中,这个GDI32.DLL实际是利用16位GDI.EXE动态链接库来执行许多函数。在Windows NT/XP中,GDI.EXE只用于16位的程序。
这些动态链接库调用您安装的显卡和打印机呼驱动程序中的例程。显卡驱动程序存取视频显示器的硬件,打印机驱动程序将GDI命令转换为各种打印机能够理解的代码或者命令。显然,不同的显卡和打印机要求不同的设备驱动程序。
因为PC兼容机种上可以连接许多种不同的视频设备,所以,GDI的主要目的之一是支持与设备无关的图形。Windows程序应该能够毫无困难地在Windows支持的任意一种图形输出设备上执行,GDI通过将您的程序和不同输出设备的特性隔离开来的方法来达到这一目的。
图形输出设备分为两大类:光栅设备和矢量设备。大多数PC的输出设备是光栅设备,这意味着它们以图点构成的数组来表示图像,这类设备包括视频显示卡、点阵打印机和激光打印机。矢量设备使用线来绘制图像,通常局限于绘图机。(光栅好比用石头在沙地上拼字,比如,很早之前使用的UCDOS,里面包含 16X16 点阵字库,存放的就是每一个字的点的信息;而矢量存放得更像是方法,比如,人字的写法就是“一撇一捺”。Windows中有很多“矢量字库”,放大很多倍后仍然不会失真)
许多传统的计算机图形程序设计方式都是完全以矢量为主的,这意味着使用矢量图形系统的程序与硬件有着一定层次的隔离。输出设备用像素表示图形,但是程序与程序接口之间并不是用像素进行沟通的。您当然可以使用Windows GDI作为一个高阶的矢量绘制系统,同时也可以将它用于比较低阶的像素操作。从这方面来看,Windows GDI和传统的图形接口语言之间的关系,就如同C和其它程序设计语言之间的关系一样。C以它在不同操作系统和环境之间的高度可移植性而闻名,然而C也以允许程序写作者进行底层系统呼叫而闻名,这些呼叫在其它高级语言中通常是不可能的。正如C有时被认为是一种「高级汇编语言」一样,您可以认为GDI是图形设备硬件之间的一种高阶界面。
您已经看到,Windows内定使用像素坐标系统。大多数传统的图形语言使用「虚拟」坐标系,其水平和垂直轴的范围在0到32,767之间。虽然有些图形语言不让您使用像素坐标,但是Windows GDI允许您使用两种坐标系统之一(甚至依据实际度量衡的坐标系)。您可以使用虚拟坐标系以便让程序独立于硬件之外,或者也可以使用设备坐标系而完全迎合硬设备提供的环境。
某些程序写作者认为一旦开始使用操作像素的程序设计方式,就放弃了设备无关性。我们在上一期看到,这不完全是正确的,其中的诀窍是在与设备无关的方式中使用像素。这要求图形接口语言为程序提供一些方法来确定设备的硬件特征,并进行适当的调节。例如,在SYSMETS程序中,我们根据标准系统字体字符的像素大小来确定屏幕上的文字间距,这种方法允许程序针对分辨率、文字大小和方向比例各不相同的显示卡进行相应的调节。您将在本章看到一些用于确定显示尺寸的其它方法。
早期,许多使用者在单色显示器上执行Windows。为此,GDI的设计保证了您可以在编写一个程序时不必太担心色彩问题-也就是说,Windows可以将色彩转换为灰阶显示。甚至在今天,Windows 98/NT/XP使用的视频显示已经具有了不同的色彩能力(16色、256色、「high-Color」以及「true-color」)。虽然,彩色喷墨打印机的成本已经很低了,但是大多数使用者仍然坚持使用黑白打印机。盲目地使用这些设备是可以的,但是您的程序也应该能决定在某种显示设备上有多少色彩可以使用,从而最佳利用硬件功能。
当然,就如同您编写C程序时,为了使它在其它计算机上执行而遇到一些微妙的移植性问题一样,您也可能不小心让设备依赖性溜进您的Windows程序,这就是不与硬件完全隔离的代价。您还应该知道Windows GDI的局限。虽然可以在显示器上到处移动图形对象,但GDI通常是一个静态的显示系统,只有有限的动画支持。如果需要为游戏编写复杂的动画,就应该研究一下Microsoft DirectX或者OpenGL,它提供了您需要的支持。
GDI函数呼叫
组成GDI的几百个函数呼叫可以分为几大类:
取得(或者建立)和释放(或者清除)设备内容的函数 我们在前面的章节中已经看到过,您在绘图时需要设备内容句柄。GetDC和RealseDC函数让您在非WM_PAINT的消息处理期间来做到这一点,而BeginPaint和EndPaint函数(虽然在技术上它们是USER模块而不是GDI模块的一部分)在进行绘图的WM_PAINT消息处理期间使用。我们马上还会介绍有关设备内容的其它一些函数。
取得有关设备内容信息的函数 再以SYSMETS程序为例,我们使用GetTextMetrics函数来取得有关设备内容中目前所选字体的尺寸信息。在本章后面,我们将看到一个取得非常广泛的设备内容信息的DEVCAPS1程序。
绘图函数 显然,在所有前提条件都得以满足之后,这些函数是真正重要的部分。在上一期中,我们使用TextOut函数在窗口的显示区域显示一些文字。我们将看到,其它GDI函数还可以让您画线、填入区域。
设定和取得设备内容参数的函数 设备内容的「属性」决定有关绘图函数如何工作的细节。例如,用SetTextColor来指定TextOut(或者其它文字输出函数)所绘制的文字色彩。在SYSMETS程序中,我们使用SetTextAlign来告诉GDI:TextOut函数中的字符串的开始位置应该在字符串的右边而不是内定的左边。设备内容的所有属性都有默认值,取得设备内容时这些默认值就设定好了。对于所有的Set函数,都有相应的Get函数,以允许您取得目前设备内容属性。
使用GDI对象的函数 GDI在这里变得有点混乱。首先举一个例子:内定时使用GDI绘制的所有直线都是实线并具有一个标准的宽度。您可能希望绘制更细的直线,或者是由一系列的点或短划线组成的直线。这种线的宽度和这种线的画笔样式不是设备内容的属性,而是一个「逻辑画笔」的特征。您可以通过在CreatePen、 CreatePenIndirect或ExtCreatePen函数中指定这些特征来建立一个逻辑画笔,这些函数传回一个逻辑画笔的句柄(虽然这些函数被认为是GDI的一部分,但是和大多数GDI函数呼叫不一样,它们不要求设备内容的句柄)。要使用这个画笔,就要将画笔句柄选进设备内容。我们认为,设备内容中目前选中的画笔就是设备内容的一个属性。这样,您画任何线都使用这个画笔,然后,您可以取消设备内容中的画笔选择,并清除画笔对象。清除画笔对象是必要的,因为画笔定义占用了分配的内存空间。除了画笔以外,GDI对象还用于建立填入封闭区域的画刷、字体、位图以及GDI的其它一些方面。
GDI基本图形
您在屏幕或打印机上显示的图形型态本身可以被分为几类,通常被称为「基本图形」,它们是:
直线和曲线 线条是所有矢量图形绘制系统的基础。GDI支持直线、矩形、椭圆(包括椭圆的子集,也就是我们所说的「圆」)、椭圆圆周上的部分曲线即所谓的「弧」以及贝塞尔曲线(Bezier spline),我们将在本期中分别对它们进行介绍。所有更复杂的曲线可由折线(polyline)代替,折线通过一组非常短的直线来定义一条曲线。线条用设备内容中选中的目前画笔绘制。
填入区域 当一系列直线或者曲线封闭了一个区域时,该区域可以使用目前GDI画刷对象进行填图。这个画刷可以是实心色彩、图案(可以是一系列的水平、垂直或者对角标记)或者是在区域内垂直或者水平重复的位图图像。
位图 位图是位的矩形数组,这些位对应于显示设备上的像素,它们是光栅图形的基础工具。位图通常用于在视频显示器或者打印机上显示复杂(一般都是真实的)图像。位图还可以用于显示必须快速绘制的小图像(诸如图标、鼠标光标以及在应用工具条中出现的按钮等)。GDI支持两种型态的位图-旧式的(虽然还非常有用)「设备相关」位图,是GDI对象;和新的「设备无关」位图(或者DIB),可以储存在磁盘文件中。后面几期我们会讨论位图。
文字 文字的数学味道不像计算机图形的其它方面那样浓。文字和几百年的传统印刷术有关,它被许多印刷工人看作为一门艺术。因此,文字通常不仅是所有的计算机图形系统中最复杂的部分,而且(如果识字还是社会基本要求的话)也是最重要的部分。用于定义GDI字体对象和取得字体信息的数据结构是Windows中最庞大的部分之一。从Windows 3.1开始,GDI开始支持TrueType字体,该字体是在填入轮廓线基础上建立的,这样的填入轮廓线可由其它GDI函数处理。依据兼容性和储存大小的考虑,我们会在后面的一期讨论这个话题。
其它部分
GDI的其它部分无法这么容易地分类,它们是:
映像模式和变换 虽然内定以像素为单位进行绘图,但是您并非局限于此。GDI映像模式允许您以英寸(或者甚至以几分之一英寸)、毫米或者任何您想使用的单位来绘图。
Metafile Metafile是以二进制形式储存的GDI命令集合。Metafile主要用于通过剪贴板传输矢量图形。
绘图区域 绘图区域是形状任意的复杂区域,通常定义为较简单的绘图区域组合。在GDI内部,绘图区域除了储存为最初用来定义绘图区域的线条组合以外,还以一系列扫描线的形式储存。您可以将绘图区域用于绘制轮廓、填入图形和剪裁。
路径 路径是GDI内部储存的直线和曲线的集合。路径可以用于绘图、填入图形和剪裁,还可以转换为绘图区域。
剪裁 绘图可以限制在显示区域的某一部分中,这就是所谓的剪裁。剪裁区域是不是矩形都可以,剪裁通常是通过区域或者路径来定义的。
调色盘 自订调色盘通常限于显示256色的显示器。Windows仅保留这些色彩之中的20种以供系统使用,您可以改变其它236种色彩,以准确显示按位图形式储存的真实图像。
打印 虽然本章限于讨论视频显示,但是您在本章中所学到的全部知识都适用于打印。
设备内容
在开始绘图之前,让我们比上一期更精确地讨论一下设备内容。
当您想在一个图形输出设备(诸如屏幕或者打印机)上绘图时,您首先必须获得一个设备内容(或者DC)的句柄。将句柄传回给程序时,Windows就给了您使用设备的权限。然后您在GDI函数中将这个句柄作为一个参数,向Windows标识您想在其上进行绘图的设备。
设备内容中包含许多确定GDI函数如何在设备上工作的目前「属性」,这些属性允许传递给GDI函数的参数只包含起始坐标或者尺寸信息,而不必包含Windows在设备上显示对象时需要的所有其它信息。例如,呼叫TextOut时,您只需要在函数中给出设备内容句柄、起始坐标、文字和文字的长度。您不必指定字体、文字颜色、文字后面的背景色彩以及字符间距,因为这些属性都是设备内容的一部分。当您想改变这些属性之一时,您呼叫一个可以改变设备内容中属性的函数,以后针对该设备内容的TextOut呼叫来使用改变后的属性。
取得设备内容句柄
Windows提供了几种取得设备内容句柄的方法。如果在处理一个消息时取得了设备内容句柄,应该在退出窗口函数之前释放它(或者删除它)。一旦释放了句柄,它就不再有效了。对于打印机设备内容句柄,规则就没有这么严格。
最常用的取得并释放设备内容句柄的方法是,在处理WM_PAINT消息时,使用BeginPaint和EndPaint呼叫:
invoke BeginPaint,hwnd,addr ps
mov hdc,eax
其它行程序
invoke EndPaint,hwnd,addr ps
变量ps是型态为PAINTSTRUCT的结构,该结构的hdc字段是BeginPaint传回的设备内容句柄。 PAINTSTRUCT结构又包含一个名为rcPaint的RECT(矩形)结构,rcPaint定义一个包围窗口显示区域无效范围的矩形。使用从BeginPaint获得的设备内容句柄,只能在这个区域内绘图。BeginPaint呼叫使该区域有效。
Windows程序还可以在处理非WM_PAINT消息时取得设备内容句柄:
invoke GetDC,EAX
mov hdc,EAX
其它行程序
invoke ReleaseDC,hMine,hdc
这个设备内容适用于窗口句柄为hwnd的显示区域。这些呼叫与BeginPaint和EndPaint的组合之间的基本区别是,利用从GetDC传回的句柄可以在整个显示区域上绘图。当然, GetDC和ReleaseDC不使显示区域中任何可能的无效区域变成有效。
Windows程序还可以取得适用于整个窗口(而不仅限于窗口的显示区域)的设备内容句柄:
invoke GetWindowDC, hwnd
mov eax,hdc
其它行程序
invoke ReleaseDC, hwnd
这个设备内容除了显示区域之外,还包括窗口的标题列、菜单、滚动条和框架(frame)。GetWindowDC函数很少使用,如果想尝试用一用它,则必须拦截处理WM_NCPAINT消息,Windows使用该消息在窗口的非显示区域上绘图。
BeginPaint、GetDC和GetWindowDC获得的设备内容都与视频显示器上的某个特定窗口相关。取得设备内容句柄的另一个更通用的函数是CreateDC:
invoke CreateDC,pszDriver, pszDevice, pszOutput, pData
mov hdc,eax
其它行程序
Invoke DeleteDC,hdc
例如,您可以通过下面的呼叫来取得整个屏幕的设备内容句柄:
invoke CreateDC, CTXT("DISPLAY"), pszDevice, pszOutput, pData
mov hdc,eax
在窗口之外写入画面一般是不恰当的,但对于一些不同寻常的应用程序来说,这样做很方便(您还可通过在呼叫GetDC时使用一个NULL参数,从而取得整个屏幕的设备内容句柄,不过这在文件中已经提到了)。
有时您只是需要取得关于某设备内容的一些信息而并不进行任何绘画,在这种情况下,您可以使用CreateIC来取得一个「信息内容」的句柄,其参数与CreateDC函数相同,例如:
invoke CreateIC, CTXT("DISPLAY"), pszDevice, pszOutput, pData
mov hdc,eax
您不能用这个信息内容句柄往设备上写东西。
使用位图时,取得一个「内存设备内容」有时是有用的:
invoke CreateCompatibleDC,hdc
mov hdcMem,eax
其它行程序
invoke DeleteDC,hdcMem
您可以将位图选进内存设备内容,然后使用GDI函数在位图上绘画。
前面已经提到过,metafile是一些GDI呼叫的集合,以二进制形式编码。您可以通过取得metafile设备内容来建立metafile:
invoke CreateMetaFile, pszFilename
mov hdcMeta,eax
其它行程序
invoke CloseMetaFile, hdcMeta
在metafile设备内容有效期间,任何用hdcMeta所做的GDI呼叫都变成metafile的一部分而不会显示。在呼叫CloseMetaFile之后,设备内容句柄变为无效,函数传回一个指向metafile(hmf)的句柄。我会在后面一期讨论metafile。
取得设备内容信息
一个设备内容通常是指一个实际显示设备,如显示器和打印机。通常,您需要取得有关该设备的信息,包括显示器的大小(单位为像素或者实际长度单位)和色彩显示能力。您可以通过呼叫GetDeviceCaps(「取得设备功能」)函数来取得这些信息:
invoken GetDeviceCaps,hdc,iIndex
mov iValue,eax
其中,参数iIndex取值为 Windows.inc 表头文件中定义的29个标识符之一。例如,iIndex为HORZRES时将使GetDeviceCaps传回设备的宽度(单位为像素);iIndex为VERTRES时将让GetDeviceCaps传回设备的高度(单位为像素)。如果hdc是打印机设备内容的句柄,则GetDeviceCaps传回打印机显示区域的高度和宽度,它们也是以像素为单位的。
还可以使用GetDeviceCaps来确定设备处理不同型态图形的能力,这对于视频显示器并不很重要,但是对于打印设备却是非常重要。例如,大多数绘图机不能画位图图像,GetDeviceCaps就可以将这一情况告诉您。
DEVCAPS1程序所示的DEVCAPS1程序显示了以一个视频显示器的设备内容为参数时,可以从 GetDeviceCaps函数中获得的部分信息。
程序5-1 DEVCAPS1
DEVCAPS1.ASM
;MASMPlus 代码模板 - 普通的 Windows 程序代码
.386
.Model Flat, StdCall
Option Casemap :None
Include windows.inc
Include user32.inc
Include kernel32.inc
Include gdi32.inc
includelib gdi32.lib
IncludeLib user32.lib
IncludeLib kernel32.lib
include macro.asm
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
WndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD
NUMLINES equ (devcapsEnd - devcaps) / 4 /3
.DATA
szAppName db "DevCaps1",0
szShow01 db "HORZSIZE",0
szShow02 db "Width in millimeters:",0
szShow03 db "VERTSIZE",0
szShow04 db "Height in millimeters:",0
szShow05 db "HORZRES",0
szShow06 db "Height in raster lines:",0
szShow07 db "VERTRES",0
szShow08 db "Width in pixels:",0
szShow09 db "BITSPIXEL",0
szShow10 db "Color bits per pixel:",0
szShow11 db "PLANES",0
szShow12 db "Window border width",0
szShow13 db "NUMBRUSHES",0
szShow14 db "Number of device brushes:",0
szShow15 db "NUMPENS",0
szShow16 db "Number of device pens:",0
szShow17 db "NUMMARKERS",0
szShow18 db "Number of device markers:",0
szShow19 db "NUMFONTS",0
szShow20 db "Number of device fonts:",0
szShow21 db "NUMCOLORS",0
szShow22 db "Number of device colors:",0
szShow23 db "PDEVICESIZE",0
szShow24 db "Size of device structure:",0
szShow25 db "ASPECTX",0
szShow26 db "Relative width of pixel:",0
szShow27 db "ASPECTY",0
szShow28 db "Relative height of pixel:",0
szShow29 db "ASPECTXY",0
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -