Win32汇编教程九 复杂形状的窗口
--------------------------------------------------------------------------------
概述
在前面八篇的 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