在前面八篇的 Win32asm 教程中,已经初步讲述了消息框、对话框、菜单、资源、GDI 等内容,基本上已经设计到了 Windows 界面的大部分内容,在继续新的 Windows 其他部分的内容如多线程、文件操作、内存操作之前,我先综合前面的内容并加上一些新内容,写上一篇综合篇。
本篇的例子程序是一个复杂形状的窗口,窗口的形状是根据位图自动计算得到的,这也就是在我编写的小闹钟中使用的技术(大家可以到我的软件发布中下载一个看看),由于以前在网上看到的有关特殊形状窗口的例子最多就是画一个圆形,或者几个方块和椭圆结合的形状,没有一篇文章指出如何画出如“唐老鸭”这样一个造型的窗口。本文使用的算法可以自动根据位图的形状计算窗口形状。
在源程序中,很多代码都是前面教程提到的,主要有以下部分:
- 首先建立一个标准的窗口。(参考窗口一节)
- 设置窗口为特殊形状。(见下面的程序分析)
- 在窗口的 WM_PAINT 消息中更新窗口的图片。(参考图形界面一节)
- 由于窗口没有标题栏,所以在右击窗口时弹出一个菜单。(参考菜单一节)
- 菜单中有个“关于本程序”项,里面有超联结文本。(参考窗口子类化一节)
Windows 里有专门的 API 来实现特殊形状的窗口,步骤是首先建立区域(Region),Region 可以合并,这样一来就可以用几个简单的区域合并出一个复杂的区域,建立、合并区域和设置窗口的 API 主要有以下几条:
- CreateRectRgn(Left,Top,Right,Bottom) - 建立矩型区域
- CreateEllipticRgn(Left,Top,Right,Bottom) - 建立椭圆区域
- CreatePolygonRgn(lpPoints,NumberOfPoints,Mode) - 建立多边形区域,这些API返回区域句柄
- CombineRgn(hDest,hSource1,hSource2,CombineMode) - 合并区域
- SetWindowRgn(hWnd,hRgn,bRedraw) - 根据区域设置窗口形状
本程序的方法是扫描位图的点,按行设置区域,然后合并到总的区域中。
源程序 - 汇编源文件
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 是否包括调试代码 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> DEBUG = 0 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Programmed by 罗云彬, bigluo@telekbird.com.cn ; Website: http://asm.yeah.net ; LuoYunBin's Win32 ASM page (罗云彬的编程乐园) ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 版本信息 ; 特殊形状窗口的演示程序 Ver 1.0 ; 可以根据位图自动设置窗口的形状。 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .386 .model flat, stdcall option casemap :none ; case sensitive ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Include 数据 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> include windows.inc include user32.inc include kernel32.inc include comctl32.inc include comdlg32.inc include shell32.inc include gdi32.inc includelib user32.lib includelib kernel32.lib includelib comctl32.lib includelib comdlg32.lib includelib shell32.lib includelib gdi32.lib ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; Equ 数据 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ;************** Equ 数据 ********************************** IDI_MAIN equ 1 ;icon IDC_HANDLE equ 2 ;Cursor ;************** Equ 数据 ********************************** DLG_ABOUT equ 1200 ;dialog - about ID_ABOUT_OK equ 1201 ID_EMAIL equ 1202 ID_HOMEPAGE equ 1203 ;************** Equ 数据 ********************************** IDM_MAIN equ 2000 IDM_ABOUT equ 2001 IDM_EXIT equ 2002 ;************** Equ 数据 ********************************** IDB_0 equ 3000 ;bitmap ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 数据段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .data? hInstance dd ? hWinMain dd ? hIcon dd ? hCursor dd ? hMenu dd ? hBmpBack dd ? ;background bitmap hDcBack dd ? ;************** 数据段 ************************************ .data szClassName db 'ShapeWindow',0 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 代码段 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> .code if DEBUG include Debug.asm endif ;******************************************************************** ; 设置窗口形状为BMP图形形状 ; 参数:窗口句柄,BMP图形句柄 ; 输入BMP图形要求:0,0处颜色为背景色 ;******************************************************************** _SetWindowShape proc hWnd:DWORD,hBitMap:DWORD local @hDC:DWORD,@hBmpDC:DWORD local @stPs:PAINTSTRUCT local @stRect:RECT local @stBmp:BITMAP local @dwX:DWORD,@dwY:DWORD,@dwStartX:DWORD local @hRgn:DWORD,@hRgnTemp:DWORD local @rgbBack:DWORD invoke GetObject,hBitMap,sizeof BITMAP,addr @stBmp invoke GetWindowRect,hWnd,addr @stRect invoke ShowWindow,hWnd,SW_HIDE invoke MoveWindow,hWnd,@stRect.left,@stRect.top,/ @stBmp.bmWidth,@stBmp.bmHeight,FALSE invoke GetDC,hWnd mov @hDC,eax invoke CreateCompatibleDC,@hDC mov @hBmpDC,eax invoke SelectObject,@hBmpDC,hBitMap ;*************** 计算窗口形状 *************************************** invoke GetPixel,@hBmpDC,0,0 mov @rgbBack,eax invoke CreateRectRgn,0,0,0,0 mov @hRgn,eax mov @dwY,0 .while TRUE mov @dwX,0 mov @dwStartX,-1 .while TRUE invoke GetPixel,@hBmpDC,@dwX,@dwY .if @dwStartX == -1 .if eax != @rgbBack mov eax,@dwX mov @dwStartX,eax .endif .else .if eax == @rgbBack mov ecx,@dwY inc ecx invoke CreateRectRgn,@dwStartX,@dwY,@dwX,ecx invoke CombineRgn,@hRgn,@hRgn,eax,RGN_OR mov @dwStartX,-1 .else mov eax,@dwX .if eax == @stBmp.bmWidth inc eax mov ecx,@dwY inc ecx invoke CreateRectRgn,@dwStartX,@dwY,eax,ecx invoke CombineRgn,@hRgn,@hRgn,eax,RGN_OR mov @dwStartX,-1 .endif .endif .endif inc @dwX mov eax,@dwX .break .if eax > @stBmp.bmWidth .endw inc @dwY mov eax,@dwY .break .if eax > @stBmp.bmHeight .endw invoke SetWindowRgn,hWnd,@hRgn,TRUE ;******************************************************************** invoke BitBlt,@hDC,0,0,@stBmp.bmWidth,@stBmp.bmHeight,/ @hBmpDC,0,0,SRCCOPY invoke DeleteDC,@hBmpDC invoke ReleaseDC,hWnd,@hDC invoke InvalidateRect,hWnd,NULL,-1 ret _SetWindowShape endp ;******************************************************************** ; 将窗口移动到屏幕中间 ; 参数:窗口句柄 ;******************************************************************** _CenterWindow proc hWnd:DWORD local @stRectDeskTop:RECT,@stRectWin:RECT local @dwWidth:DWORD,@dwHeight:DWORD invoke GetWindowRect,hWnd,addr @stRectWin invoke GetDesktopWindow mov ebx,eax invoke GetWindowRect,ebx,addr @stRectDeskTop mov eax,@stRectWin.bottom sub eax,@stRectWin.top mov @dwHeight,eax mov eax,@stRectWin.right sub eax,@stRectWin.left mov @dwWidth,eax mov ebx,@stRectDeskTop.bottom sub ebx,@dwHeight shr ebx,1 mov ecx,@stRectDeskTop.right sub ecx,@dwWidth shr ecx,1 invoke MoveWindow,hWnd,ecx,ebx,@dwWidth,@dwHeight,FALSE ret _CenterWindow endp ;******************************************************************** include About.asm ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 程序开始 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> start: call _WinMain invoke ExitProcess,NULL ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> ; 主窗口程序 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> _WinMain proc local @stWcMain:WNDCLASSEX local @stMsg:MSG invoke InitCommonControls invoke GetModuleHandle,NULL mov hInstance,eax invoke LoadIcon,hInstance,IDI_MAIN mov hIcon,eax invoke LoadMenu,hInstance,IDM_MAIN invoke GetSubMenu,eax,0 ;PopUp 菜单要用到子菜单 mov hMenu,eax ;*************** 注册窗口类 ***************************************** invoke LoadCursor,0,IDC_ARROW mov @stWcMain.hCursor,eax mov @stWcMain.cbSize,sizeof WNDCLASSEX mov @stWcMain.hIconSm,0 mov @stWcMain.style,CS_HREDRAW or CS_VREDRAW mov @stWcMain.lpfnWndProc,offset WndMainProc mov @stWcMain.cbClsExtra,0 mov @stWcMain.cbWndExtra,0 mov eax,hInstance mov @stWcMain.hInstance,eax mov @stWcMain.hIcon,0 mov @stWcMain.hbrBackground,COLOR_WINDOW + 1 mov @stWcMain.lpszClassName,offset szClassName mov @stWcMain.lpszMenuName,0 invoke RegisterClassEx,addr @stWcMain ;***************** 建立输出窗口 ***************************************** ; 属性:没有标题栏,不显示在任务栏 ;******************************************************************** invoke CreateWindowEx,WS_EX_TOOLWINDOW,/ offset szClassName,NULL,/ WS_POPUP or WS_SYSMENU,/ 0,0,1,1,/ NULL,NULL,hInstance,NULL 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 ;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>> WndMainProc proc uses ebx edi esi, / hWnd:DWORD,uMsg:DWORD,wParam:DWORD,lParam:DWORD local @stPos:POINT local @stPs:PAINTSTRUCT,@hDC:DWORD mov eax,uMsg .if eax == WM_CREATE mov eax,hWnd mov hWinMain,eax call _Init ;******************************************************************** .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,/ hDcBack,@stPs.rcPaint.left,@stPs.rcPaint.top,SRCCOPY invoke EndPaint,hWnd,addr @stPs ;******************************************************************** ; 由于没有菜单,下面代码用于按下右键时弹出POPUP菜单 ;******************************************************************** .elseif eax == WM_RBUTTONDOWN .if wParam == MK_RBUTTON invoke GetCursorPos,addr @stPos invoke TrackPopupMenu,hMenu,TPM_LEFTALIGN,@stPos.x,@stPos.y,NULL,hWnd,NULL .endif ;******************************************************************** ; 由于没有标题栏,下面代码用于按下左键时移动窗口 ;******************************************************************** .elseif eax == WM_LBUTTONDOWN invoke UpdateWindow,hWnd ;即时刷新 invoke ReleaseCapture invoke SendMessage,hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0 ;******************************************************************** .elseif eax == WM_COMMAND .if lParam == 0 mov eax,wParam .if ax == IDM_EXIT call _Quit .elseif ax == IDM_ABOUT invoke DialogBoxParam,hInstance,DLG_ABOUT,hWnd,offset AboutDialogProc,DLG_ABOUT .endif .endif ;******************************************************************** .elseif eax == WM_CLOSE call _Quit ;******************************************************************** .else invoke DefWindowProc,hWnd,uMsg,wParam,lParam ret .endif ;******************************************************************** ; 注意:WndProc 处理 Windows 消息后,必须在 Eax 中返回 0 ; 但是由 DefWindowProc 处理后的返回值不能改变,否则窗口 ; 将无法显示! ;******************************************************************** xor eax,eax ret WndMainProc endp ;******************************************************************** _Init proc local @hDC invoke SendMessage,hWinMain,WM_SETTEXT,0,offset szClassName invoke SendMessage,hWinMain,WM_SETICON,ICON_SMALL,hIcon invoke LoadBitmap,hInstance,IDB_0 ;装入背景图片 mov hBmpBack,eax invoke _SetWindowShape,hWinMain,hBmpBack ;设置窗口形状为背景图片 invoke GetDC,hWinMain mov @hDC,eax invoke CreateCompatibleDC,@hDC ;建立背景及数字 DC mov hDcBack,eax invoke ReleaseDC,hWinMain,@hDC invoke SelectObject,hDcBack,hBmpBack invoke _CenterWindow,hWinMain ret _Init endp ;******************************************************************** _Quit proc local @stWindow:RECT invoke DestroyMenu,hMenu invoke DeleteDC,hDcBack invoke DeleteObject,hBmpBack invoke DestroyWindow,hWinMain invoke PostQuitMessage,NULL ret _Quit endp ;******************************************************************** end start
程序的分析和要点
创建窗口的时候,窗口风格为 WS_POPUP,所以创建的窗口没有标题栏,这样的窗口适合于设置成特殊形状的窗口
invoke CreateWindowEx,WS_EX_TOOLWINDOW,/ offset szClassName,NULL,/ WS_POPUP or WS_SYSMENU,/ 0,0,1,1,/ NULL,NULL,hInstance,NULL
但是当窗口没有标题栏后,我们就无法用拖动标题栏的办法来移动窗口,如果让窗口一动不动呆在屏幕中间显然是不行的,这里有一个替代办法,我们可以响应按下鼠标左键的消息,在 WM_LBUTTONDOWN 消息中想窗口发送 WM_NCLBUTTONDOWN (非客户区鼠标按下消息) 位置在 HTCAPTION 来模拟鼠标按在标题栏中来实现移动的功能。
.elseif eax == WM_LBUTTONDOWN invoke UpdateWindow,hWnd ;即时刷新 invoke ReleaseCapture invoke SendMessage,hWnd,WM_NCLBUTTONDOWN,HTCAPTION,0
>> Win32汇编教程之一:Win32汇编的环境和基础 | >> Win32汇编教程二 |
>> Win32汇编教程三 | >> Win32汇编教程四 |
>> Win32汇编教程五 | >> Win32汇编教程六 |
>> Win32汇编教程七 | >> Win32汇编教程八 |
>> Win32汇编教程十 | |
>> Win32汇编教程十一 | >> Win32汇编教程十二 |
>> Win32汇编教程十三 | >> Win32ASM经验点滴 |
>> 汇编语言的艺术(组合语言的艺术)--基本认 | >> 汇编语言的艺术(组合语言的艺术)--基本认 |
>> 汇编语言的艺术(组合语言的艺术)--基本认 | >> 汇编语言的艺术(组合语言的艺术)--准备工 |
>> 汇编语言的艺术(组合语言的艺术)--观念正 | >> 如何建立汇编工作环境 |