📄 见招拆招 windows 程序设计 (四) .txt
字号:
某些情况下,您可能想改变某些设备内容属性,用改变后的属性进行绘图,然后恢复原来的设备内容。要简化这一过程,可以通过如下呼叫来保存设备内容的状态:
invoke SaveDC,hdc
mov idSaved,eax
现在,可以改变一些属性,在想要回到呼叫SaveDC前存在的设备内容时,呼叫:
invoke RestoreDC,hdc, idSaved
您可以在呼叫RestoreDC之前呼叫SaveDC数次。
大多数程序写作者以不同的方式使用SaveDC和RestoreDC。然而,更像汇编语言中的PUSH和POP指令,当您呼叫SaveDC时,不需要保存传回值:
invoke SaveDC,hdc
然后,您可以更改某些属性并再次呼叫SaveDC。要将设备内容恢复到一个已经保存的状态,呼叫:
invoke RestoreDC,hdc, -1
这就将设备内容恢复到最近由SaveDC函数保存的状态中。
画点和线
在前几期,我们谈论过Windows图形设备接口将图形输出设备的设备驱动程序与计算机连在一起的方式。在理论上,只要提供SetPixel和GetPixel函数,就可以使用图形设备驱动程序绘制一切东西了。其余的一切都可以使用GDI模块中实作的更高阶的例程来处理。例如,画线时,只需GDI呼叫SetPixel数次,并适当地调整x和y坐标。
在实际情况中,也的确可以仅使用SetPixel和GetPixel函数进行您需要的任何绘制。您也可以在这些函数的基础上设计出简洁和构造良好的图形编程系统。唯一的问题是启能。如果一个函数通过几次呼叫才能到达SetPixel函数,那么它执行起来会非常慢。如果一个图形系统画线和进行其它复杂的图形操作是在设备驱动程序的层次上,它就会更有效得多,因为设备驱动程序对完成这些操作的程序代码进行了最佳化。此外,一些显示卡包含了图形协处理器,它允许视频硬件自己绘制图形。
设定像素
即使Windows GDI包含了SetPixel和GetPixel函数,但很少使用它们由它们开始来研究图形是非常方便。
SetPixel函数在指定的x和y坐标以特定的颜色设定像素:
SetPixel (hdc, x, y, crColor) ;
如同在任何绘图函数中一样,第一个参数是设备内容的句柄。第二个和第三个参数指明了坐标位置。通常要获得窗口显示区域的设备内容,并且x和y相对于该显示区域的左上角。最后一个参数是COLORREF型态指定了颜色。如果在函数中指定的颜色视频显示器不支持,则函数将像素设定为最接近的纯色并从函数传回该值。
GetPixel函数传回指定坐标处的像素颜色:
invoke GetPixel,hdx,x,y
mov crColor,eax
直线
Windows可以画直线、椭圆线(椭圆圆周上的曲线)和贝塞尔曲线。Windows 98支援的7个画线函数是:
LineTo 画直线
Polyline和PolylineTo 画一系列相连的直线
PolyPolyline 画多组相连的线
Arc 画椭圆线
PolyBezier和PolyBezierTo 画贝塞尔曲线
另外,Windows NT还支持3种画线函数:
ArcTo和AngleArc 画椭圆线
PolyDraw 画一系列相连的线以及贝塞尔曲线
这三个函数Windows 98不支援。
在本章的后面我将介绍一些既画线也填入所画图形的封闭区域的函数,这些函数是:
Rectangle 画矩形
Ellipse 画椭圆
RoundRect 画带圆角的矩形
Pie 画椭圆的一部分,使其看起来像一个扇形
Chord 画椭圆的一部分,以呈弓形。
设备内容的五个属性影响着用这些函数所画线的外观:目前画笔的位置(仅用于LineTo、PolylineTo、PolyBezierTo和ArcTo )、画笔、背景方式、背景色和绘图模式。
画一条直线,必须呼叫两个函数。第一个函数指定了线的开始点,第二个函数指定了线的终点:
MoveToEx (hdc, xBeg, yBeg, NULL) ;
LineTo (hdc, xEnd, yEnd) ;
MoveToEx实际上不会画线,它只是设定了设备内容的「目前位置」属性。然后LineTo函数从目前的位置到它所指定的点画一条直线。目前位置只是用于其它几个GDI函数的开始点。在内定的设备内容中,目前位置最初设定在点(0,0)。如果在呼叫LineTo之前没有设定目前位置,那么它将从显示区域的左上角开始画线。
________________________________________
小历史:
Windows的16位版本中,用来改变目前位置的函数是MoveTo。该函数只调整三个参数-设备内容句柄、x和y坐标。函数通过两个16位数拼成的32位无正负号长整数传回先前的目前位置。然而,在Windows的32位版本中,坐标是32位的数值,而C的32位版本中又没有定义64位的整数数据型态,因此这种改变意味着MoveTo在其传回值中不再指出先前的目前位置。在实际的程序写作中,由MoveTo传回的值几乎从来不用,因此就需要一个新函数,这就是MoveToEx。
________________________________________
MoveToEx的最后一个参数是指向POINT结构的指针。从该函数传回后,POINT结构的x和y字段指出了先前的目前位置。如果您不需要这种信息(通常如此),可以简单地如上面的例子所示的那样将最后一个参数设定为NULL。
________________________________________
警告:
尽管Windows 98中的坐标值看起来是32位的,实际上却只用到了低16位,坐标值实际上被限制在-32,768到32,767之间。在Windows NT/XP中,使用完整的32位值。
________________________________________
如果您需要目前位置,就可以通过以下呼叫获得:
invoke GetCurrentPositionEx,hdc,addr pt
其中,pt是POINT结构的。
下面的程序代码从窗口的左上角开始,在显示区域中画一个网格,线与线之间相隔100个像素,其中hwnd是窗口句柄,hdc是设备内容句柄,而x和y是整数:
GetClientRect (hwnd, &rect) ;
for ( x = 0 ; x < rect.right ; x+= 100)
{
MoveToEx (hdc, x, 0, NULL) ;
LineTo (hdc, x, rect.bottom) ;
}
for (y = 0 ; y < rect.bottom ; y += 100)
{
MoveToEx (hdc, 0, y, NULL) ;
LineTo (hdc, rect.right, y) ;
}
虽然用两个函数来画一条直线显得有些麻烦,但是在希望画一组相连的直线时,目前画笔位置属性又会变得很有用。例如,您可能想定义一个包含5个点(10个值)的数组,来画一个矩形的边界框:
POINT apt[5] = { 100, 100, 200, 100, 200, 200, 100, 200, 100, 100 } ;
注意,最后一个点与第一个点相同。现在,只需要使用MoveToEx移到第一个点,并对后面的点使用LineTo:
MoveToEx (hdc, apt[0].x, apt[0].y, NULL) ;
for ( i = 1 ; i < 5 ; i++)
LineTo (hdc, apt[i].x, apt[i].y) ;
由于LineTo从目前位置画到(但不包括)LineTo函数中给出的点,所以这段程序代码没有在任何坐标处画两次。虽然在显示器上多输出几次不存在问题,但是在绘图机上或者在其它绘图方式(下面马上会讲到)下,视觉效果就不太好了。
当您要将数组中的点连接成线时,使用Polyline函数要简单得多。下面这条叙述画出与上面一段程序代码相同的矩形:
Polyline (hdc, apt, 5) ;
最后一个参数是点的数目。我们还可以使用(sizeof (apt) / sizeof (POINT))来表示这个值。Polyline与一个MoveToEx函数后面加几个LineTo函数的效果相同,但是,Polyline既不使用也不改变目前位置。PolylineTo有些不同,这个函数使用目前位置作为开始点,并将目前位置设定为最后一根线的终点。下面的程序代码画出与上面所示一样的矩形:
MoveToEx (hdc, apt[0].x, apt[0].y, NULL) ;
PolylineTo (hdc, apt + 1, 4) ;
您可以对几条线使用Polyline和PolylineTo,这些函数在绘制复杂曲线最有用了。您使用由几百甚至几千条线组成的极短线段,把它们连在一起就像一条曲线一样。例如,画正弦波就是这样的,程序5-2所示的SINEWAVE程序显示了如何做到这一点。
程序5-2 SINEWAVE
SINEWAVE.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
.DATA
szAppName db "SineWave",0
NUM DD 1000
TWOPI REAL8 6.28318
.DATA?
hInstance dd ?
cxClient dd ?
cyClient dd ?
apt POINT 1000 DUP({?}) ;1000这么大就不能放在WndProc中用LOCAL定义了,
;否则会出现莫名其妙的错误
szBuffer db 100 dup (?)
.CODE
START: ;从这里开始执行
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke WinMain,hInstance,NULL,NULL,SW_SHOWDEFAULT
invoke ExitProcess,0
WinMain proc hInst:DWORD,hPrevInst:DWORD,CmdLine:DWORD,iCmdShow:DWORD
LOCAL wndclass :WNDCLASSEX
LOCAL msg :MSG
local hWnd :HWND
mov wndclass.cbSize,sizeof WNDCLASSEX
mov wndclass.style,CS_HREDRAW or CS_VREDRAW
mov wndclass.lpfnWndProc,offset WndProc
mov wndclass.cbClsExtra,0
mov wndclass.cbWndExtra,0
push hInst
pop wndclass.hInstance
invoke LoadIcon,NULL,IDI_APPLICATION
mov wndclass.hIcon,eax
invoke LoadCursor,NULL,IDC_ARROW
mov wndclass.hCursor,eax
invoke GetStockObject,WHITE_BRUSH
mov wndclass.hbrBackground,EAX
mov wndclass.lpszMenuName,NULL
mov wndclass.lpszClassName,offset szAppName
mov wndclass.hIconSm,0
invoke RegisterClassEx, ADDR wndclass
.if (EAX==0)
invoke MessageBox,NULL,CTXT("This program requires Windows NT!"),addr szAppName,MB_ICONERROR
ret
.endif
invoke CreateWindowEx,
NULL,
ADDR szAppName, ;window class name
CTXT("Sine Wave Using Polyline"), ;window caption
WS_OVERLAPPEDWINDOW, ;window style
CW_USEDEFAULT, ;initial x position
CW_USEDEFAULT, ;initial y position
CW_USEDEFAULT, ;initial x size
CW_USEDEFAULT, ;initial y size
NULL, ;parent window handle
NULL, ;window menu handle
hInstance, ;program instance handle
NULL ;creation parameters
mov hWnd,eax
invoke ShowWindow,hWnd,iCmdShow
invoke UpdateWindow,hWnd
StartLoop:
invoke GetMessage,ADDR msg,NULL,0,0
cmp eax, 0
je ExitLoop
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
jmp StartLoop
ExitLoop:
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -