见招拆招《Windows程序设计》(七)
相关的例子:下载>>> 作者:Zoologist 于2008-4-14上传 

定时器

前面一期的《用MasmPlus学汇编》提到过定时器,因此,这一章对我们来说非常简单。这一期的例子程序非常有意思,仔细研究能学到很多编程上的技巧。

Microsoft Windows定时器是一种输入设备,它周期性地在每经过一个指定的时间间隔后就通知应用程序一次。您的程序将时间间隔告诉Windows,例如「每10秒钟通知我一声」,然后Windows给您的程序发送周期性发生的WM_TIMER消息以表示时间到了。

初看之下,Windows定时器似乎不如键盘和鼠标设备重要,而且对许多应用程序来说确实如此。但是,定时器比您可能认为的要重要得多,它不只用于计时程序,比如出现在工具栏中的Windows时钟和这一章中的两个时钟程序。下面是Windows定时器的其它应用,有些可能并不那么明显:

另一项应用可以保证程序在退出窗口消息处理程序后,能够重新得到控制。在大多数时情况下,程序不能够知道何时下一个消息会到来。

定时器入门

您可以通过呼叫SetTimer函数为您的Windows程序分配一个定时器。SetTimer有一个时间间隔范围为1毫秒到4,294,967,295毫秒(将近50天)的整数型态参数,这个值指示Windows每隔多久时间给您的程序发送WM_TIMER消息。例如,如果间隔为1000毫秒,那么Windows将每秒给程序发送一个WM_TIMER消息。

当您的程序用完定时器时,它呼叫KillTimer函数来停止定时器消息。在处理WM_TIMER消息时,您可以通过呼叫KillTimer函数来编写一个「限用一次」的定时器。KillTimer呼叫清除消息队列中尚未被处理的WM_TIMER消息,从而使程序在呼叫KillTimer之后就不会再接收到WM_TIMER消息。

系统和定时器

Windows定时器是PC硬件和ROM BIOS架构下之定时器一种相对简单的扩充。回到Windows以前的MS-DOS程序写作环境下,应用程序能够通过拦截者称为timer tick的BIOS中断来实作时钟或定时器。一些为MS-DOS编写的程序自己拦截这个硬件中断以实作时钟和定时器。这些中断每54.915毫秒产生一次,或者大约每秒18.2次。这是原始的IBM PC的处理器频率4.772720 MHz被218所除而得出的结果。

Windows应用程序不拦截BIOS中断,相反地,Windows本身处理硬件中断,这样应用程序就不必进行处理。对于目前拥有定时器的每个程序,Windows储存一个每次硬件timer tick减少的计数。当这个计数减到0时,Windows在应用程序消息队列中放置一个WM_TIMER消息,并将计数重置为其最初值。

因为Windows应用程序从正常的消息队列中取得WM_TIMER消息,所以您的程序在进行其它处理时不必担心WM_TIMER消息会意外中断了程序。在这方面,定时器类似于键盘和鼠标。驱动程序处理异步硬件中断事件,Windows把这些事件翻译为规律、结构化和顺序化的消息。

在Windows 98中,定时器与其下的PC定时器一样具有55毫秒的分辨率。在Microsoft Windows NT中,定时器的分辨率为10毫秒。

Windows应用程序不能以高于这些分辨率的频率(在Windows 98下,每秒18.2次,在Windows NT/XP下,每秒大约100次)接收WM_TIMER消息。在SetTimer呼叫中指定的时间间隔总是截尾后tick数的整数倍。例如,1000毫秒的间隔除以54.925毫秒,得到18.207个tick,截尾后是18个tick,它实际上是989毫秒。对每个小于55毫秒的间隔,每个tick都会产生一个WM_TIMER消息。

定时器消息不是异步的

因为定时器使用硬件定时器中断,程序写作者有时会误解,认为他们的程序会异步地被中断来处理WM_TIMER消息。

然而,WM_TIMER消息并不是异步的。WM_TIMER消息放在正常的消息队列之中,和其它消息排列在一起,因此,如果在SetTimer呼叫中指定间隔为1000毫秒,那么不能保证程序每1000毫秒或者989毫秒就会收到一个WM_TIMER消息。如果其它程序的执行事件超过一秒,在此期间内,您的程序将收不到任何WM_TIMER消息。您可以使用本章的程序来展示这一点。事实上,Windows对WM_TIMER消息的处理非常类似于对WM_PAINT消息的处理,这两个消息都是低优先级的,程序只有在消息队列中没有其它消息时才接收它们。

WM_TIMER还在另一方面和WM_PAINT相似:Windows不能持续向消息队列中放入多个WM_TIMER消息,而是将多余的WM_TIMER消息组合成一个消息。因此,应用程序不会一次收到多个这样的消息,尽管可能在短时间内得到两个WM_TIMER消息。应用程序不能确定这种处理方式所导致的WM_TIMER消息「遗漏」的数目。

这样,WM_TIMER消息仅仅在需要更新时才提示程序,程序本身不能经由统计WM_TIMER消息的数目来计时(在本章后面,我们将编写两个每秒更新一次的时钟程序,并可以看到如何做到这一点)。

为了方便起见,下面在讨论时钟时,我将使用「每秒得到一次WM_TIMER消息」这样的叙述,但是请记住,这些消息并非精确的tick中断。

定时器的使用:三种方法

如果您需要在整个程序执行期间都使用定时器,那么您将得从WinMain函数中或者在处理WM_CREATE消息时呼叫SetTimer,并在退出WinMain或响应WM_DESTROY消息时呼叫KillTimer。根据呼叫SetTimer时使用的参数,可以下列三种方法之一使用定时器。

方法一

这是最方便的一种方法,它让Windows把WM_TIMER消息发送到应用程序的正常窗口消息处理程序中,SetTimer呼叫如下所示:

SetTimer (hwnd, 1, uiMsecInterval, NULL) ;
        

第一个参数是其窗口消息处理程序将接收WM_TIMER消息的窗口句柄。第二个参数是定时器ID,它是一个非0数值,在整个例子中假定为1。第三个参数是一个32位无正负号整数,以毫秒为单位指定一个时间间隔,一个60,000的值将使Windows每分钟发送一次WM_TIMER消息。

您可以通过呼叫

KillTimer (hwnd, 1) ;
        

在任何时刻停止WM_TIMER消息(即使正在处理WM_TIMER消息)。此函数的第二个参数是SetTimer呼叫中所用的同一个定时器ID。在终止程序之前,您应该响应WM_DESTROY消息停止任何活动的定时器。

当您的窗口消息处理程序收到一个WM_TIMER消息时,wParam参数等于定时器的ID值(上述情形为1),lParam参数为0。如果需要设定多个定时器,那么对每个定时器都使用不同的定时器ID。wParam的值将随传递到窗口消息处理程序的WM_TIMER消息的不同而不同。为了使程序更具有可读性,您可以使用#define叙述定义不同的定时器ID:

#define TIMER_SEC 1
        
#define TIMER_MIN 2
        

然后您可以使用两个SetTimer呼叫来设定两个定时器:

SetTimer (hwnd, TIMER_SEC, 1000, NULL) ;
        
SetTimer (hwnd, TIMER_MIN, 60000, NULL) ;
        

WM_TIMER的处理如下所示:

caseWM_TIMER:
        
    switch (wParam)
        
    {
        
    case TIMER_SEC:
        
            //每秒一次的处理
        
                  break ;
        
    case TIMER_MIN:
        
            //每分钟一次的处理
        
            break ;
        
    }
        
return 0 ;
        

如果您想将一个已经存在的定时器设定为不同的时间间隔,您可以简单地用不同的时间值再次呼叫SetTimer。在时钟程序里,如果显示秒或不显示秒是可以选择的,您就可以这样做,只需简单地将时间间隔在1000毫秒和60 000毫秒间切换就可以了。

程序8-1显示了一个使用定时器的简单程序,名为BEEPER1,定时器的时间间隔设定为1秒。当它收到WM_TIMER消息时,它将显示区域的颜色由蓝色变为红色或由红色变为蓝色,并通过呼叫MessageBeep函数发出响声。(虽然MessageBeep通常用于MessageBox,但它确实是一个全功能的鸣叫函数。在有声卡的PC机上,一般可以使用不同的MB_ICON参数作为MessageBeep的一个参数以用于MessageBox,来播放使用者在「控制面板」的「声音」程序中选择的不同声音)。

BEEPER1在窗口消息处理程序处理WM_CREATE消息时设定定时器。在处理WM_TIMER消息处理期间,BEEPER1呼叫MessageBeep,翻转bFlipFlop的值并使窗口无效以产生WM_PAINT消息。在处理WM_PAINT消息处理期间,BEEPER1通过呼叫GetClientRect获得窗口大小的RECT结构,并通过呼叫FillRect改变窗口的颜色。

程序8-1 BEEPER1

        
BEEPER1.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

	ID_TIMER		equ	1
.DATA
	szAppName   db "Beeper1",0
	fFlipFlop	BOOL		FALSE
.DATA?
	hInstance	dd ?
.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("Beeper1 Timer Demo"), ;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:
	
	mov eax,msg.wParam
	ret
WinMain endp

WndProc proc hwnd:DWORD,message:DWORD,wParam :DWORD,lParam :DWORD
	LOCAL hBrush			:HBRUSH
	LOCAL hdc 				:HDC
	LOCAL ps  				:PAINTSTRUCT 
	LOCAL rc					:RECT
	
	.if 	  message == WM_CREATE
		invoke	SetTimer,hwnd,ID_TIMER,1000,NULL
	   ret   	
	   
	.elseif message == WM_TIMER
		invoke	MessageBeep,-1
		mov		eax,fFlipFlop
		not		eax
		mov		fFlipFlop,eax
		invoke	InvalidateRect,hwnd,NULL,FALSE
		ret
		
	.elseif message == WM_PAINT
		invoke	BeginPaint,hwnd,addr ps
		mov		hdc,eax
		invoke	GetClientRect,hwnd,addr rc
		
		.if		(fFlipFlop == FALSE)
					mov	eax, 0FF0000h
		.else
					mov	eax, 00000FFh
		.endif
		invoke	CreateSolidBrush,eax
		mov		hBrush,eax
		invoke	FillRect,hdc,addr rc,hBrush
		
		invoke	EndPaint,hwnd,addr ps
		invoke	DeleteObject,hBrush
		ret
	.elseif message == WM_DESTROY
		invoke	KillTimer,hwnd,ID_TIMER
		invoke 	PostQuitMessage,NULL		
		ret	
	.endif	
	
	invoke DefWindowProc,hwnd, message, wParam, lParam
	ret
	
WndProc endp
END START

        

因为BEEPER1每次收到WM_TIMER消息时,都用颜色的变换显示出来,所以您可以通过呼叫BEEPER1来查看WM_TIMER消息的性质,并完成Windows内部的一些其它操作。

例如,首先呼叫控制面板 显示程序,选择效果,确定 拖曳时显示窗口内容复选框没有被选中。现在,试着移动或者缩放BEEPER1窗口,这将导致程序进入「模态消息循环」。Windows通过在内部消息而非您程序的消息循环中拦截所有消息,来禁止对移动或者缩放操作的任何干扰。通过此循环到达程序窗口的大多数消息都被丢弃,这就是BEEPER1停止蜂鸣的原因。当完成了移动与缩放之后,您将会注意到BEEPER1不能取得它所丢弃的所有WM_TIMER消息,尽管前两个消息的间隔可能少于1秒。

在「拖曳时显示窗口内容」复选框被选中时,Windows中,的模态消息循环会试图给您的窗口消息处理程序传递一些丢失的消息。这样做有时工作得很好,有时却不行。

p81.PNG (63821 字节)

方法二

设定定时器的第一种方法是把WM_TIMER消息发送到通常的窗口消息处理程序,而第二种方法是让Windows直接将定时器消息发送给您程序的另一个函数。

接收这些定时器消息的函数被称为「callback」函数,这是一个在您的程序之中但是由Windows呼叫的函数。您先告诉Windows此函数的地址,然后Windows呼叫此函数。这看起来也很熟悉,因为程序的窗口消息处理程序实际上也是一种callback函数。当注册窗口类别时,要将函数的地址告诉Windows,当发送消息给程序时,Windows会呼叫此函数。

SetTimer并非是唯一使用callback函数的Windows函数。CreateDialog和DialogBox函数使用callback函数处理对话框中的消息;有几个Windows函数(EnumChildWindow、EnumFonts、EnumObjects、EnumProps和EnumWindow)把列举信息传递给callback函数;还有几个不那么常用的函数(GrayString、LineDDA和SetWindowHookEx)也要求callback函数。

像窗口消息处理程序一样,callback函数也必须定义为CALLBACK,因为它是由Windows从程序的程序代码段呼叫的。callback函数的参数和callback函数的传回值取决于callback函数的目的。跟定时器有关的callback函数中,输入参数与窗口消息处理程序的输入参数一样。定时器callback函数不向Windows传回值。

我们把以下的callback函数称为TimerProc(您能够选择与其它一些用语不会发生冲突的任何名称),它只处理WM_TIMER消息:

VOID CALLBACK TimerProc (  HWND hwnd, UINT message, UINT iTimerID, DWORD dwTime)
        
{
        
           处理WM_TIMER消息
        
}
        

TimerProc的参数hwnd是在呼叫SetTimer时指定的窗口句柄。Windows只把WM_TIMER消息送给TimerProc,因此消息参数总是等于WM_TIMER。iTimerID值是定时器ID,dwTimer值是与从GetTickCount函数的传回值相容的值。这是自Windows启动后所经过的毫秒数。

在BEEPER1中已经看到过,用第一种方法设定定时器时要求下面格式的SetTimer呼叫:

SetTimer (hwnd, iTimerID, iMsecInterval, NULL) ;
        

您使用callback函数处理WM_TIMER消息时,SetTimer的第四个参数由callback函数的地址取代,如下所示:

SetTimer (hwnd, iTimerID, iMsecInterval, TimerProc) ;
        

我们来看看一些范例程序代码,这样您就会了解这些东西是如何组合在一起的。在功能上,除了Windows发送一个定时器消息给TimerProc而非WndProc之外,程序8-2所示的BEEPER2程序与BEEPER1是相同的。注意,TimerProc和WndProc一起被声明在程序的开始处。

程序8-2  BEEPER2
        
BEEPER2.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

LOWORD	MACRO bigword;; Retrieves the low word from double word argument
	mov	eax,bigword
	and	eax,0FFFFh	;; Get low word 
	ENDM

HIWORD	MACRO bigword  ;; Retrieves the high word from double word 
	mov	ebx,bigword
	shr	ebx,16;; Shift 16 for high word to set to high word
	ENDM

RGB	MACRO red, green, blue	;; Get composite number from red green and blue bytes 
	mov	al,blue	;; ,,,blue	
	shl	eax,8	;; ,,blue,

	add	al,green;; ,,blue,green
	shl	eax,8	;; ,blue,green,
	add	al,red	;; ,blue,green,red
	and	eax,0FFFFFFh;; Mask out top byte to complete COLORREF dword 
	ENDM
	
	WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

	ID_TIMER		equ	1
	
.DATA
	szAppName   db "DigClock",0

fSevenSegment BYTE 1, 1, 	1, 	0, 	1, 	1, 	1,\
						 0, 0, 	1, 	0, 	0, 	1, 	0,\
                   1, 0, 	1, 	1, 	1, 	0, 	1,\	    
                   1, 0, 	1, 	1, 	0, 	1, 	1,\
                   0, 1, 	1, 	1, 	0, 	1, 	0,\	
                   1, 1, 	0, 	1, 	0, 	1, 	1
dummy0		  BYTE 1, 1, 	0, 	1, 	1, 	1, 	1,\
                   1, 0, 	1, 	0, 	0, 	1, 	0,\	    
                   1, 1, 	1, 	1, 	1, 	1, 	1,\	    
                   1, 1, 	1, 	1, 	0, 	1, 	1 	
                   
ptSegment	  POINT   {7,   6},  {11,	2},  {31,  2}, {35,  6},  {31, 10}, {11, 10},\
	                   { 6,  7},  {10, 11}, {10, 31}, {6,   35}, {2,  31}, {2,  11},\
	                   { 36, 7},  {40, 11}, {40, 31}, {36,  35}, {32, 31}, {32, 11},\
	                   { 7 , 36}, {11, 32}, {31, 32}, {35,  36}, {31, 40}, {11, 40},\
	                   { 6 , 37}, {10, 41}, {10, 61}, {6,   65}, {2,  61}, {2,  41},\
	                   { 36, 37}, {40, 41}, {40, 61}, {36,  65}, {32, 61}, {32, 41}
dummy1		  POINT   { 7 , 66}, {11, 62}, {31, 62}, {35,  66}, {31, 70}, {11, 70} 			

ptColon		  POINT   {2,21},{6,17},{10,21},{6,25},{2,51},{6,47},{10,51},{6,55 }
.DATA?
	hInstance	dd ?
	cxClient		dd ?
	cyClient		dd ?	

	f24Hour		BOOL ?
	fSuppress 	BOOL ?
	hBrushRed	HBRUSH	?

	
.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("Digital Clock"), ;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:
	
	mov eax,msg.wParam
	ret
WinMain endp

DisplayDigit	proc hdc:HDC,iNumber:DWORD	;显示一个数字
	LOCAL		iSeg:DWORD
	mov		eax,iNumber	
	shl		eax,3
	sub		eax,iNumber ;iNumber*8-iNumber
	lea		edi,fSevenSegment
	add		edi,eax
	
	lea		esi,ptSegment;取数字对应的数码管是否显示
	mov		iSeg,0	
	.while	(iSeg<7)
		mov		al,[edi]
		.if		(al!=0)	
					invoke	Polygon,hdc,esi,6	
		.endif	
					inc	iSeg
					add	esi,48
					add	edi,1
	.endw
	ret
DisplayDigit	Endp

DisplayTwoDigits	proc	hdc:HDC,iNumber:DWORD,fSuppressDT:BOOL	;显示2位数字
	xor	edx,edx
	mov	eax,iNumber
	mov	ecx,10
	div	ecx
	push	edx
	.if	(fSuppressDT==0)||(edx!=0)
			invoke	DisplayDigit,hdc,eax
	.endif	
	invoke	OffsetWindowOrgEx,hdc,-42,0,NULL
	pop	edx
	invoke	DisplayDigit,hdc,edx
	invoke	OffsetWindowOrgEx,hdc,-42,0,NULL
	ret
DisplayTwoDigits	Endp

DisplayColon	proc	hdc:HDC	;显示冒号
	lea		esi,ptColon
	invoke	Polygon,hdc,esi,4
	add		esi,32
	invoke	Polygon,hdc,esi,4
	invoke	OffsetWindowOrgEx,hdc,-12,0,NULL
	ret
DisplayColon	Endp

DisplayTime	proc	hdc:HDC,f24HourDispT:BOOL,fSuppressDispT:BOOL
	LOCAL		stCurrent:SYSTEMTIME
	invoke	GetLocalTime,addr stCurrent
	.if		(f24HourDispT)
				invoke	DisplayTwoDigits,hdc,stCurrent.wHour,fSuppressDispT
	.else
				xor		edx,edx
				xor		eax,eax
				mov		ax,stCurrent.wHour
				mov		ecx,12
				div		ecx
				.if		(edx==0)
							mov	eax,12
				.else
							xor	eax,eax
							mov	ax,stCurrent.wHour
				.endif	
				invoke	DisplayTwoDigits,hdc,eax,fSuppressDispT
	.endif	
	
	invoke	DisplayColon,hdc
	invoke	DisplayTwoDigits,hdc,stCurrent.wMinute,FALSE
	invoke	DisplayColon,hdc
	invoke	DisplayTwoDigits,hdc,stCurrent.wSecond,FALSE
	ret
DisplayTime Endp

WndProc proc uses ebx esi edi ,hwnd:DWORD,message:DWORD,wParam :DWORD,lParam :DWORD
	LOCAL		hdc:HDC
	LOCAL		ps	:PAINTSTRUCT
	LOCAL		szBuffer[2]		:TCHAR	
	
	.if 	  message == WM_CREATE
				RGB	255,0,0
				invoke	CreateSolidBrush,eax
				mov		hBrushRed,eax
				invoke	SetTimer,hwnd,ID_TIMER,500,NULL
				jmp	@f
	.elseif	message == WM_SETTINGCHANGE
	@@:
				invoke	GetLocaleInfo,LOCALE_USER_DEFAULT,LOCALE_ITIME,addr szBuffer,2
				.if	(szBuffer[0]=='1')
						mov		f24Hour,TRUE
				.else
						mov		f24Hour,FALSE
				.endif	
				invoke	GetLocaleInfo,LOCALE_USER_DEFAULT,LOCALE_ITLZERO,addr szBuffer,2
				.if	(szBuffer[0]=='0')
						mov		f24Hour,TRUE
				.else
						mov		f24Hour,FALSE
				.endif			
				invoke	InvalidateRect,hwnd,NULL,TRUE
				xor	eax,eax
				ret
	.elseif  message == WM_SIZE   
			  LOWORD	lParam		
			  mov	cxClient,eax
			  HIWORD	lParam		
			  mov	cyClient,ebx
			  xor	eax,eax
			  ret
	.elseif	message == WM_TIMER
			  invoke	InvalidateRect,hwnd,NULL,TRUE
			  xor	eax,eax
			  ret
	.elseif message == WM_PAINT
	        invoke  BeginPaint, hwnd, ADDR ps
	        mov	hdc,eax	 	; Get handle to device context
	        
	        invoke	SetMapMode,hdc,MM_ISOTROPIC
	        invoke	SetWindowExtEx,hdc,276,72,NULL
	        invoke	SetViewportExtEx,hdc,cxClient,cyClient,NULL
	        
	        invoke	SetWindowOrgEx,hdc,138,36,NULL
	        push	NULL
	        mov		eax,cyClient
	        shr		eax,1
	        push	eax
	        mov		eax,cxClient
	        shr		eax,1
	        push	eax
	        push	hdc
	        call	SetViewportOrgEx
	        invoke	GetStockObject,NULL_PEN
	        invoke	SelectObject,hdc,eax
	        invoke	SelectObject,hdc,hBrushRed
	        
	        invoke	DisplayTime,hdc,f24Hour,fSuppress
	        invoke  EndPaint, hwnd, ADDR ps
	        xor	eax,eax
			  ret
	.elseif message == WM_DESTROY
			  invoke	KillTimer,hwnd,ID_TIMER
			  invoke	DeleteObject,hBrushRed
			  invoke 	PostQuitMessage,NULL		
			  ret	
	.endif	
	
	invoke DefWindowProc,hwnd, message, wParam, lParam
	ret
WndProc endp
END START
        

DIGCLOCK窗口如图8-1所示。


 

dig.PNG (15512 字节)

图8-1 DIGCLOCK的屏幕显示

虽然,在图8-1中您看不到时钟的数字是红色的。DIGCLOCK的窗口消息处理程序在处理WM_CREATE消息处理期间建立了一个红色的画刷并在处理WM_DESTROY消息处理期间清除它。WM_CREATE消息也为DIGCLOCK设定了一个一秒的定时器,该定时器在处理WM_DESTROY消息处理期间被终止(待会将讨论对GetLocaleInfo的呼叫)。

在收到WM_TIMER消息后,DIGCLOCK的窗口过程调用InvalidateRect简单地使整个窗口无效。这不是最佳方法,因为每秒整个窗口都要被擦除和重画,有时会引起显示器的闪烁。依据目前的时间使窗口需要更新的部分无效是最好的解决方法。然而,在逻辑上这样做的确很复杂。

在处理WM_TIMER消息处理期间使窗口无效会迫使所有程序的真正活动转入WM_PAINT。DIGCLOCK在WM_PAINT消息一开始将映像方式设定为MM_ISOTROPIC。这样,DIGCLOCK将使用水平方向和垂直方向相等的轴。这些轴(由SetWindowExtEx呼叫设定)是水平276个单位,垂直72个单位。当然,这些轴定得有点太随意了,但它们是按照时钟数字元的大小和间距安排的。

DIGCLOCK将窗口原点设定为(138,36),这是窗口范围的中心;将视埠原点设定为(cxClient / 2,cyClient / 2)。这意味着时钟的显示位于DIGCLOCK显示区域的中心,但是该DIGCLOCK也可以使用在显示屏左上角的原点(0, 0)的轴。

然后WM_PAINT将目前画刷设定为之前建立的红画刷,将目前画笔设定为NULL_PEN,并呼叫DIGCLOCK中的函数DisplayTime。

思考:

    仔细阅读程序,

    1.如何修改程序使得能够显示24小时制时间或者12小时制时间?

    2.如何修改程序,能够完成自动省略0。比如,12:08,显示为12: 8?

    3.如果有兴趣的话,不妨修改一个属于你自己的十六进制的电子表。比如: 12:10,显示出来就是C:0A。

取得目前时间

DisplayTime函数开始呼叫Windows函数GetLocalTime,它带有一个的SYSTEMTIME结构的参数,在WINDOWS.INC中定义为:

SYSTEMTIME STRUCT
  wYear             WORD      ?
  wMonth            WORD      ?
  wDayOfWeek        WORD      ?
  wDay              WORD      ?
  wHour             WORD      ?
  wMinute           WORD      ?
  wSecond           WORD      ?
  wMilliseconds     WORD      ?
SYSTEMTIME ENDS
        

很明显,SYSTEMTIME结构包含日期和时间。月份由1开始递增(也就是说,一月是1),星期由0开始递增(星期天是0)。wDay成员是本月目前的日子,也是由1开始递增的。

SYSTEMTIME主要用于GetLocalTime和GetSystemTime函数。GetSystemTime函数传回目前的世界时间(Coordinated Universal Time,UTC),大概与英国格林威治时间相同。GetLocalTime函数传回当地时间,依据计算机所在的时区。这些值的精确度完全决定于使用者所调整的时间精确度以及是否指定了正确的时区。可以双击工作栏的时间显示来检查计算机上的时区设定。

Windows还有SetLocalTime和SetSystemTime函数,可以在MSDN上查找到详细介绍。

显示数字和冒号

如果DIGCLOCK使用一种仿真7段显示的字体将会简单一些。否则,它就得使用Polygon函数做所有的工作。

DIGCLOCK中的DisplayDigit函数定义了两个数组。fSevenSegment数组有7个BOOL值,用于从0到9的每个十进制数。这些值指出了哪一段需要显示(为1),哪一段不需要显示(为0)。在这个数组中,7段由上到下、由左到右排序。7段中的每个段都是一个6边的多边形。ptSegment数组是一个POINT结构的数组,指出了7个段中每个点的图形坐标。每个数字由下列程序代码画出:

	mov		eax,iNumber	
	shl		eax,3
	sub		eax,iNumber ;iNumber*8-iNumber
	lea		edi,fSevenSegment
	add		edi,eax
	
	lea		esi,ptSegment;取数字对应的数码管是否显示
	mov		iSeg,0	
	.while	(iSeg<7)
		mov		al,[edi]
		.if		(al!=0)	
					invoke	Polygon,hdc,esi,6	
		.endif	
					inc	iSeg
					add	esi,48	;需要注意的是汇编语言处理结构体的时候需要自己计算
					add	edi,1
	.endw
        

类似地(但更简单),DisplayColon函数在小时与分钟、分钟与秒之间画一个冒号。数字是42个单位宽,冒号是12个单位宽,因此6个数字与2个冒号,总宽度是276个单位,SetWindowExtEx呼叫中使用了这个大小。

回到DisplayTime函数,原点位于最左数字位置的左上角。DisplayTime呼叫DisplayTwoDigits,DisplayTwoDigits呼叫DisplayDigit两次,并且在每次呼叫OffsetWindowOrgEx后,将窗口原点向右移动42个单位。类似地,DisplayColon函数在画完冒号后,将窗口原点向右移动12个单位。用这种方法,不管对象出现在窗口内的哪个地方,函数对数字和冒号都使用同样的坐标。

这个程序的其它技巧是以12小时或24小时的格式显示时间以及当最左边的小时数字为0时不显示它。

建立模拟时钟

模拟时为了正确的显示时钟,您需要知道一些三角函数。CLOCK如程序8-4所示。

程序8-4 CLOCK

        
CLOCK.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
LOWORD	MACRO bigword;; Retrieves the low word from double word argument

	mov	eax,bigword
	and	eax,0FFFFh	;; Get low word 
	ENDM

HIWORD	MACRO bigword  ;; Retrieves the high word from double word 

	mov	ebx,bigword
	shr	ebx,16;; Shift 16 for high word to set to high word
				
	ENDM

RGB	MACRO red, green, blue	;; Get composite number from red green and blue bytes 

	mov	al,blue	;; ,,,blue	
	shl	eax,8	;; ,,blue,

	add	al,green;; ,,blue,green
	shl	eax,8	;; ,blue,green,
	add	al,red	;; ,blue,green,red
	and	eax,0FFFFFFh;; Mask out top byte to complete COLORREF dword 

	ENDM
	
	WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
	WndProc PROTO :DWORD,:DWORD,:DWORD,:DWORD

	ID_TIMER		equ	1
	
.DATA
	szAppName   db "Clock",0
theta		real4	?	; Required rotation in degrees
math_X_Coord	real4	?	; Math space coords
math_Y_Coord	real4	?	; 
New_X1		real4	?
New_Y1		real4	?	
pt		POINT	{0, -150},{100,0},{0,600},{-100,0},{0,-150}, \ 
			{0, -200},{ 50,0},{0,800},{ -50,0},{0,-200}, \
			{0,    0},{  0,0},{0,  0},{   0,0},{0, 800}	
.DATA?
	hInstance	dd ?
	stPrevious	SYSTEMTIME <>	; Previous time data
	stCurrent	SYSTEMTIME <>	; Current 
	cxClient		dd ?
	cyClient		dd ?	
	fChange		BOOL ?




	
.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
	LOCAL cxWindow,cyWindow:DWORD
	
	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("Analog Clock"), ;window caption
					WS_OVERLAPPED or WS_CAPTION or WS_SYSMENU or WS_BORDER,	;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:
	
	mov eax,msg.wParam
	ret
WinMain endp

SetIsotropic proc hdc:HDC,cxC:DWORD,cyC:DWORD 
	invoke SetMapMode, hdc, MM_ISOTROPIC	; Select isotropic mapping 
;  Set window extents to 1000 and the viewport extents to 1/2 width of client area,
;  and the negative of 1/2 height of client area
	invoke SetWindowExtEx, hdc, 1000, 1000, NULL
	push		NULL
	mov	eax,cyC
	shr	eax,1
	neg	eax	; Get -cyClient/2
	push	eax
	mov	eax,cxC
	shr	eax,1
	push	eax	
	push	hdc
	call	SetViewportExtEx
	
	push	NULL
	mov	eax,cyC
	shr	eax,1
	push	eax
	mov	eax,cxC
	shr	eax,1
	push	eax	
	push	hdc
	call SetViewportOrgEx
	ret
SetIsotropic endp

DrawClock proc hdc:HDC

LOCAL   iAngle:DWORD, pt4[3]:POINT

	mov	iAngle,0

	.WHILE	iAngle < 360

	mov	pt4[0].x,0
	mov	pt4[0].y,900

	fild	pt4[0*8].x
	fstp	math_X_Coord
	
	fild	pt4[0*8].y
	fstp	math_Y_Coord

	fild	iAngle
	fstp	theta

	mov	esi,offset theta	; Point at parameter block

	call	rotate	; Rotate the 5-minute or minute marks

	fld	New_X1	; Get new X coord
	fistp	pt4[0*8].x; Store in structure

	fld	New_Y1	; Ditto for Y coord
	fistp	pt4[0*8].y; 

	mov	eax,iAngle
	mov	ecx,5
	mov	edx,0
	div	ecx	; Get the remainder in dx

	.IF edx
	    mov pt4[2*8].x,33; if edx is true its a minute mark (small circle)
	    mov pt4[2*8].y,33
	.ELSEIF
	    mov pt4[2*8].x,100; if its false its a 5-minute mark (larger circle)
	    mov pt4[2*8].y,100	
	.ENDIF

	mov	eax,pt4[2*8].x
	shr	eax,1	; (pt[2].x)/2
	sub	pt4[0].x,eax
	
	mov	eax,pt4[2*8].y
	shr	eax,1	; (pt[2].y)/2
	sub	pt4[0].y,eax

	mov	eax,pt4[0].x
	add	eax,pt4[2*8].x
	mov	pt4[1*8].x,eax

	mov	eax,pt4[0].y
	add	eax,pt4[2*8].y
	mov	pt4[1*8].y,eax

	invoke	GetStockObject, BLACK_BRUSH
	invoke  SelectObject, hdc, eax

	invoke Ellipse, hdc, pt4[0].x, pt4[0].y, pt4[1*8].x, pt4[1*8].y	

	add	iAngle,6

	.ENDW	

	ret
DrawClock endp

DrawHands proc	hdc:HDC, pst:DWORD, Change:BOOL	; pst points @ current or previous TIME data

LOCAL iAngle[3]:DWORD, ptTemp[15]:POINT	; Provide space for copy of polyline points 

;	This routine is entered twice, once to erase the old hand position and then
;	a second time it uses the new time data to update the hand positions.   
 
;	Convert time data (hour, min and sec) into angles for the three hands
 		
	mov	ebx,pst	; Get address of current or previous SYSTEMTIME

	mov	ax,[ebx+8]; Get hours
	mov	edx,0
	mov	cx,30	; Scaling factor for 360 degrees
	mul	cx
	mov	cx,360
	div	cx	; mod 360 divide; get hours-remainder in edx
	mov	cx,[ebx+10]; Get minute value
	shr	cx,1	;
	add	dx,cx	; Add on angle for fractional part of the hour
	mov	iAngle[0],edx; Now have hour-hand angle

	mov	ax,[ebx+10]; Get minutes again
	mov	edx,0
	mov	cx,6	; Scale factor for minute-hand angle
	mul	cx
	mov	iAngle[4],eax; Got minute-hand angle

	mov	ax,[ebx+12]; Get seconds
	mov	edx,0
	mov	cx,6	; Scale factor for second-hand angle
	mul	cx
	mov	iAngle[8],eax; Got second-hand angle	

;	Copy polyline data to temp storage

	mov	esi,offset pt; es and ds point to same segment 
	lea	edi,ptTemp
	mov	ecx,sizeof pt
	cld
	rep	movsb	; Copy polyline points to ptTemp

	.IF Change
	mov     esi,0	; If hour or minute hands have changed position
	mov	ebx,0
	.ELSE
	mov     esi,2*4	; If second hand only has changed 
	mov	ebx,2*40 
	.ENDIF

.WHILE  ebx < 3*40

	mov	edi,0	; Index to point

    .WHILE      edi < 40; Loop for all points in the structure

	push	edi
	add	edi,ebx	; Add offset for the appropriate hand			

	fild	iAngle[esi]; Load data into the rotate parameter block				
	fstp	theta	;
	fild	ptTemp[edi].x;
	fstp	math_X_Coord;
	fild	ptTemp[edi].y;
	fstp	math_Y_Coord;

	push	esi
	mov	esi,offset theta	; Point at parameter block

	call   rotate	; Rotate the point
	pop	esi

	fld	New_X1	; Get new X coord
	fistp	ptTemp[edi].x; Store in hands structure

	fld	New_Y1	; Ditto for Y coord
	fistp	ptTemp[edi].y;
 
	pop	edi
	add	edi,8

    .ENDW

	invoke Polyline, hdc,ADDR ptTemp[ebx],5 ; Draw the hand
 
	add	ebx,40; Inc to next hand
	add	esi,4; Inc index for angle of next hand

.ENDW
	ret
DrawHands endp


WndProc proc uses ebx esi edi ,hwnd:DWORD,message:DWORD,wParam :DWORD,lParam :DWORD
	LOCAL		hdc:HDC
	LOCAL		ps	:PAINTSTRUCT
	
		.if 	  message == WM_CREATE
			finit
			invoke	SetTimer,hwnd,ID_TIMER,500,NULL
			invoke   GetLocalTime, ADDR stCurrent
	;invoke   GetLocalTime, ADDR stPrevious;stPrevious = st ;
			xor	eax,eax
		   ret   
		.elseif  message == WM_SIZE   
			  LOWORD	lParam; Get low word from lParam in eax
			  mov	cxClient,eax
		
			  HIWORD	lParam; Get high "	"	"     ebx
			  mov	cyClient,ebx
			  xor	eax,eax
			  ret
		.elseif	message == WM_TIMER

	  invoke GetLocalTime, ADDR stCurrent

	  mov	cx,stCurrent.wHour
	  mov	dx,stCurrent.wMinute

	  .IF     cx != stPrevious.wHour
	     mov  fChange, TRUE
	  .ELSEIF dx != stPrevious.wMinute
	     mov  fChange, TRUE
	  .ELSE
	     mov  fChange, FALSE
	  .ENDIF

	  invoke GetDC, hwnd
	  mov	hdc,eax

	  invoke SetIsotropic, hdc,cxClient,cyClient

	  invoke GetStockObject, WHITE_PEN; Erase hands
	  invoke SelectObject, hdc, eax 
	  invoke DrawHands, hdc, ADDR stPrevious, fChange

	  invoke GetStockObject, BLACK_PEN; Draw hands at new position
	  invoke SelectObject, hdc, eax 
	  invoke DrawHands, hdc, ADDR stCurrent, TRUE

	  invoke ReleaseDC, hwnd, hdc

	  mov	ax,stCurrent.wHour; Save the current time
	  mov	stPrevious.wHour,ax;
	  mov	ax,stCurrent.wMinute;
	  mov	stPrevious.wMinute,ax;
	  mov	ax,stCurrent.wSecond;
	  mov	stPrevious.wSecond,ax;
	  xor	eax,eax
		ret
	.elseif message == WM_PAINT
        invoke  BeginPaint, hwnd, ADDR ps

        mov	hdc,eax	 	; Get handle to device context
 
	  	  invoke  SetIsotropic, hdc,cxClient,cyClient

	  	  invoke  DrawClock, hdc; Draw the dial
        invoke  EndPaint, hwnd, ADDR ps
        xor	eax,eax
		ret
	.elseif message == WM_DESTROY
		invoke	KillTimer,hwnd,ID_TIMER
		invoke 	PostQuitMessage,NULL		
		ret	
	.endif	
	
	invoke DefWindowProc,hwnd, message, wParam, lParam
	ret
WndProc endp

;---------------------------------------------------------------;
; This routine finds new values for cartesian coordinates X & Y	;
; when the point is rotated by theta degrees	;
;;
; Enter: DS:SI points at a data block in the calling routine	;
;;
;	  [si] (real4) contains the rotation angle in degrees	;
;	[si+4] (real4) contains the math X coordinate;
;	[si+8] (real4) contains the math Y coordinate;     	   
;	[si+12](real4) will contain the new X coordinate	;
;	[si+16](real4) will contain the new Y coordinate	;
;;
; Note: I keep the orignal coords (as opposed to updating	;
;	so that a program can reuse the same values if required	;
;; 		  		;
;	X1 =  X * cos(theta)  +  Y * sin(theta) ;
;	Y1 = -X * sin(theta)  +  Y * cos(theta)	;
; ;
; Return:  New coords are updated in callers parameter block.	;  
;---------------------------------------------------------------;

.DATA
deg2rad		real4	1.7453292E-2	; 2 * pi / 360 (conversion factor for radians)

.CODE

angle	equ	dword ptr [esi]
X_Coord	equ 	dword ptr [esi+4]
Y_Coord	equ	dword ptr [esi+8]
New_X	equ	dword ptr [esi+12]
New_Y	equ	dword ptr [esi+16]  

rotate	PROC	 

	fld	angle	; Put the rotation in degrees into st(0)

	fmul	deg2rad	; st=radians
	fsincos; st=cos,   st(1)=sin

	fld	st	; st=cos,   st(1)=cos, st(2)=sin

	fmul	X_Coord	; st=X*cos, st(1)=cos, st(2)=sin

	fxch; st=cos,   st(1)=X*cos, st(2)=sin
	fmul	Y_Coord	; st=Y*cos, st(1)=X*cos, st(2)=sin

	fxch	st(2)	; st=sin,   st(1)=X*cos, st(2)=Y*cos

	fld	st	; st=sin, st1=sin, st2=X*cos, st3=Y*cos	;now X, X, Y*sin, Y*cos, cos, sin	

	fmul	X_Coord	; X*sin, sin,   X*cos, Y*cos

	fxch; sin,   X*sin, X*cos, Y*cos
	fmul	Y_Coord	; Y*sin, X*sin, X*cos, Y*cos
	fadd	st,st(2); Y*sin + X*cos, X*sin, X*cos, Y*cos

	fstp	New_X	; X*sin, X*cos, Y*cos ; Stored new X coord
 
	fxch	st(1)	; X*cos, X*sin, Y*cos
	fstp	st	; X*sin, Y*cos  
	fsub; -X*sin +Y*cos

	fstp	New_Y	; Stored new Y coord
	
	RET

rotate	ENDP
END START

        

CLOCK屏幕显示如图8-2。


 

clock.PNG (15234 字节)

图8-2 CLOCK的屏幕显示

等方向性(isotropic)映像对于这样的应用来说是理想的,CLOCK.C中的SetIsotropic函数负责设定此模式。在呼叫SetMapMode之后,SetIsotropic将窗口范围设定为1000,并将视端口范围设定为显示区域的一半宽度和显示区域的负的一半高度。视端口原点被设定为显示区域的中心。我在第五章中讨论过,这将建立一个笛卡儿坐标系,其点(0,0)位于显示区域的中心,在所有方向上的范围都是1000。

RotatePoint函数是用到三角函数的地方,此函式的三个参数分别是一个或者多个点的数组、数组中点的个数以及以度为单位的旋转角度。函式以原点为中心按顺时针方向(这对一个时钟正合适)旋转这些点。例如,如果传给函式的点是(0,100)-即12:00的位置-而角度为90度,那么该点将被变换为(100,0)-即3:00。它使用下列公式来做到这一点:

x' = x * cos (a) + y * sin (a)
        
y' = y * cos (a) - x * sin (a)
        

RotatePoint函数在绘制时钟表面的点和表针时都是有用的,我们将马上看到这一点。

DrawClock函数绘制60个时钟表面的点,从顶部(12:00)开始,其中每个点离原点900单位,因此第一个点位于(0,900),此后的每个点按顺时针依次增加6度。这些点中的l2个直径为100个单位;其余的为33个单位。使用Ellipse函数来画点。

DrawHands函数绘制时钟的时针、分针和秒针。定义表针轮廓(当它们垂直向上时的形状)的坐标存放在一个POINT结构的数组中。根据时间,这些坐标使用RotatePoint函数进行旋转,并用Windows的Polyline函数进行显示。注意时针和分针只有当传递给DrawHands的bChange参数为TRUE时才被显示。当程序更新时钟的表针时,大多数情况下时针和分针不需要重画。

现在让我们将注意力转到窗口消息处理程序。在WM_CREATE消息处理期间,窗口消息处理程序取得目前时间并将它存放在名为dtPrevious的变量中,这个变量将在以后被用于确定时针或者分针从上次更新以来是否改变过。

第一次绘制时钟是在第一个WM_PAINT消息处理期间,这只不过是依次呼叫SetIsotropic、DrawClock和DrawHands,后者的bChange参数被设定为TRUE。

在WM_TIMER消息处理期间,WndProc首先取得新的时间并确定是否需要重新绘制时针和分针。如果需要,则使用一个白色画笔和上一次时间绘制所有的表针,从而有效地擦除它们。否则,只对秒针使用白色画笔进行擦除,然后,再使用一个黑色画笔绘制所有的表针。

以定时器进行状态报告

本章的最后一个程序是我在第五章提到过的。它是一个使用GetPixel函数的好例子。

WHATCLR (见程序8-5)显示了鼠标光标下目前图素的RGB颜色。

程序8-5 WHATCLR

        
WHATCLR.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
	FindWindowSize PROTO :DWORD,:DWORD

	ID_TIMER		equ	1
	
.DATA
	szAppName   db "WhatClr",0
	fFlipFlop	BOOL		FALSE
.DATA?
	hInstance	dd ?
	cr				COLORREF	?
	crLast		COLORREF	?
	hdcScreen	HDC	?
.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
	LOCAL cxWindow,cyWindow:DWORD
	
	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
   
   lea		eax,cyWindow
   push		eax
   lea		eax,cxWindow
   push		eax
   call		FindWindowSize
   
	invoke CreateWindowEx,
					NULL,
					ADDR szAppName, ;window class name
					CTXT("What Color"), ;window caption
					WS_OVERLAPPED or WS_CAPTION or WS_SYSMENU or WS_BORDER,	;window style
					CW_USEDEFAULT,;initial x position
					CW_USEDEFAULT,;initial y position
					cxWindow, 	;initial x size
					cyWindow,	;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:
	
	mov eax,msg.wParam
	ret
WinMain endp

FindWindowSize proc pcxWindow:DWORD,pcyWindow:DWORD
     	LOCAL   			hdcFindWin:HDC
     	LOCAL				tm:TEXTMETRIC

		invoke	CreateIC,CTEXT("DISPLAY"),NULL,NULL,NULL
		mov		hdcFindWin,eax
		invoke   GetTextMetrics,hdcFindWin,addr tm
     	invoke	DeleteDC,hdcFindWin
;* pcxWindow = 2 * 	GetSystemMetrics (SM_CXBORDER)+12 * tm.tmAveCharWidth ;     
      invoke	GetSystemMetrics,SM_CXBORDER
      shl		eax,1
      mov		ebx,eax
      mov		eax,tm.tmAveCharWidth
      mov		ecx,12
      mul		ecx
      add		eax,ebx
      mov		edi,pcxWindow
      mov		[edi],eax
      
; * pcyWindow = 2 * 	GetSystemMetrics (SM_CYBORDER)+GetSystemMetrics (SM_CYCAPTION) + 
;                         	    2 * tm.tmHeight ;
      invoke	GetSystemMetrics,SM_CYBORDER
      shl		eax,1
      mov		ebx,eax
      invoke	GetSystemMetrics,SM_CYCAPTION
      add		ebx,eax
      mov		eax,tm.tmHeight
      shl		eax,1
      add		ebx,eax
      mov		edi,pcyWindow
      mov		[edi],ebx
		ret
FindWindowSize Endp

WndProc proc hwnd:DWORD,message:DWORD,wParam :DWORD,lParam :DWORD
	LOCAL		hdc:HDC
	LOCAL		ps	:PAINTSTRUCT
	LOCAL		pt	:POINT
	LOCAL		rc	:RECT
	LOCAL		szBuffer[16]:TCHAR
	
		.if 	  message == WM_CREATE
			invoke	CreateDC,CTEXT("DISPLAY"),NULL,NULL,NULL
			mov		hdcScreen,eax
			invoke	SetTimer,hwnd,ID_TIMER,100,NULL
		   ret   	
		.elseif	message == WM_TIMER

		invoke	GetCursorPos,addr pt
		invoke	GetPixel,hdcScreen,pt.x,pt.y
		mov		cr,eax
		
;invoke	SetPixel,hdcScreen,pt.x,pt.y,0
		mov		eax,cr
		.if	(eax!=crLast)
					mov	eax,cr
					mov	crLast,eax
					invoke	InvalidateRect,hwnd,NULL,FALSE
		.endif	
		ret
	.elseif message == WM_PAINT
		invoke	BeginPaint,hwnd,addr ps
		mov		hdc,eax
		invoke	GetClientRect,hwnd,addr rc
		
		mov		eax,cr
		and		eax,0FFh
		push		eax

		mov		eax,cr
		and		eax,0FF00h
		shr		eax,8
		push		eax

		mov		eax,cr
		and		eax,0FF0000h
		shr		eax,16
		push		eax
		
		mov		eax,CTEXT(" [ %02X %02X %02X] ")
		push		eax
		lea		eax,szBuffer
		push		eax
		call		wsprintf
		invoke	DrawText,hdc,addr szBuffer,-1,addr rc,DT_SINGLELINE or DT_CENTER or DT_VCENTER
		
		invoke	EndPaint,hwnd,addr ps
		ret
	.elseif message == WM_DESTROY
		invoke	DeleteDC,hdcScreen
		invoke	KillTimer,hwnd,ID_TIMER
		invoke 	PostQuitMessage,NULL		
		ret	
	.endif	
	
	invoke DefWindowProc,hwnd, message, wParam, lParam
	ret
WndProc endp
END START
        

                                           p3.PNG (14216 字节)

                                                 程序运行结果(可惜看不到鼠标)

WHATCLR在WinMain中做了一点与以往不同的事。因为WHATCLR的窗口只需要显示十六进制RGB值那么大,所以它在CreateWindow函数中使用WS_BORDER窗口样式建立了一个不能改变大小的窗口。要计算窗口的大小,WHATCLR通过先呼叫CreateIC再呼叫GetSystemMetrics以取得用于显示的设备内容信息。计算好的窗口宽度和高度值被传递给CreateWindow。

WHATCLR的窗口消息处理程序在处理WM_CREATE消息处理期间,呼叫CreateDC建立了用于整个视讯显示的设备内容。这个设备内容在程序的生命周期内都有效。在处理WM_TIMER消息处理期间,程序取得目前鼠标光标位置的图素。在处理WM_PAINT消息处理期间显示RGB颜色。

您可能想知道,从CreateDC函数中取得的设备内容句柄是否能让您在屏幕的任意位置显示一些东西,而不光只是取得图素颜色。答案是可以的,一般而言,让一个应用程序在另一个程控的画面区域上画图是不好的,但在某些特殊情况下,这可能会非常有用。比如,有一种“桌面破坏”的小游戏,你可以使用锤子电锯等等,破坏你的桌面。不喜欢的时候,按下一个键,马上又恢复为以前的桌面。



<<<上一篇
欢迎访问AoGo汇编小站:http://www.aogosoft.com
下一篇>>>