忙了两三天的空余时间,终于把桌面时钟程序完成了,功能也是比较粗糙的。众所周知的,汇编写程序容易造成代码膨胀,本程序光代码就用了465行,对于我来说也算是不少了……
除了实现一些基本功能外,我还为程序实现了在托盘显示的功能。关于实现该功能,当然是费了不少时间,首先要感谢lczelion,他的理论支持下,让我知道了该如何去调用API,以及注意事项。主要还是windows外壳函数Shell_NotifyIcon和结构体NOTIFYICONDATA <>去配合实现的功能,具体如何调用网上也许有很多,就不去说了。但是还有一点就是如何实现窗口像QQ那样,只有托盘图标而把任务栏窗口隐藏?这是我所碰到的问题:原来CreateWindowEx中有个扩展属性WS_EX_TOOLWINDOW,该属性可以把我的想法实现。
对于最费事的还是如何去画时钟,去使用位图。如何使用DC以及ROP码去实现时钟图案和背景,如何去画时钟的指针和外围的圆点,如何去使用浮点寄存器去精确计算时钟指针的坐标来为我所用……这些都是我所碰到的问题以及学习到得知识。再此又要感谢老罗了~~
唯一一点没有按照我想法去走的就是如何只能让该实例程序只运行一个。由于时间关系,这个以后再去实现。据说要调用API,利用互斥法实现,具体思路和函数都知道了,实现也只是迟早的事情了。
程序中一些基本的语句就没有注释,关键的地方用注释表明了。不明白的地方亦可以把MSDN拿出来查查。下面就把代码贴上去:
资源文件Clock.rc的源代码如下:
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< #include <resource.h> //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< #define ICO_MAIN 100 #define IDC_MAIN 100 #define IDC_MOVE 101 #define IDB_BACK1 100 #define IDB_CIRCLE1 101 #define IDB_MASK1 102 #define IDB_BACK2 103 #define IDB_CIRCLE2 104 #define IDB_MASK2 105 #define ICO_TRAYICON 106 //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< ICO_MAIN ICON "Main.ico" IDC_MAIN CURSOR "Main.cur" IDC_MOVE CURSOR "Move.cur" IDB_BACK1 BITMAP "Back1.bmp" IDB_CIRCLE1 BITMAP "Circle1.bmp" IDB_MASK1 BITMAP "Mask1.bmp" IDB_BACK2 BITMAP "Back2.bmp" IDB_CIRCLE2 BITMAP "Circle2.bmp" IDB_MASK2 BITMAP "Mask2.bmp" ICO_TRAYICON ICON "TrayIcon.ico" //<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
Clock.asm源代:
;---------------------------------------------------------------- include windows.inc include user32.inc includelib user32.lib include kernel32.inc includelib kernel32.lib include gdi32.inc includelib gdi32.lib include shell32.inc includelib shell32.lib ;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< WM_SHELLNOTIFY equ WM_USER + 1 IDI_TRAYICON equ 1 ;托盘小图标 ID CLOCK_SIZE equ 150 ICO_MAIN equ 100 IDC_MAIN equ 100 IDC_MOVE equ 101 IDB_BACK1 equ 100 IDB_CIRCLE1 equ 101 IDB_MASK1 equ 102 IDB_BACK2 equ 103 IDB_CIRCLE2 equ 104 IDB_MASK2 equ 105 ICO_TRAYICON equ 106 ID_TIMER equ 1 IDM_BACK1 equ 100 IDM_BACK2 equ 101 IDM_CIRCLE1 equ 102 IDM_CIRCLE2 equ 103 IDM_TOPMOST equ 104 IDM_HIDE equ 105 IDM_SHOW equ 106 IDM_EXIT equ 107 ;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< .data? hInstance dd ? hWinMain dd ? hCursorMain dd ? hCursorMove dd ? hMenu dd ? hBmpBack dd ? hDcBack dd ? hBmpClock dd ? hDcClock dd ? dwNowBack dd ? dwNowCircle dd ? stNote NOTIFYICONDATA <> ;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< .const szClassName db "Clock",0 dwParam180 dw 180 dwRadius dw CLOCK_SIZE/2 szTrayTip db "Powered by Win32 Assembly",0 szMenuBack1 db "使用格子背景(&A)",0 szMenuBack2 db "使用花布背景(&B)",0 szMenuCircle1 db "使用淡蓝色边框(&C)",0 szMenuCircle2 db "使用粉红色边框(&D)",0 szMenuTopMost db "总在最前(&T)",0 szMenuHide db "隐藏时钟窗口(&H)",0 szMenuShow db "显示时钟窗口(&S)",0 szMenuExit db "退出(&X)...",0 ;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< .code ;======================================== ;计算出圆周上某个角度对应的 X 坐标 ; X = 圆心X + sin(角度)*半径 ;======================================== _CalcX proc _dwDegree, _dwRadius local @dwReturn:DWORD fild dwRadius fild _dwDegree fldpi fmul fild dwParam180 fdivp st(1),st fsin fild _dwRadius fmul fadd fistp @dwReturn mov eax,@dwReturn ret _CalcX endp ;======================================== ;计算出圆周上某个角度对应的 Y 坐标 ; Y = 圆心Y - cos(角度)*半径 ;======================================== _CalcY proc _dwDegree, _dwRadius local @dwReturn:DWORD fild dwRadius fild _dwDegree fldpi fmul fild dwParam180 fdivp st(1),st fcos fild _dwRadius fmul fsubp st(1),st fistp @dwReturn mov eax,@dwReturn ret _CalcY endp ;======================================== ; 画 _dwDegree 角度的线条, ; 半径 = _dwRadius ;======================================== _DrawLine proc _hDC, _dwDegree, _dwRadius local @dwX1, @dwY1 local @dwX2, @dwY2 invoke _CalcX,_dwDegree,_dwRadius mov @dwX1,eax invoke _CalcY,_dwDegree,_dwRadius mov @dwY1,eax ;--------------------------------- add _dwDegree,180 invoke _CalcX,_dwDegree,10 mov @dwX2,eax invoke _CalcY,_dwDegree,10 mov @dwY2,eax invoke MoveToEx,_hDC,@dwX1,@dwY1,NULL invoke LineTo,_hDC,@dwX2,@dwY2 ret _DrawLine endp ;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< _CreateClockPic proc local @stTime:SYSTEMTIME pushad invoke BitBlt,hDcClock,0,0,CLOCK_SIZE,CLOCK_SIZE,hDcBack,0,0,SRCCOPY ;=========================画时钟指针 invoke GetLocalTime,addr @stTime invoke CreatePen,PS_SOLID,1,0 invoke SelectObject,hDcClock,eax invoke DeleteObject,eax movzx eax,@stTime.wSecond mov ecx,360/60 mul ecx invoke _DrawLine,hDcClock,eax,60 ;----------------------------------- invoke CreatePen,PS_SOLID,2,0 invoke SelectObject,hDcClock,eax invoke DeleteObject,eax movzx eax,@stTime.wMinute mov ecx,360/60 mul ecx invoke _DrawLine,hDcClock,eax,56 ;----------------------------------- invoke CreatePen,PS_SOLID,3,0 invoke SelectObject,hDcClock,eax invoke DeleteObject,eax movzx eax,@stTime.wHour .if eax >= 12 sub eax,12 .endif mov ecx,360/12 mul ecx movzx ecx,@stTime.wMinute shr ecx,1 add eax,ecx invoke _DrawLine,hDcClock,eax,53 invoke GetStockObject,NULL_PEN invoke SelectObject,hDcClock,eax invoke DeleteObject,eax popad ret _CreateClockPic endp ;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< _CreateBackGround proc local @hDC, @hDcCircle, @hDcMask local @hBmpBack, @hBmpCircle, @hBmpMask invoke GetDC,hWinMain mov @hDC,eax invoke CreateCompatibleDC,@hDC mov hDcBack,eax invoke CreateCompatibleDC,@hDC mov hDcClock,eax invoke CreateCompatibleDC,@hDC mov @hDcCircle,eax invoke CreateCompatibleDC,@hDC mov @hDcMask,eax invoke CreateCompatibleBitmap,@hDC,CLOCK_SIZE,CLOCK_SIZE mov hBmpBack,eax invoke CreateCompatibleBitmap,@hDC,CLOCK_SIZE,CLOCK_SIZE mov hBmpClock,eax invoke ReleaseDC,hWinMain,@hDC invoke LoadBitmap,hInstance,dwNowBack mov @hBmpBack,eax invoke LoadBitmap,hInstance,dwNowCircle mov @hBmpCircle,eax mov eax,dwNowCircle inc eax invoke LoadBitmap,hInstance,eax mov @hBmpMask,eax invoke SelectObject,hDcBack,hBmpBack invoke SelectObject,hDcClock,hBmpClock invoke SelectObject,@hDcCircle,@hBmpCircle invoke SelectObject,@hDcMask,@hBmpMask ;==========================以背景图片填充 invoke CreatePatternBrush,@hBmpBack push eax invoke SelectObject,hDcBack,eax invoke PatBlt,hDcBack,0,0,CLOCK_SIZE,CLOCK_SIZE,PATCOPY pop eax invoke DeleteObject,eax ;>>>>>>>>>>>关键技术<<<<<<<<<<< ;/// ; 块传送函数中对于 ROP 码的操作 ;/// ;==========================画钟面 invoke BitBlt,hDcBack,0,0,CLOCK_SIZE,CLOCK_SIZE,@hDcMask,0,0,SRCAND invoke BitBlt,hDcBack,0,0,CLOCK_SIZE,CLOCK_SIZE,@hDcCircle,0,0,SRCPAINT invoke DeleteDC,@hDcCircle invoke DeleteDC,@hDcMask invoke DeleteObject,@hBmpBack invoke DeleteObject,@hBmpCircle invoke DeleteObject,@hBmpMask ret _CreateBackGround endp ;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< _Initial proc ;========================初始化托盘图标 mov stNote.cbSize,sizeof NOTIFYICONDATA push hWinMain pop stNote.hwnd mov stNote.uID,IDI_TRAYICON mov stNote.uFlags,NIF_ICON+NIF_MESSAGE+NIF_TIP mov stNote.uCallbackMessage,WM_SHELLNOTIFY invoke LoadIcon,hInstance,ICO_TRAYICON mov stNote.hIcon,eax invoke lstrcpy,addr stNote.szTip,addr szTrayTip invoke Shell_NotifyIcon,NIM_ADD,offset stNote ;========================创建弹出菜单项 invoke CreatePopupMenu mov hMenu,eax invoke AppendMenu,hMenu,MF_BYCOMMAND,IDM_BACK1,offset szMenuBack1 invoke AppendMenu,hMenu,MF_BYCOMMAND,IDM_BACK2,offset szMenuBack2 invoke AppendMenu,hMenu,MF_SEPARATOR,0,0 invoke AppendMenu,hMenu,MF_BYCOMMAND,IDM_CIRCLE1,offset szMenuCircle1 invoke AppendMenu,hMenu,MF_BYCOMMAND,IDM_CIRCLE2,offset szMenuCircle2 invoke AppendMenu,hMenu,MF_SEPARATOR,0,0 invoke AppendMenu,hMenu,MF_BYCOMMAND,IDM_TOPMOST,offset szMenuTopMost invoke AppendMenu,hMenu,MF_BYCOMMAND,IDM_HIDE,offset szMenuHide invoke AppendMenu,hMenu,MF_BYCOMMAND,IDM_SHOW,offset szMenuShow invoke AppendMenu,hMenu,MF_SEPARATOR,0,0 invoke AppendMenu,hMenu,MF_BYCOMMAND,IDM_EXIT,offset szMenuExit invoke CheckMenuRadioItem,hMenu,IDM_BACK1,IDM_BACK2,IDM_BACK1,MF_BYCOMMAND invoke CheckMenuRadioItem,hMenu,IDM_CIRCLE1,IDM_CIRCLE2,IDM_CIRCLE1,MF_BYCOMMAND ;========================设置圆形窗口 invoke CreateEllipticRgn,0,0,CLOCK_SIZE+1,CLOCK_SIZE+1 push eax invoke SetWindowRgn,hWinMain,eax,TRUE pop eax invoke DeleteObject,eax ;========================建立背景 mov dwNowBack,IDB_BACK1 mov dwNowCircle,IDB_CIRCLE1 invoke _CreateBackGround invoke _CreateClockPic ;========================建立周期为 1s 的计时器 invoke SetTimer,hWinMain,ID_TIMER,1000,NULL ret _Initial endp ;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< _DelBackGround proc invoke DeleteDC,hDcBack invoke DeleteDC,hDcClock invoke DeleteObject,hBmpBack invoke DeleteObject,hBmpClock ret _DelBackGround endp ;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< _Quit proc invoke _DelBackGround invoke KillTimer,hWinMain,ID_TIMER invoke Shell_NotifyIcon,NIM_DELETE,addr stNote invoke DestroyWindow,hWinMain invoke PostQuitMessage,NULL invoke DestroyMenu,hMenu ret _Quit endp ;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< _ProcWinMain proc uses ebx edi esi hWnd, uMsg, wParam, lParam local @stPS:PAINTSTRUCT local @hDC local @stPos:POINT mov eax,uMsg .if eax == WM_TIMER invoke _CreateClockPic invoke InvalidateRect,hWnd,NULL,FALSE .elseif eax == WM_PAINT invoke BeginPaint,hWnd,addr @stPS mov @hDC,eax mov eax,@stPS.rcPaint.right sub eax,@stPS.rcPaint.left mov ecx,@stPS.rcPaint.bottom sub ecx,@stPS.rcPaint.top invoke BitBlt,@hDC,@stPS.rcPaint.left,@stPS.rcPaint.top,eax,ecx,/ hDcClock,@stPS.rcPaint.left,@stPS.rcPaint.top,SRCCOPY invoke EndPaint,hWnd,addr @stPS .elseif eax == WM_RBUTTONDOWN invoke GetCursorPos,addr @stPos invoke EnableMenuItem,hMenu,IDM_SHOW,MF_GRAYED invoke TrackPopupMenu,hMenu,TPM_LEFTALIGN,@stPos.x,@stPos.y,NULL,hWnd,NULL .elseif eax == WM_LBUTTONDOWN invoke SetCursor,hCursorMove invoke UpdateWindow,hWnd invoke ReleaseCapture invoke SendMessage,hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0 invoke SetCursor,hCursorMain .elseif eax == WM_COMMAND mov eax,wParam .if ax == IDM_BACK1 mov dwNowBack,IDB_BACK1 invoke CheckMenuRadioItem,hMenu,IDM_BACK1,IDM_BACK2,IDM_BACK1,MF_BYCOMMAND .elseif ax == IDM_BACK2 mov dwNowBack,IDB_BACK2 invoke CheckMenuRadioItem,hMenu,IDM_BACK1,IDM_BACK2,IDM_BACK2,MF_BYCOMMAND .elseif ax == IDM_CIRCLE1 mov dwNowCircle,IDB_CIRCLE1 invoke CheckMenuRadioItem,hMenu,IDM_CIRCLE1,IDM_CIRCLE2,IDM_CIRCLE1,MF_BYCOMMAND .elseif ax == IDM_CIRCLE2 mov dwNowCircle,IDB_CIRCLE2 invoke CheckMenuRadioItem,hMenu,IDM_CIRCLE1,IDM_CIRCLE2,IDM_CIRCLE2,MF_BYCOMMAND .elseif ax == IDM_TOPMOST movzx eax,ax mov ebx,eax invoke GetMenuState,hMenu,ebx,MF_BYCOMMAND .if eax == MF_CHECKED invoke CheckMenuItem,hMenu,ebx,MF_UNCHECKED invoke SetWindowPos,hWnd,HWND_NOTOPMOST,0,0,0,0,SWP_NOMOVE or SWP_NOSIZE .else invoke CheckMenuItem,hMenu,ebx,MF_CHECKED invoke SetWindowPos,hWnd,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE or SWP_NOSIZE .endif .elseif ax == IDM_SHOW invoke ShowWindow,hWnd,SW_RESTORE ;invoke SetWindowPos,hWnd,HWND_TOPMOST,0,0,0,0,SWP_NOMOVE or SWP_NOSIZE ;invoke SetWindowPos,hWnd,HWND_NOTOPMOST,0,0,0,0,SWP_NOMOVE or SWP_NOSIZE invoke SetForegroundWindow,hWnd .elseif ax == IDM_HIDE invoke ShowWindow,hWnd,SW_HIDE .elseif ax == IDM_EXIT call _Quit xor eax,eax ret .endif invoke _DelBackGround invoke _CreateBackGround invoke _CreateClockPic invoke InvalidateRect,hWnd,NULL,FALSE .elseif eax == WM_SHELLNOTIFY .if wParam == IDI_TRAYICON .if lParam == WM_RBUTTONDOWN invoke GetCursorPos,addr @stPos invoke SetForegroundWindow,hWnd invoke EnableMenuItem,hMenu,IDM_SHOW,MF_ENABLED invoke TrackPopupMenu,hMenu,TPM_RIGHTALIGN,@stPos.x,@stPos.y,NULL,hWnd,NULL invoke PostMessage,hWnd,WM_NULL,0,0 .elseif lParam == WM_LBUTTONDBLCLK invoke SendMessage,hWnd,WM_COMMAND,IDM_SHOW,0 .endif .endif .elseif eax == WM_CREATE mov eax,hWnd mov hWinMain,eax invoke _Initial .elseif eax == WM_CLOSE call _Quit .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif xor eax,eax ret _ProcWinMain endp ;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< _WinMain proc local @stWndClass:WNDCLASSEX local @stMsg:MSG invoke GetModuleHandle,NULL mov hInstance,eax invoke LoadCursor,hInstance,IDC_MAIN mov hCursorMain,eax invoke LoadCursor,hInstance,IDC_MOVE mov hCursorMove,eax invoke RtlZeroMemory,addr @stWndClass,sizeof @stWndClass invoke LoadIcon,hInstance,ICO_MAIN mov @stWndClass.hIcon,eax mov @stWndClass.hIconSm,eax push hCursorMain pop @stWndClass.hCursor push hInstance pop @stWndClass.hInstance mov @stWndClass.cbSize,sizeof WNDCLASSEX mov @stWndClass.style,CS_HREDRAW or CS_VREDRAW or CS_DBLCLKS mov @stWndClass.lpfnWndProc,offset _ProcWinMain mov @stWndClass.hbrBackground,COLOR_WINDOW + 1 mov @stWndClass.lpszClassName,offset szClassName invoke RegisterClassEx,addr @stWndClass invoke CreateWindowEx,WS_EX_TOOLWINDOW,/ offset szClassName,offset szClassName,/ WS_SYSMENU or WS_POPUP,/ 900,100,CLOCK_SIZE,CLOCK_SIZE,/ NULL,NULL,hInstance,NULL mov hWinMain,eax invoke ShowWindow,hWinMain,SW_SHOWNORMAL invoke UpdateWindow,hWinMain .while TRUE invoke GetMessage,addr @stMsg,NULL,0,0 .break .if eax == 0 invoke TranslateMessage,addr @stMsg invoke DispatchMessage,addr @stMsg .endw ret _WinMain endp ;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< start: call _WinMain invoke ExitProcess,NULL ;<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< end start
实验图片如下:
时钟的图片:
鼠标放在托盘上:
在时钟上点击右键:
在托盘图标上点击右键: