前面一期的《用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中,的模态消息循环会试图给您的窗口消息处理程序传递一些丢失的消息。这样做有时工作得很好,有时却不行。 方法二 设定定时器的第一种方法是把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所示。
虽然,在图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。
等方向性(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
程序运行结果(可惜看不到鼠标) WHATCLR在WinMain中做了一点与以往不同的事。因为WHATCLR的窗口只需要显示十六进制RGB值那么大,所以它在CreateWindow函数中使用WS_BORDER窗口样式建立了一个不能改变大小的窗口。要计算窗口的大小,WHATCLR通过先呼叫CreateIC再呼叫GetSystemMetrics以取得用于显示的设备内容信息。计算好的窗口宽度和高度值被传递给CreateWindow。 WHATCLR的窗口消息处理程序在处理WM_CREATE消息处理期间,呼叫CreateDC建立了用于整个视讯显示的设备内容。这个设备内容在程序的生命周期内都有效。在处理WM_TIMER消息处理期间,程序取得目前鼠标光标位置的图素。在处理WM_PAINT消息处理期间显示RGB颜色。 您可能想知道,从CreateDC函数中取得的设备内容句柄是否能让您在屏幕的任意位置显示一些东西,而不光只是取得图素颜色。答案是可以的,一般而言,让一个应用程序在另一个程控的画面区域上画图是不好的,但在某些特殊情况下,这可能会非常有用。比如,有一种“桌面破坏”的小游戏,你可以使用锤子电锯等等,破坏你的桌面。不喜欢的时候,按下一个键,马上又恢复为以前的桌面。
|
|