Win32汇编教程九
综合篇(一)复杂形状的窗口

在这儿下载本节的所有源程序(74k)。

概述

    在前面八篇的 Win32asm 教程中,已经初步讲述了消息框、对话框、菜单、资源、GDI 等内容,基本上已经设计到了 Windows 界面的大部分内容,在继续新的 Windows 其他部分的内容如多线程、文件操作、内存操作之前,我先综合前面的内容并加上一些新内容,写上一篇综合篇。
本篇的例子程序是一个复杂形状的窗口,窗口的形状是根据位图自动计算得到的,这也就是在我编写的小闹钟中使用的技术(大家可以到我的软件发布中下载一个看看),由于以前在网上看到的有关特殊形状窗口的例子最多就是画一个圆形,或者几个方块和椭圆结合的形状,没有一篇文章指出如何画出如“唐老鸭”这样一个造型的窗口。本文使用的算法可以自动根据位图的形状计算窗口形状。
在源程序中,很多代码都是前面教程提到的,主要有以下部分:

Windows 里有专门的 API 来实现特殊形状的窗口,步骤是首先建立区域(Region),Region 可以合并,这样一来就可以用几个简单的区域合并出一个复杂的区域,建立、合并区域和设置窗口的 API 主要有以下几条:

本程序的方法是扫描位图的点,按行设置区域,然后合并到总的区域中。

源程序 - 汇编源文件

;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
;	是否包括调试代码
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
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




(C) Copyright by LuoYunBin's Win32 ASM Page,http://asm.yeah.net