Ch 18 更多設備內容的知識(1)

在這一章小木偶將提及有關圓形視窗、路徑 ( path )、區域 ( region ) 等主題,本章末了將示範一個時鐘程式,CLOCK.ASM,說明這些主題。


區域 ( region )

應用程式可以用底下的 API 建立區域 ( region ):

CreateRectRgn、CreateRectRgnIndirect            建立矩形區域
CreateRoundRectRgn                              建立圓角的矩形區域
CreateEllipticRgn、CreateEllipticRgnIndirect    建立橢圓或圓形區域
CreatePolygonRgn、CreatePolyPolygonRgn          建立多邊形區域

區域是一個封閉的範圍,可以經由上面七個 API 建立不同形狀的區域,如果成功的建立起區域,它們會傳回區域代碼。區域是屬於 GDI 物件,建立好區域後,應用程式可以用 SelectObject 把區域選入某一個設備內容堙A然後針對這個區域做處理。跟其他的 GDI 物件一樣,當不再須要時,必須呼叫 DeleteObject 把它刪除。

區域還有另一個用途,就是用來建立特殊形狀的視窗。其步驟是先建立好一個特殊形狀的區域,然後呼叫 SetWindowRgn API 指定視窗為該區域,所以視窗的形狀就變成了該區域的形狀,SetWindowRgn 的原型是:

SetWindowRgn API

int SetWindowRgn(
    HWND    hWnd,   // handle to window whose window region is to be set
    HRGN    hRgn,   // handle to region
    BOOL    bRedraw // window redraw flag
   );

參數 bRedraw 可是 TRUE 或 FALSE,這個參數是指設定好視窗的區域之後是否重繪,TRUE 是指立即重繪,亦即發送 WM_PAINT 給是視窗;FALSE 是指不進行重繪。


路徑 ( path )

路徑並不是 Win32 的圖形物件,並沒有一個稱為路徑代碼的名詞。我們可以為每個設備內容建立一個路徑,這些路徑可以是封閉起來,也可以是開放的,不管是那一種,對系統而言,只記錄所經過的假想線條,並不記錄所圍起來的範圍。想要建立路徑時可以用 BeginPath API,其語法是

BOOL BeginPath(
    HDC hdc     // handle to device context 
   );

每當呼叫 BeginPath 建立一個新的路徑時,舊的路徑就被破壞掉了,所以系統只會為每個設備內容維護一個路徑。建立路徑後,程式所呼叫的每個畫線 API 都會被當做路徑而被系統記錄起來,例如 MoveToEx、LineTo、Arc、Polyline……。當多次呼叫畫線的 API 後,可以呼叫 CloseFigure 使路徑封閉起來,CloseFigure 的語法是

BOOL CloseFigure(
    HDC hdc     // handle to device context
   );

我們也可以在一個設備內容中,建立多個封閉的路徑,即路徑的起點以 MoveTo 或 MoveToEx 設定,然後呼叫其他畫線 API,最後呼叫 CloseFigure 封閉路徑。有了路徑後,我們可以呼叫 StrokePath 來沿著路徑,以當前畫筆畫出路徑來;也可以呼叫 FillPath,以當前筆刷填充封閉的路徑;或者可以呼叫 StrokeAndFillPath 畫出路徑並填充封閉的路徑。

底下的 CLOCK.ASM 程式中,在鐘面上繪出的時針與分針的程式片段,就是建立兩個封閉的路徑,然後呼叫 FillPath 以不同筆刷填充這兩個路徑。除此之外,還可以利用 PathToRegion 把封閉的路徑變成區域,PathToRegion 的語法是:

HRGN PathToRegion(
    HDC hdc     // handle to device context
   );

PathToRegion 的返回值是區域的代碼。當然也可以用 CreateRectRgn、CreateRoundRectRgn、CreateEllipticRgn 等方法直接建立區域,但是先設好封閉的路徑,再把這個封閉的路徑轉化成區域,可以得到複雜形狀的區域。


時鐘程式,CLOCK.ASM

小木偶利用上面的原理撰寫了一個時鐘程式,CLOCK.ASM,執行時畫面如下圖左。使用者可以把滑鼠移到鐘面上任一點,再按下滑鼠左鍵不放,滑鼠游標會變成手的形狀,可以拖曳這個圓形視窗;也可以使滑鼠在鐘面上,按下滑鼠右鍵,便會出現『彈出式選單』,可以變換背景圖或離開程式。底下是 CLOCK.RC 檔的原始碼:

Clock   ICON    clock02.ico
Boy     BITMAP  boy.bmp
Girl    BITMAP  girl.bmp
Dog     BITMAP  dog.bmp

下圖中的右邊三張圖是 CLOCK.RC 會用到的點陣圖:


boy.bmp

girl.bmp

dog.bmp

底下是 CLOCK.ASM 的源碼:

;圓形時鐘程式,可更換背景陣圖
        .586
        .model  flat,stdcall
        option  casemap:none

include         windows.inc
include         kernel32.inc
include         user32.inc
include         gdi32.inc
includelib      kernel32.lib
includelib      user32.lib
includelib      gdi32.lib

IDM_BOY         EQU     1000
IDM_GIRL        EQU     1001
IDM_DOG         EQU     1002
IDM_EXIT        EQU     1003
CLOCK_SIZE      EQU     200
CENTER_PONIT    EQU     CLOCK_SIZE/2-1

;*******************************************************************************
                .const
dw180           DWORD   180
dw360           DWORD   360
six             DWORD   6
five            DWORD   5
fifteen         DWORD   15
twelve          DWORD   12
sixty           DWORD   60
ptCntr          POINT   <CENTER_PONIT,CENTER_PONIT>     ;鐘面中心的座標
lpBmpString     DWORD   OFFSET szBoyBmp,OFFSET szGirlBmp,OFFSET szDogBmp
dwTimerID       DWORD   3000
szClassName     BYTE    'CLOCK_CLASS',0
szAppName       BYTE    'CLOCK',0
szClockIcon     BYTE    'Clock',0
szBoyBmp        BYTE    'Boy',0
szDogBmp        BYTE    'Dog',0
szGirlBmp       BYTE    'Girl',0
szBoy           BYTE    '帥哥背景',0
szGirl          BYTE    '美女背景',0
szDog           BYTE    '小狗背景',0
szExit          BYTE    '離開',0
;*******************************************************************************
                .data
hInstance       HINSTANCE       ?
hwnd            HWND            ?
hMenu           HMENU           ?       ;彈出式選單代碼
hCursorHand     HCURSOR         ?
hBitmap         HBITMAP         ?
hdcMem          HDC             ?       ;記憶體設備內容代碼
wc              WNDCLASSEX      <?>
msg             MSG             <?>
mii             MENUITEMINFO    <?>
point           POINT           <?>
ps              PAINTSTRUCT     <?>
dwScrWidth      DWORD           ?       ;螢幕寬度
dwScrHigh       DWORD           ?       ;螢幕高度
dwCheckMenuItem DWORD           IDM_BOY ;內定的選項為IDM_BOY,使用者可更改成IDB_GIRL,IDM_DOG
loctime         SYSTEMTIME      <?>     ;當地時間
;*******************************************************************************
        .code
;-------------------------------------------------------------------------------
;由半徑(圓心在鐘面正中心)與角度,求出鐘面上某一點的座標。
;輸入-radius:半徑
;      angle:與X軸的夾角
;輸出-EAX:X座標
;      ECX:Y座標
CalcDot PROC    radius:DWORD,angle:DWORD
        LOCAL   val:DWORD
                        ;--st --;--st1--;--st2--;
        fild    dw180   ;  180  ;       ;       ;
        fild    angle   ;   a   ;  180  ;       ;
        fldpi           ;   pi  ;   a   ;  180  ;
        fmulp   st(1),st;  a*pi ;  180  ;       ;
        fdivr           ;api/180;       ;       ;θ=(a*pi/180)
        fld     st      ;   θ   ;   θ   ;       ;
        fsin            ; sinθ  ;   θ   ;       ;
        fimul   radius  ; rsinθ ;   θ   ;       ;
        fiadd   ptCntr.y;   Y   ;   θ   ;       ;
        fistp   val     ;   θ   ;       ;       ;
        fcos            ; cosθ  ;       ;       ;
        mov     ecx,val
        fimul   radius  ; rcosθ ;       ;       ;
        fiadd   ptCntr.x;   X   ;       ;       ;
        fistp   val
        mov     eax,val
        ret
CalcDot ENDP
;-------------------------------------------------------------------------------
;畫出背景點陣圖與時鐘鐘面的刻度。
DrawBmpAndClock PROC    hwin:HWND
        LOCAL   hDC:HDC,angle1:DWORD,angle2:DWORD
        mov     ecx,dwCheckMenuItem
        sub     ecx,IDM_BOY
        shl     ecx,2
        mov     edx,lpBmpString[ecx]    ;EDX指向資源中點陣圖的名稱
        INVOKE  LoadBitmap,hInstance,edx;載入點陣圖
        mov     hBitmap,eax
        INVOKE  GetDC,hwin              ;取得時鐘的裝置內容
        mov     hDC,eax
        INVOKE  CreateCompatibleDC,eax  ;建立相容的記憶體裝置內容
        mov     hdcMem,eax
        INVOKE  SelectObject,hdcMem,hBitmap
;選擇黑色筆刷,以使刻度12、5、10、15…能成為塗滿黑色的圓點
        INVOKE  GetStockObject,BLACK_BRUSH
        INVOKE  SelectObject,hdcMem,eax
        INVOKE  DeleteObject,eax
;畫出刻度,angle1依序是0、30、60、90…330,即分別表示時鐘上的刻度12、5、10、15…55
        mov     angle1,0
@@:     INVOKE  CalcDot,92,angle1
        mov     point.x,eax
        mov     point.y,ecx
        sub     point.x,4
        sub     point.y,4
        add     eax,4
        add     ecx,4
        INVOKE  Ellipse,hdcMem,point.x,point.y,eax,ecx
;angle2是時鐘鐘面上的小刻度,也就是指12和5刻度之間還有四個小的刻度
        mov     angle2,6
next:           mov     eax,angle1
                add     eax,angle2
                push    eax
                INVOKE  CalcDot,92,eax
                INVOKE  MoveToEx,hdcMem,eax,ecx,NULL
                pop     eax
                INVOKE  CalcDot,97,eax
                INVOKE  LineTo,hdcMem,eax,ecx
                add     angle2,6
                cmp     angle2,30
                jb      next
        add     angle1,30
        cmp     angle1,360
        jb      @b
        INVOKE  ReleaseDC,hwin,hDC
        ret
DrawBmpAndClock ENDP
;-------------------------------------------------------------------------------
;計算時針、分針、秒針的頂點座標,時針、分針有四個頂點,秒針有兩個頂點
;輸入-value:時、分或秒
;      r:距離鐘面中心有多遠
;      flag:表示現在計算的是時針、分針還是秒針的頂點,h、m、s分別表示時、分、秒針
;輸出-EAX:X座標
;      ECX:Y座標
CalcPinDot      PROC    value:DWORD,r:DWORD,flag:DWORD
                LOCAL   angle:DWORD
                fild    value
;如果value超過12(計算時針頂點時)或60(計算分、秒針頂點時),應使其在0∼11或0∼59的範圍內
     .IF flag=='h'
          .IF value>=12
                fisub   twelve
          .ENDIF
                ;使分鐘除以60再加上時數,變成確實的時數,例如4時30分變成4.5小時
                fild    loctime.wMinute
                fidiv   sixty
                fadd
                fimul   five    ;時針每小時走30度,但為了配合分針、秒針,此處僅乘以5
     .ELSE
          .IF value>=60
                fisub   sixty
          .ENDIF
     .ENDIF
                fisub   fifteen ;時、分、秒針角度應由通過鐘面中心的鉛垂線順時針開始
                fimul   six     ;每分鐘角度為6度
                cmp     flag,'h'
                je      @f
                cmp     value,15;分、秒針若小於15分或15秒會變負數,再加上360使之變正
                jae     @f
                fiadd   dw360
@@:             fistp   angle
                INVOKE  CalcDot,r,angle
                ret
CalcPinDot      ENDP
;-------------------------------------------------------------------------------
DrawPin PROC    hdc:HDC
        INVOKE  GetLocalTime,OFFSET loctime
        cmp     loctime.wHour,12
        jb      @f
        sub     loctime.wHour,12
;畫時針,時針長75圖素,時鐘中心把時針分成60、15圖素,時針最寬處寬10個圖素
@@:     INVOKE  BeginPath,hdc
        INVOKE  CreatePen,PS_SOLID,1,0448800h
        INVOKE  SelectObject,hdc,eax
        INVOKE  DeleteObject,eax
        INVOKE  CreateSolidBrush,0448800h
        INVOKE  SelectObject,hdc,eax
        INVOKE  DeleteObject,eax
        movzx   edx,loctime.wHour
        INVOKE  CalcPinDot,edx,60,'h'
        INVOKE  MoveToEx,hdc,eax,ecx,NULL
        add     loctime.wHour,3
        movzx   edx,loctime.wHour
        INVOKE  CalcPinDot,edx,5,'h'
        INVOKE  LineTo,hdc,eax,ecx
        add     loctime.wHour,3
        movzx   edx,loctime.wHour
        INVOKE  CalcPinDot,edx,15,'h'
        INVOKE  LineTo,hdc,eax,ecx
        add     loctime.wHour,3
        movzx   edx,loctime.wHour
        INVOKE  CalcPinDot,edx,5,'h'
        INVOKE  LineTo,hdc,eax,ecx
        INVOKE  CloseFigure,hdc
        INVOKE  EndPath,hdc
        INVOKE  FillPath,hdc
;畫分針,分針長95圖素,時鐘中心把分針分為75、20圖素,分針最寬處寬8個圖素
        INVOKE  CreatePen,PS_SOLID,1,0884400h
        INVOKE  SelectObject,hdc,eax
        INVOKE  DeleteObject,eax
        INVOKE  CreateSolidBrush,0884400h
        INVOKE  SelectObject,hdc,eax
        INVOKE  DeleteObject,eax
        INVOKE  BeginPath,hdc
        movzx   edx,loctime.wMinute
        INVOKE  CalcPinDot,edx,75,'m'
        INVOKE  MoveToEx,hdc,eax,ecx,NULL
        add     loctime.wMinute,15
        movzx   edx,loctime.wMinute
        INVOKE  CalcPinDot,edx,4,'m'
        INVOKE  LineTo,hdc,eax,ecx
        add     loctime.wMinute,15
        movzx   edx,loctime.wMinute
        INVOKE  CalcPinDot,edx,20,'m'
        INVOKE  LineTo,hdc,eax,ecx
        add     loctime.wMinute,15
        movzx   edx,loctime.wMinute
        INVOKE  CalcPinDot,edx,4,'m'
        INVOKE  LineTo,hdc,eax,ecx
        INVOKE  CloseFigure,hdc
        INVOKE  EndPath,hdc
        INVOKE  FillPath,hdc
;畫秒針,秒針長110圖素,時鐘中心把秒針分為85、25圖素
        INVOKE  CreatePen,PS_SOLID,2,0ffh
        INVOKE  SelectObject,hdc,eax
        INVOKE  DeleteObject,eax
        movzx   edx,loctime.wSecond
        INVOKE  CalcPinDot,edx,85,'s'   ;計算秒針針尖的座標
        INVOKE  MoveToEx,hdc,eax,ecx,NULL
        movzx   edx,loctime.wSecond
        add     edx,30                  ;若現在為20秒,那麼秒針反向將指向50秒
        INVOKE  CalcPinDot,edx,25,'s'   ;計算反向秒針針尖座標
        INVOKE  LineTo,hdc,eax,ecx
        INVOKE  GetStockObject,BLACK_PEN
        INVOKE  SelectObject,hdc,eax
        INVOKE  DeleteObject,eax
        INVOKE  GetStockObject,BLACK_BRUSH
        INVOKE  SelectObject,hdc,eax
        INVOKE  DeleteObject,eax
;畫出中心圓點
        mov     eax,ptCntr.x
        mov     ecx,ptCntr.y
        mov     point.x,eax
        mov     point.y,ecx
        sub     point.x,3
        sub     point.y,3
        add     eax,3
        add     ecx,3
        INVOKE  Ellipse,hdc,point.x,point.y,eax,ecx
        ret
DrawPin ENDP
;-------------------------------------------------------------------------------
WndProc         PROC    hWnd:HWND,uMsg:UINT,wParam:WPARAM,lParam:LPARAM
                LOCAL   hRegion:HANDLE,hdc:HDC
.IF uMsg==WM_COMMAND
     .IF lParam==0
                mov     eax,wParam
          .IF ax==IDM_EXIT
                jmp     exit
          .ELSE
                push    eax
                mov     mii.fMask,MIIM_STATE
                mov     mii.fState,MFS_UNCHECKED
                INVOKE  SetMenuItemInfo,hMenu,dwCheckMenuItem,FALSE,OFFSET mii
                pop     eax
                mov     dwCheckMenuItem,eax
                mov     mii.fState,MFS_CHECKED
                INVOKE  SetMenuItemInfo,hMenu,eax,FALSE,OFFSET mii
                INVOKE  DrawBmpAndClock,hWnd
                jmp     refresh
          .ENDIF
     .ENDIF

.ELSEIF uMsg==WM_PAINT
                INVOKE  BeginPaint,hWnd,OFFSET ps
                INVOKE  BitBlt,ps.hdc,0,0,CLOCK_SIZE,CLOCK_SIZE,hdcMem,0,0,SRCCOPY
                INVOKE  DrawPin,ps.hdc
                INVOKE  EndPaint,hWnd,OFFSET ps

.ELSEIF uMsg==WM_TIMER
refresh:        INVOKE  InvalidateRect,hWnd,NULL,TRUE

.ELSEIF uMsg==WM_RBUTTONDOWN
                INVOKE  GetCursorPos,OFFSET point
                INVOKE  TrackPopupMenu,hMenu,TPM_LEFTALIGN,point.x,point.y,0,hWnd,0

.ELSEIF uMsg== WM_LBUTTONDOWN
                INVOKE  SetCursor,hCursorHand
                INVOKE  ReleaseCapture
                INVOKE  SendMessage,hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0
                INVOKE  SetCursor,wc.hCursor

.ELSEIF uMsg==WM_CREATE
        ;建立並設定彈出選單
                finit
                INVOKE  CreatePopupMenu
                mov     hMenu,eax
                mov     mii.cbSize,SIZEOF MENUITEMINFO
                mov     mii.fMask,MIIM_TYPE or MIIM_ID
                mov     mii.fType,MFT_STRING or MFT_RADIOCHECK
                mov     mii.dwTypeData,OFFSET szBoy
                mov     mii.wID,IDM_BOY
                INVOKE  InsertMenuItem,hMenu,0,TRUE,OFFSET mii
                mov     mii.dwTypeData,OFFSET szGirl
                mov     mii.wID,IDM_GIRL
                INVOKE  InsertMenuItem,hMenu,1,TRUE,OFFSET mii
                mov     mii.dwTypeData,OFFSET szDog
                mov     mii.wID,IDM_DOG
                INVOKE  InsertMenuItem,hMenu,2,TRUE,OFFSET mii
                mov     mii.fType,MFT_SEPARATOR
                INVOKE  InsertMenuItem,hMenu,3,TRUE,OFFSET mii
                mov     mii.fType,MFT_STRING
                mov     mii.dwTypeData,OFFSET szExit
                mov     mii.wID,IDM_EXIT
                INVOKE  InsertMenuItem,hMenu,4,TRUE,OFFSET mii
                mov     mii.fMask,MIIM_STATE
                mov     mii.fState,MFS_CHECKED
                INVOKE  SetMenuItemInfo,hMenu,IDM_BOY,FALSE,OFFSET mii
        ;設定為圓形視窗
                INVOKE  CreateEllipticRgn,0,0,CLOCK_SIZE+1,CLOCK_SIZE+1
                mov     hRegion,eax
                INVOKE  SetWindowRgn,hWnd,eax,TRUE
                INVOKE  DeleteObject,hRegion
        ;設定計時器
                INVOKE  SetTimer,hWnd,dwTimerID,1000,NULL
        ;設定內定的背景圖並畫出鐘面的刻度
                INVOKE  DrawBmpAndClock,hWnd

.ELSEIF uMsg==WM_DESTROY
exit:           INVOKE  PostQuitMessage,NULL
                INVOKE  KillTimer,hWnd,dwTimerID
                INVOKE  DeleteDC,hdcMem

.ELSE
                INVOKE  DefWindowProc,hWnd,uMsg,wParam,lParam
                ret

.ENDIF
                xor     eax,eax
                ret
WndProc         ENDP
;-------------------------------------------------------------------------------
start:  INVOKE  GetModuleHandle,NULL
        mov     hInstance,eax
        mov     wc.cbSize,SIZEOF WNDCLASSEX
        mov     wc.style,CS_HREDRAW or CS_VREDRAW
        mov     wc.lpfnWndProc,OFFSET WndProc
        mov     wc.cbClsExtra,NULL
        mov     wc.cbWndExtra,NULL
        mov     wc.hInstance,eax
        INVOKE  LoadIcon,NULL,OFFSET szClockIcon
        mov     wc.hIcon,eax
        mov     wc.hIconSm,eax
        INVOKE  LoadCursor,NULL,IDC_ARROW
        mov     wc.hCursor,eax
        mov     wc.hbrBackground,COLOR_WINDOW+1
        mov     wc.lpszMenuName,NULL
        mov     wc.lpszClassName,OFFSET szClassName
        INVOKE  RegisterClassEx,OFFSET wc
;取得螢幕寬度及高度,並減去鐘面大小,再除以二,使時鐘放在螢幕正中央
        INVOKE  GetSystemMetrics,SM_CXSCREEN
        sub     eax,CLOCK_SIZE
        shr     eax,1
        push    eax
        INVOKE  GetSystemMetrics,SM_CYSCREEN
        sub     eax,CLOCK_SIZE
        pop     edx
        shr     eax,1
        INVOKE  CreateWindowEx,NULL,OFFSET szClassName,OFFSET szAppName,\ 
                WS_POPUP or WS_SYSMENU,edx,eax,CLOCK_SIZE,CLOCK_SIZE,0,\
                NULL,hInstance,NULL 
        mov     hwnd,eax
        INVOKE  ShowWindow,eax,SW_SHOWDEFAULT
        INVOKE  LoadCursor,NULL,IDC_HAND
        mov     hCursorHand,eax
        INVOKE  UpdateWindow,hwnd
.WHILE TRUE
        INVOKE  GetMessage,OFFSET msg,NULL,0,0
.BREAK .IF !eax
        INVOKE  TranslateMessage,OFFSET msg
        INVOKE  DispatchMessage,OFFSET msg
.ENDW
        mov     eax,msg.wParam
        INVOKE  ExitProcess,eax
;*******************************************************************************
        END     start

要正確的組譯 CLOCK.ASM 必須先把 boy.bmp、girl.bmp、dog.bmp、clock02.ico、clock.asm 和 clock.rc 六個檔案放在同一個子目錄堙A例如都放在『E:\HomePage\SOURCE\CLOCK』子目錄堙A然後在 XP 桌面開啟『開始』、『所有程式』、『附屬應用程式』、『命令提示字元』,輸入下面指令:

E:\HomePage\SOURCE\CLOCK>rc clock.rc [Enter]

E:\HomePage\SOURCE\CLOCK>ml clock.asm /link clock.res [Enter]
Microsoft (R) Macro Assembler Version 6.14.8444
Copyright (C) Microsoft Corp 1981-1997.  All rights reserved.

 Assembling: clock.asm
Microsoft (R) Incremental Linker Version 5.12.8078
Copyright (C) Microsoft Corp 1992-1998. All rights reserved.

/SUBSYSTEM:WINDOWS
"clock.obj" /DEBUG
"/OUT:clock.exe"
"clock.res"

E:\HomePage\SOURCE\CLOCK>

這樣就可以得到 CLOCK.EXE 檔。如果讀者想試試,小木偶把 clock02.ico、clock.asm 和 clock.rc 三個檔案壓縮成 CLOCK.RAR 檔,至於剩下的三個點陣圖可從上表中下載,將這六個檔案放在同一子目錄依上法組譯即可得 CLOCK.EXE。


沒有標題欄,如何移動視窗?

CLOCK.EXE 程式並沒有標題欄,但是小木偶卻希望如果使用者把滑鼠移到 CLOCK 的圓形視窗上面,並壓下滑鼠左鍵,仍能移動 CLOCK 視窗,小木偶使用的方法是欺騙 Windows 作業系統。在普通的情形下,滑鼠游標位於工作區而使用者按下滑鼠左鍵時,作業系統會發送 WM_LBUTTONDOWN 給視窗,CLOCK 卻在處理 WM_LBUTTONDOWN 訊息時偷偷地發送 WM_NCLBUTTONDOWN 訊息給自己,讓系統以為使用者是在非工作區堳鬗U了滑鼠左鍵。

WM_NCLBUTTONDOWN 訊息

當滑鼠游標在非工作區堙A如標題欄、邊框,而使用者又按下滑鼠左鍵時,系統發送 WM_NCLBUTTONDOWN 給該視窗。這時候,lParam 是指向 POINT 結構體的位址,這個結構體以螢幕左上角為參考點,存放滑鼠游標在螢幕上的位置。wParam 參數則是代表 hit-test values,其部份意義如下表:

數值 意 義數值 意 義
HTBOTTOM游標在底邊框 HTBOTTOMLEFT游標在左下角邊框
HTBOTTOMRIGHT游標在右下角邊框 HTCAPTION游標在標題欄
HTHSCROLL游標在水平捲軸 HTLEFT游標在左邊框
HTMENU游標在選單上 HTBOTTOMRIGHT游標在右下角邊框
HTSYSMENU游標在系統選單 HTREDUCE游標在縮小盒

在 COLCK.ASM 堙A小木偶在處理 WM_LBUTTONDOWN 訊息時,發送以 HTCAPTION 為參數的 WM_NCLBUTTONDOWN 訊息,欺騙 Windows 作業系統,如下面程式:

.ELSEIF uMsg == WM_LBUTTONDOWN
                INVOKE  SetCursor,hCursorHand
                INVOKE  ReleaseCapture
                INVOKE  SendMessage,hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0   ;欺騙作業系統,使其以為使用者在標題欄按下滑鼠左鍵
                INVOKE  SetCursor,wc.hCursor

SetCursor

SetCursor 會改變滑鼠游標的形狀,其原型是:

HCURSOR SetCursor(
    HCURSOR     hCursor         // handle of cursor
   );

參數 hCursor 是 CreateCursor、LoadCursor 或 LoadImage 所傳回的游標代碼,如果此參數是 NULL,那麼游標就會消失。CLOCK.ASM 在處理 WM_LBUTTONDOWN 訊息的第一條指令把滑鼠游標變成手形,這個游標形狀是系統已經預先定義過的,可直接使用。處理 WM_LBUTTONDOWN 最後一條指令是呼叫 SetCursor 恢復成原來箭頭的游標形狀。


畫出時針、分針

因為時針、分針的長度是固定的,但在不同的時間與橫座標的夾角是不同的,請參考右圖,假設針尖走到 A 點,A 點與鐘面中心 O 點的距離是 r,且 OA 與橫座標的夾角為Θ,可以得知在水平方向 A 點與 O 點相距 rcosΘ,垂直方向相距 rcosΘ,所以鐘面上與中心相距 r 的任一點座標為:

X=中心點的 X 座標+rcosΘ
Y=中心點的 Y 座標+rsinΘ

這件工作由 CalcDot 副程式完成。

不管是畫時針也好,還是分針也好,都需要計算四個頂點。例如假設右圖是 4:20 分的時候,分針所在位置,那麼必須把 A、B、C、D 四點連起來形成一個封閉路徑,再呼叫 FillPath 填滿深藍色即可。A 點的座標並不難求,但是小木偶還希望在進行繪製時針或分針時,只要以幾時幾分為參數呼叫副程式計算針尖尖端的座標即可,假設這個畫針尖尖端的副程式名為 CalcPinDot,那麼要計算 4:20 分分針前端座標,只要用

        INVOKE  CalcPinDot,75,20,'m'

呼叫 CalcPinDot 即可,75 是分針前端長度,『20』表示 20 分,『'm'』表示分針,CalcPinDot 會把計算所得的 X 座標、Y 座標分別存於 EAX、ECX 並傳回來。計算 B、C、D 三點的座標比較麻煩,但是我們可以這樣想:當分針為 20 分時,OB 點與 OA 垂直,所以可以把 B 點思考成是 35 分時,分針前端位置,但是長度則不到 75,小木偶假設分針寬為 8,故 OB 的長度為 4。同理,C 點可以想成是 55 分時分針前端位置,但是長度則為 15。換句話說,分針 B 點比 A 點多 15 分鐘,C 點比 A 點多 30 分鐘,D 點比 A 點多 45 分鐘。所以如果已經取得時間,並存於 loctime 結構體後,可以用以下程式片段畫出填滿分針的圖形:

        movzx   edx,loctime.wMinute
        INVOKE  CalcPinDot,edx,75,'m'           ;計算 A 點座標
        INVOKE  MoveToEx,hdc,eax,ecx,NULL
        add     loctime.wMinute,15
        movzx   edx,loctime.wMinute
        INVOKE  CalcPinDot,edx,4,'m'           ;計算 B 點座標
        INVOKE  LineTo,hdc,eax,ecx
        add     loctime.wMinute,15
        movzx   edx,loctime.wMinute
        INVOKE  CalcPinDot,edx,20,'m'           ;計算 C 點座標
        INVOKE  LineTo,hdc,eax,ecx
        add     loctime.wMinute,15
        movzx   edx,loctime.wMinute
        INVOKE  CalcPinDot,edx,4,'m'           ;計算 D 點座標
        INVOKE  LineTo,hdc,eax,ecx
        INVOKE  CloseFigure,hdc

回到首頁到第十七章到第十九章