见招拆招《Windows程序设计》(十二) 第二部分
相关的例子:下载>>> 作者:Zoologist 于2008-12-15上传 

位图中的画刷

BRICKS系列的最后一个项目是BRICKS3,如程序14-5所示。乍看此程序,您可能会有这种感觉:程序代码哪里去了呢?

程序14-5 BRICKS3
        
BRICKS3.ASM
        
;MASMPlus 代码模板 - 普通的 Windows 程序代码

.386
.Model Flat, StdCall
Option Casemap :None

Include windows.inc
Include user32.inc
Include kernel32.inc
Include gdi32.inc

includelib gdi32.lib
IncludeLib user32.lib
IncludeLib kernel32.lib
include macro.asm

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.DATA
szAppName TCHAR "BRICKS3",0

.DATA?
hInstance HINSTANCE ?

.CODE
START:
invoke GetModuleHandle,NULL
invoke WinMain,eax,NULL,NULL,SW_SHOWDEFAULT
invoke ExitProcess,0
WinMain proc hInst:DWORD,hPrevInst:DWORD,szCmdLine:DWORD,iCmdShow:DWORD
LOCAL wndclass :WNDCLASSEX
LOCAL msg :MSG
LOCAL hWnd :HWND
LOCAL hBitmap:HBITMAP
LOCAL hBrush:HBRUSH

invoke LoadBitmap,hInst, CTEXT ("Bricks")
mov hBitmap,eax
invoke CreatePatternBrush,hBitmap
mov hBrush,eax
invoke DeleteObject,hBitmap

mov wndclass.cbSize,sizeof WNDCLASSEX
mov wndclass.style,CS_HREDRAW or CS_VREDRAW
mov wndclass.lpfnWndProc,offset WndProc

mov wndclass.cbClsExtra,0
mov wndclass.cbWndExtra,0

push hInst
pop wndclass.hInstance

invoke LoadIcon,NULL, IDI_INFORMATION
mov wndclass.hIcon,eax

invoke LoadCursor,NULL,IDC_ARROW
mov wndclass.hCursor,eax

mov eax,hBrush
mov wndclass.hbrBackground,eax

lea eax,szAppName
mov wndclass.lpszMenuName,eax
mov wndclass.lpszClassName,eax

mov wndclass.hIconSm,0

invoke RegisterClassEx, ADDR wndclass
.if (eax==0)
invoke MessageBox,NULL,CTXT("This program requires Windows NT!"),szAppName, MB_ICONERROR
ret
.endif


invoke CreateWindowEx,NULL,
ADDR szAppName, ;window class name
CTXT("CreatePatternBrush Demo"),
WS_OVERLAPPEDWINDOW, ;window style
CW_USEDEFAULT, ;initial x position
CW_USEDEFAULT, ;initial y position
CW_USEDEFAULT, ;initial x size
CW_USEDEFAULT, ;initial y size
NULL, ;parent window handle
NULL, ;window menu handle
hInst, ;program instance handle
NULL ;creation parameters
mov hWnd,eax

invoke ShowWindow,hWnd,iCmdShow
invoke UpdateWindow,hWnd

StartLoop:
invoke GetMessage,ADDR msg,NULL,0,0
cmp eax, 0
je ExitLoop
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
jmp StartLoop
ExitLoop:

mov eax,msg.wParam
ret
WinMain endp

WndProc proc hwnd:DWORD,uMsg:DWORD,wParam :DWORD,lParam :DWORD

.if uMsg == WM_DESTROY
invoke PostQuitMessage,NULL
xor eax,eax
ret
.endif

invoke DefWindowProc,hwnd,uMsg,wParam,lParam
ret
WndProc endp
END START

此程序与BRICKS1使用同一个BRICKS.BMP文件,而且窗口看上去也相同。

正如您看到的一样,窗口消息处理程序没有更多的内容。BRICKS3实际上使用砖块图案作为窗口类别背景画刷,它在WNDCLASS结构的hbrBackground字段中定义。

您现在可能猜想GDI画刷是很小的位图,通常是8个像素见方。如果将LOGBRUSH结构的lbStyle字段设定为BS_PATTERN,然后呼叫CreatePatternBrush或CreateBrushIndirect,您就可以在位图外面 来建立画刷了。此位图至少是宽高各8个像素。如果再大,Windows 98将只使用位图的左上角作为画刷。而Windows NT不受此限制,它会使用整个位图。

请记住,画刷和位图都是GDI对象,而且您应该在程序终止前删除您在程序中建立画刷和位图。如果您依据位图建立画刷,那么在用画刷画图时,Windows将复制位图位到画刷所绘制的区域内。呼 叫CreatePatternBrush(或者CreateBrushIndirect)之后,您可以立即删除位图而不会影响到画笔。类似地,您也可以删除画刷而不会影响到您选进的原始位图。注意,BRICKS3在建立画刷后删除了位 图,并在程序终止前删除了画刷。

绘制位图

在窗口中绘图时,我们已经将位图当成绘图来源使用过了。这要求先将位图选进内存设备内容,并呼叫BitBlt或者StretchBlt。您也可以用内存设备内容句柄作为所有实际呼叫的GDI函数中的第一 参数。内存设备内容的动作与实际的设备内容相同,除非显示平面是位图。

程序14-6所示的HELLOBIT程序展示了此项技术。程序在一个小位图上显示了字符串「Hello, world!」,然后从位图到程序显示区域执行BitBlt或StretchBlt(依照选择的菜单选项而定)。

程序14-6 HELLOBIT
        
HELLOBIT.C
        
;MASMPlus 代码模板 - 普通的 Windows 程序代码

.386
.Model Flat, StdCall
Option Casemap :None

Include windows.inc
Include user32.inc
Include kernel32.inc
Include gdi32.inc

includelib gdi32.lib
IncludeLib user32.lib
IncludeLib kernel32.lib
include macro.asm

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
IDM_BIG equ 40001
IDM_SMALL equ 40002

.DATA
szAppName TCHAR "HelloBit",0
iSize DWORD IDM_BIG
szTxt TCHAR " Hello, world! ",0

.DATA?
hInstance HINSTANCE ?
cxBitmap DWORD ?
cyBitmap DWORD ?
cxClient DWORD ?
cyClient DWORD ?
hBitmap HBITMAP ?
hdcMem HDC ?

.CODE
START:
invoke GetModuleHandle,NULL
invoke WinMain,eax,NULL,NULL,SW_SHOWDEFAULT
invoke ExitProcess,0
WinMain proc hInst:DWORD,hPrevInst:DWORD,szCmdLine:DWORD,iCmdShow:DWORD
LOCAL wndclass :WNDCLASSEX
LOCAL msg :MSG
LOCAL hWnd :HWND

mov wndclass.cbSize,sizeof WNDCLASSEX
mov wndclass.style,CS_HREDRAW or CS_VREDRAW
mov wndclass.lpfnWndProc,offset WndProc

mov wndclass.cbClsExtra,0
mov wndclass.cbWndExtra,0

push hInst
pop wndclass.hInstance

invoke LoadIcon,NULL, IDI_INFORMATION
mov wndclass.hIcon,eax

invoke LoadCursor,NULL,IDC_ARROW
mov wndclass.hCursor,eax

invoke GetStockObject,WHITE_BRUSH
mov wndclass.hbrBackground,EAX

lea eax,szAppName
mov wndclass.lpszMenuName,eax
mov wndclass.lpszClassName,eax

mov wndclass.hIconSm,0

invoke RegisterClassEx, ADDR wndclass
.if (eax==0)
invoke MessageBox,NULL,CTXT("This program requires Windows NT!"),szAppName, MB_ICONERROR
ret
.endif


invoke CreateWindowEx,NULL,
ADDR szAppName, ;window class name
CTXT("LoadBitmap Demo"),
WS_OVERLAPPEDWINDOW, ;window style
CW_USEDEFAULT, ;initial x position
CW_USEDEFAULT, ;initial y position
CW_USEDEFAULT, ;initial x size
CW_USEDEFAULT, ;initial y size
NULL, ;parent window handle
NULL, ;window menu handle
hInst, ;program instance handle
NULL ;creation parameters
mov hWnd,eax

invoke ShowWindow,hWnd,iCmdShow
invoke UpdateWindow,hWnd

StartLoop:
invoke GetMessage,ADDR msg,NULL,0,0
cmp eax, 0
je ExitLoop
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
jmp StartLoop
ExitLoop:

mov eax,msg.wParam
ret
WinMain endp

WndProc proc hwnd:DWORD,uMsg:DWORD,wParam :DWORD,lParam :DWORD
LOCAL hdc:HDC
LOCAL hMenu:HMENU
LOCAL x,y :DWORD
LOCAL ps :PAINTSTRUCT
LOCAL sizel :SIZEL

.if uMsg == WM_CREATE
invoke GetDC,hwnd
mov hdc,eax
invoke CreateCompatibleDC,hdc
mov hdcMem,eax
invoke lstrlen,addr szTxt
mov ebx,eax
invoke GetTextExtentPoint32,hdc,addr szTxt, ebx ,addr sizel

lea esi,sizel
mov eax,[esi] ;sizel.cx
mov cxBitmap,eax
mov eax,[esi+4] ;sizel.cy
mov cyBitmap,eax
invoke CreateCompatibleBitmap,hdc, cxBitmap, cyBitmap
mov hBitmap,eax
invoke ReleaseDC,hwnd, hdc
invoke SelectObject,hdcMem, hBitmap
invoke lstrlen,addr szTxt
mov ebx,eax
invoke TextOut,hdcMem, 0, 0, addr szTxt, ebx

xor eax,eax
ret
.elseif uMsg == WM_SIZE
mov eax,lParam
and eax,0FFFFh
mov cxClient,eax

mov eax,lParam
shr eax,16
mov cyClient,eax

xor eax,eax
ret

.elseif uMsg == WM_COMMAND
invoke GetMenu,hwnd
mov hMenu,eax
mov eax,wParam
and eax,0FFFFh
.if (eax==IDM_BIG)||(eax==IDM_SMALL)
invoke CheckMenuItem,hMenu, iSize, MF_UNCHECKED
mov eax,wParam
and eax,0FFFFh
mov iSize,eax
invoke CheckMenuItem,hMenu, iSize, MF_CHECKED
invoke InvalidateRect,hwnd, NULL, TRUE
.endif
xor eax,eax
ret
.elseif uMsg == WM_PAINT
invoke BeginPaint,hwnd,addr ps
mov hdc,eax

mov eax,iSize
.if eax==IDM_BIG
invoke StretchBlt,hdc, 0, 0, cxClient, cyClient, hdcMem, 0, 0, cxBitmap, cyBitmap, SRCCOPY
.elseif eax==IDM_SMALL
xor eax,eax
mov y,eax
loopy:
xor eax,eax
mov x,eax
loopx:
invoke BitBlt,hdc, x, y, cxBitmap, cyBitmap,hdcMem, 0, 0, SRCCOPY
mov eax,cxBitmap
add x,eax
mov eax,x
.if eax<cxClient
jmp loopx
.endif
mov eax,cyBitmap
add y,eax
mov eax,y
.if eax<cyClient
jmp loopy
.endif
.endif

invoke EndPaint,hwnd,addr ps
xor eax,eax
ret
.elseif uMsg == WM_DESTROY
invoke DeleteDC,hdcMem
invoke DeleteObject,hBitmap
invoke PostQuitMessage,NULL
xor eax,eax
ret
.endif

invoke DefWindowProc,hwnd,uMsg,wParam,lParam
ret
WndProc endp
END START

程序从呼叫GetTextExtentPoint32确定字符串的像素尺寸开始。这些尺寸将成为与视频显示兼容的位图尺寸。当此位图被选进内存设备内容(也与视频显示兼容)后,再呼叫TextOut将文字显示在 位图上。内存设备内容在程序执行期间保留。在处理WM_DESTROY信息期间,HELLOBIT删除了位图和内存设备内容。

HELLOBIT中的一条菜单选项允许您显示位图尺寸,此尺寸或者是显示区域中水平和垂直方向平铺的实际尺寸,或者是缩放成显示区域大小的尺寸,如图14-4所示。正与您所见到的一样,这不是显 示大尺寸字符的好方法!它只是小字体的放大版,并带有放大时产生的锯齿线。


 

图14-4 HELLOBIT的屏幕显示

您可能想知道一个程序,例如HELLOBIT,是否需要处理WM_DISPLAYCHANGE消息。只要使用者(或者其它应用程序)修改了视频显示大小或者颜色深度,应用程序就接收到此讯息。其中颜色深度的 改变会导致内存设备内容和视频设备内容不兼容。但这并不会发生,因为当显示模式修改后,Windows自动修改了内存设备内容的颜色分辨率。选进内存设备内容的位图仍然保持原样,但不会造成任何问 题。

阴影位图

在内存设备内容绘图(也就是位图)的技术是执行「阴影位图(shadow bitmap)」的关键。此位图包含窗口显示区域中显示的所有内容。这样,对WM_PAINT消息的处理就简化到简单的BitBlt。

阴影位图在绘画程序中最有用。程序14-7所示的SKETCH程序并不是一个最完美的绘画程序,但它是一个开始。

程序14-7  SKETCH
        
SKETCH.ASM


;MASMPlus 代码模板 - 普通的 Windows 程序代码

.386
.Model Flat, StdCall
Option Casemap :None

Include windows.inc
Include user32.inc
Include kernel32.inc
Include gdi32.inc

includelib gdi32.lib
IncludeLib user32.lib
IncludeLib kernel32.lib
include macro.asm

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.DATA
szAppName TCHAR "Sketch",0

.DATA?
fLeftButtonDown BOOL ?
fRightButtonDown BOOL ?
hBitmap HBITMAP ?
hdcMem HDC ?
cxBitmap DWORD ?
cyBitmap DWORD ?
cxClient DWORD ?
cyClient DWORD ?
xMouse DWORD ?
yMouse DWORD ?

.CODE
START:
invoke GetModuleHandle,NULL
invoke WinMain,eax,NULL,NULL,SW_SHOWDEFAULT
invoke ExitProcess,0
WinMain proc hInst:DWORD,hPrevInst:DWORD,szCmdLine:DWORD,iCmdShow:DWORD
LOCAL wndclass :WNDCLASSEX
LOCAL msg :MSG
LOCAL hWnd :HWND

mov wndclass.cbSize,sizeof WNDCLASSEX
mov wndclass.style,CS_HREDRAW or CS_VREDRAW
mov wndclass.lpfnWndProc,offset WndProc

mov wndclass.cbClsExtra,0
mov wndclass.cbWndExtra,0

push hInst
pop wndclass.hInstance

invoke LoadIcon,NULL, IDI_APPLICATION
mov wndclass.hIcon,eax

invoke LoadCursor,NULL,IDC_ARROW
mov wndclass.hCursor,eax

invoke GetStockObject,WHITE_BRUSH
mov wndclass.hbrBackground,EAX

mov wndclass.lpszMenuName,NULL
lea eax,szAppName
mov wndclass.lpszClassName,eax

mov wndclass.hIconSm,0

invoke RegisterClassEx, ADDR wndclass
.if (eax==0)
invoke MessageBox,NULL,CTXT("This program requires Windows NT!"),szAppName, MB_ICONERROR
ret
.endif

invoke CreateWindowEx,NULL,
ADDR szAppName, ;window class name
CTXT("Sketch"),
WS_OVERLAPPEDWINDOW, ;window style
CW_USEDEFAULT, ;initial x position
CW_USEDEFAULT, ;initial y position
CW_USEDEFAULT, ;initial x size
CW_USEDEFAULT, ;initial y size
NULL, ;parent window handle
NULL, ;window menu handle
hInst, ;program instance handle
NULL ;creation parameters
mov hWnd,eax
.if (hWnd == NULL)
invoke MessageBox,NULL, CTEXT ("Not enough memory to create bitmap!"),addr szAppName, MB_ICONERROR
xor eax,eax
ret
.endif
invoke ShowWindow,hWnd,iCmdShow
invoke UpdateWindow,hWnd

StartLoop:
invoke GetMessage,ADDR msg,NULL,0,0
cmp eax, 0
je ExitLoop
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
jmp StartLoop
ExitLoop:

mov eax,msg.wParam
ret
WinMain endp

GetLargestDisplayMode proc pcxBitmap:DWORD,pcyBitmap:DWORD
LOCAL devmode:DEVMODE
LOCAL iModeNum:DWORD

xor eax,eax
mov iModeNum,eax
mov esi,pcxBitmap
mov [esi],eax
mov esi,pcyBitmap
mov [esi],eax
invoke RtlZeroMemory,addr devmode,sizeof (DEVMODE)
mov ax,sizeof DEVMODE
mov devmode.dmSize,ax

@@:
invoke EnumDisplaySettings,NULL, iModeNum,addr devmode
.if eax==0
jmp @f
.endif
inc iModeNum

mov esi,pcxBitmap
mov eax,[esi]
.if eax<devmode.dmPelsWidth
mov eax,devmode.dmPelsWidth
.endif
mov esi,pcxBitmap
mov [esi],eax

mov esi,pcyBitmap
mov eax,[esi]
.if eax<devmode.dmPelsHeight
mov eax,devmode.dmPelsHeight
.endif
mov esi,pcyBitmap
mov [esi],eax

jmp @b
@@:
ret
GetLargestDisplayMode endp


WndProc proc hwnd:DWORD,uMsg:DWORD,wParam :DWORD,lParam :DWORD
LOCAL hdc:HDC
LOCAL ps :PAINTSTRUCT
LOCAL szBuffer[64 + MAX_PATH]:TCHAR

.if uMsg == WM_CREATE
invoke GetLargestDisplayMode,addr cxBitmap,addr cyBitmap
invoke GetDC,hwnd
mov hdc,eax

invoke wsprintf,addr szBuffer, CTXT ("%d??%d"),cxBitmap,cyBitmap ;这个地方是我设计用来debug的
invoke SetWindowText,hwnd,addr szBuffer

invoke CreateCompatibleBitmap,hdc, cxBitmap, cyBitmap
mov hBitmap,eax
invoke CreateCompatibleDC,hdc
mov hdcMem,eax
invoke ReleaseDC,hwnd, hdc

.if (hBitmap==0);no memory for bitmap
invoke DeleteDC,hdcMem
mov eax,-1
ret
.endif

invoke SelectObject,hdcMem, hBitmap
invoke PatBlt,hdcMem, 0, 0, cxBitmap, cyBitmap, WHITENESS


xor eax,eax
ret
.elseif uMsg == WM_SIZE
mov eax,lParam
and eax,0FFFFh
mov cxClient,eax

mov eax,lParam
shr eax,16
mov cyClient,eax

xor eax,eax
ret
.elseif uMsg == WM_LBUTTONDOWN

.if fRightButtonDown==0
invoke SetCapture,hwnd
.endif
invoke SetWindowText,hwnd,CTXT("DOWN")
mov eax,lParam
and eax,0FFFFh
mov xMouse,eax

mov eax,lParam
shr eax,16
mov yMouse,eax
mov fLeftButtonDown,TRUE
xor eax,eax
ret
.elseif uMsg ==WM_LBUTTONUP
.if fLeftButtonDown!=0
invoke SetCapture,NULL
.endif
invoke SetWindowText,hwnd,CTXT("UPUP")
mov fLeftButtonDown,FALSE
xor eax,eax
ret
.elseif uMsg == WM_RBUTTONDOWN
.if fLeftButtonDown==0
invoke SetCapture,hwnd
.endif
mov eax,lParam
and eax,0FFFFh
mov xMouse,eax

mov eax,lParam
shr eax,16
mov yMouse,eax
mov fRightButtonDown,TRUE
xor eax,eax
ret
.elseif uMsg == WM_RBUTTONUP
.if fRightButtonDown!=0
invoke SetCapture,NULL
.endif
mov fRightButtonDown,FALSE
xor eax,eax
ret
.elseif uMsg == WM_MOUSEMOVE
.if (fLeftButtonDown==0) && (fRightButtonDown==0)
xor eax,eax
ret
.endif
invoke GetDC,hwnd
mov hdc,eax

.if fLeftButtonDown==0
mov eax,WHITE_PEN
.else
mov eax,BLACK_PEN
.endif
invoke GetStockObject,eax
invoke SelectObject,hdc,eax

.if fLeftButtonDown==0
mov eax,WHITE_PEN
.else
mov eax,BLACK_PEN
.endif
invoke GetStockObject,eax
invoke SelectObject,hdcMem,eax
invoke MoveToEx,hdc, xMouse, yMouse, NULL
invoke MoveToEx,hdcMem, xMouse, yMouse, NULL
mov eax,lParam
and eax,0FFFFh
movsx ecx,ax
mov xMouse,ecx

mov eax,lParam
shr eax,16
movsx ecx,ax
mov yMouse,ecx
invoke LineTo,hdc, xMouse, yMouse
invoke LineTo,hdcMem, xMouse, yMouse
invoke ReleaseDC,hwnd, hdc

xor eax,eax
ret
.elseif uMsg == WM_PAINT
invoke BeginPaint,hwnd,addr ps
mov hdc,eax

invoke BitBlt,hdc, 0, 0, cxClient, cyClient, hdcMem, 0, 0, SRCCOPY

invoke EndPaint,hwnd,addr ps
xor eax,eax
ret
.elseif uMsg == WM_DESTROY
invoke DeleteDC,hdcMem
invoke DeleteObject,hBitmap
invoke PostQuitMessage,NULL
xor eax,eax
ret
.endif

invoke DefWindowProc,hwnd,uMsg,wParam,lParam
ret
WndProc endp
END START

         

要想在SKETCH中画线,请按下鼠标左键并拖动鼠标。要擦掉画过的东西(更确切地说,是画白线),请按下鼠标右键并拖动鼠标。要清空整个窗口,请…结束程序,然后重新加载,一切从头再来 。图14-5中显示的SKETCH程序图样表达了对频果公司的麦金塔计算机早期广告的敬意。


 

图14-5 SKETCH的屏幕显示

此阴影位图应多大?在本程序中,它应该大到能包含最大化窗口的整个显示区域。这一问题很容易根据GetSystemMetrics信息计算得出,但如果使用者修改了显示设定后再显示,进而扩大了最大 化时窗口的尺寸,这时将发生什么呢?SKETCH程序在EnumDisplaySettings函数的帮助下解决了此问题。此函数使用DEVMODE结构来传回全部有效视频显示模式的信息。第一次呼叫此函数时,应将 EnumDisplaySettings的第二参数设为0,以后每次呼叫此值都增加。EnumDisplaySettings传回FALSE时完成。

与此同时,SKETCH将建立一个阴影位图,它比目前视频显示模式的表面还多四倍,而且需要几兆字节的内存。由于如此,SKETCH将检查位图是否建立成功了,如果没有建立,就从WM_CREATE传回-1 ,以表示错误。

在WM_MOUSEMOVE消息处理期间,按下鼠标左键或者右键,并在内存设备内容和显示区域设备内容中画线时,SKETCH拦截鼠标。如果画线方式更复杂的话,您可能想在一个函数中实作,程序将呼叫 此函数两次-一次画在视频设备内容上,一次画在内存设备内容上。

下面是一个有趣的实验:使SKETCH窗口小于全画面尺寸。随着鼠标左键的按下,将鼠标拖出窗口的右下角。因为SKETCH拦截鼠标,所以它继续接收并处理WM_MOUSEMOVE消息。现在扩大窗口,您将 看到阴影位图包含您在SKETCH窗口外所画的内容。

在菜单中使用位图

您也可以用位图在菜单上显示选项。如果您联想起菜单中文件夹、剪贴簿和资源回收筒的图片,那么不要再想那些图片了。您应该考虑一下,菜单上显示位图对画图程序用途有多大,想象一下在 菜单中使用不同字体和字体大小、线宽、阴影图案以及颜色。

GRAFMENU是展示图形菜单选项的范例程序。此程序顶层菜单如图14-6所示。放大的字母来自于40×16像素的单色位图文件,该文件在Visual C++ Developer Studio建立。从菜单上选择「FONT」将弹出三个选择项-「Courier New」、「 Arial」和「Times New Roman」。它们是标准的Windows TrueType字体,并且每一个都按其相关的字体显示,如图14-7所示。这些位图在程序中用内存设备内容建立。


 

图14-6 GRAFMENU程序的顶层菜单


 

图14-7 GRAFMENU程序弹出的「FONT」菜单

最后,在拉下系统菜单时,您将获得一些「辅助」信息,用「HELP」表示了新使用者的在线求助项目(参见图14-8)。此64×64像素的单色位图是在Developer Studio中建立的。


 

图14-8 GRAFMENU程序系统菜单

GRAFMENU程序,包括四个Developer Studio中建立的位图,如程序14-8所示。

程序14-8 GRAFMENU
        
GRAFMENU.ASM
;MASMPlus 代码模板 - 普通的 Windows 程序代码

.386
.Model Flat, StdCall
Option Casemap :None

Include windows.inc
Include user32.inc
Include kernel32.inc
Include gdi32.inc

includelib gdi32.lib
IncludeLib user32.lib
IncludeLib kernel32.lib
include macro.asm

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
IDM_FONT_COUR equ 101
IDM_FONT_ARIAL equ 102
IDM_FONT_TIMES equ 103
IDM_HELP equ 104
IDM_EDIT_UNDO equ 40005
IDM_EDIT_CUT equ 40006
IDM_EDIT_COPY equ 40007
IDM_EDIT_PASTE equ 40008
IDM_EDIT_CLEAR equ 40009
IDM_FILE_NEW equ 40010
IDM_FILE_OPEN equ 40011
IDM_FILE_SAVE equ 40012
IDM_FILE_SAVE_AS equ 40013

.DATA
szAppName TCHAR "GrafMenu",0

Font1 db "Courier New",0
Font2 db "Arial",0
Font3 db "Times New Roman",0
szFaceName DWORD offset Font1
DWORD offset Font2
DWORD offset Font3

iCurrentFont DWORD IDM_FONT_COUR ;
.DATA?

.CODE
START:
invoke GetModuleHandle,NULL
invoke WinMain,eax,NULL,NULL,SW_SHOWDEFAULT
invoke ExitProcess,0
WinMain proc hInst:DWORD,hPrevInst:DWORD,szCmdLine:DWORD,iCmdShow:DWORD
LOCAL wndclass :WNDCLASSEX
LOCAL msg :MSG
LOCAL hWnd :HWND

mov wndclass.cbSize,sizeof WNDCLASSEX
mov wndclass.style,CS_HREDRAW or CS_VREDRAW
mov wndclass.lpfnWndProc,offset WndProc

mov wndclass.cbClsExtra,0
mov wndclass.cbWndExtra,0

push hInst
pop wndclass.hInstance

invoke LoadIcon,NULL, IDI_INFORMATION
mov wndclass.hIcon,eax

invoke LoadCursor,NULL,IDC_ARROW
mov wndclass.hCursor,eax

invoke GetStockObject,WHITE_BRUSH
mov wndclass.hbrBackground,EAX

lea eax,szAppName
mov wndclass.lpszMenuName,eax
mov wndclass.lpszClassName,eax

mov wndclass.hIconSm,0

invoke RegisterClassEx, ADDR wndclass
.if (eax==0)
invoke MessageBox,NULL,CTXT("This program requires Windows NT!"),szAppName, MB_ICONERROR
ret
.endif


invoke CreateWindowEx,NULL,
ADDR szAppName, ;window class name
CTXT("Bitmap Menu Demonstration"),
WS_OVERLAPPEDWINDOW, ;window style
CW_USEDEFAULT, ;initial x position
CW_USEDEFAULT, ;initial y position
CW_USEDEFAULT, ;initial x size
CW_USEDEFAULT, ;initial y size
NULL, ;parent window handle
NULL, ;window menu handle
hInst, ;program instance handle
NULL ;creation parameters
mov hWnd,eax

invoke ShowWindow,hWnd,iCmdShow
invoke UpdateWindow,hWnd

StartLoop:
invoke GetMessage,ADDR msg,NULL,0,0
cmp eax, 0
je ExitLoop
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
jmp StartLoop
ExitLoop:

mov eax,msg.wParam
ret
WinMain endp

CopyMemory proc Dest:DWORD, Source:DWORD, mlength:DWORD
cld ;Work upwards
mov esi, Source ;Source address
mov edi, Dest ;Destination address
mov ecx, mlength ;Get size in bytes
rep movsb ; repeat copy util all done
ret
CopyMemory endp

;StretchBitmap: Scales bitmap to display resolution
StretchBitmap proc hBitmap1:HBITMAP
LOCAL bm1:BITMAP
LOCAL bm2:BITMAP
LOCAL hBitmap2:HBITMAP
LOCAL hdc, hdcMem1, hdcMem2:HDC
LOCAL cxChar, cyChar:DWORD

;Get the width and height of a system font character
invoke GetDialogBaseUnits
mov cxChar,eax
and cxChar,0FFFFh
shr eax,16
mov cyChar,eax

;Create 2 memory DCs compatible with the display

invoke CreateIC,CTEXT ("DISPLAY"), NULL, NULL, NULL
mov hdc,eax

invoke CreateCompatibleDC,hdc
mov hdcMem1,eax

invoke CreateCompatibleDC,hdc
mov hdcMem2,eax

invoke DeleteDC,hdc

;Get the dimensions of the bitmap to be stretched

invoke GetObject,hBitmap1, sizeof (BITMAP), addr bm1

;Scale these dimensions based on the system font size
invoke CopyMemory,addr bm2,addr bm1,sizeof(BITMAP)

mov eax,cxChar
mov ecx,bm2.bmWidth
mul ecx
shr eax,2
mov bm2.bmWidth,eax

mov eax,cyChar
mov ecx,bm2.bmHeight
mul ecx
shr eax,3
mov bm2.bmHeight,eax

mov eax,bm2.bmWidth
add eax,15
shr eax,4
shl eax,2
mov bm2.bmWidthBytes,eax

;Create a new bitmap of larger size
invoke CreateBitmapIndirect,addr bm2
mov hBitmap2,eax

;Select the bitmaps in the memory DCs and do a StretchBlt
invoke SelectObject,hdcMem1, hBitmap1
invoke SelectObject,hdcMem2, hBitmap2

invoke StretchBlt,hdcMem2, 0, 0, bm2.bmWidth, \
bm2.bmHeight,hdcMem1, 0, 0, \
bm1.bmWidth, bm1.bmHeight, SRCCOPY

;Clean up
invoke DeleteDC,hdcMem1
invoke DeleteDC,hdcMem2
invoke DeleteObject,hBitmap1
mov eax,hBitmap2
ret
StretchBitmap endp

; AddHelpToSys: Adds bitmap Help item to system menu

AddHelpToSys proc hInstance:HINSTANCE, hwnd:HWND
LOCAL hBitmap:HBITMAP
LOCAL hMenu:HMENU
invoke GetSystemMenu,hwnd, FALSE
mov hMenu,eax
invoke LoadBitmap,hInstance, CTEXT ("BitmapHelp")
invoke StretchBitmap,eax
mov hBitmap,eax
invoke AppendMenu,hMenu, MF_SEPARATOR, 0, NULL
invoke AppendMenu,hMenu, MF_BITMAP, IDM_HELP,hBitmap
mov eax,eax
ret
AddHelpToSys endp

;GetBitmapFont: Creates bitmaps with font names
GetBitmapFont proc i:DWORD
LOCAL hBitmap:HBITMAP
LOCAL hdc, hdcMem:HDC
LOCAL hFont:HFONT
LOCAL sizeGB:SIZEL
LOCAL tm:TEXTMETRIC
invoke CreateIC,CTEXT ("DISPLAY"), NULL, NULL, NULL
mov hdc,eax
invoke GetTextMetrics,hdc,addr tm
invoke CreateCompatibleDC,hdc
mov hdcMem,eax

mov ebx,tm.tmHeight
shl ebx,1
mov eax,i
shl eax,2
mov eax,[szFaceName+eax]
invoke CreateFont,ebx, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,eax
mov hFont,eax

invoke SelectObject,hdcMem, hFont
mov hFont,eax

mov eax,i
shl eax,2
mov eax,[szFaceName+eax]
push eax
invoke lstrlen,eax
mov ecx,eax
pop ebx
invoke GetTextExtentPoint32,hdcMem, ebx,ecx, addr sizeGB

invoke CreateBitmap,sizeGB.x, sizeGB.y, 1, 1, NULL
mov hBitmap,eax
invoke SelectObject,hdcMem, hBitmap

mov eax,i
shl eax,2
mov eax,[szFaceName+eax]
push eax
invoke lstrlen,eax
pop ebx
invoke TextOut,hdcMem, 0, 0, ebx,eax

invoke SelectObject,hdcMem, hFont
invoke DeleteObject,eax
invoke DeleteDC,hdcMem
invoke DeleteDC,hdc

mov eax,hBitmap
ret
GetBitmapFont endp

;CreateMyMenu: Assembles menu from components
CreateMyMenu proc hInstance:HINSTANCE
LOCAL hBitmap:HBITMAP
LOCAL hMenu, hMenuPopup:HMENU
LOCAL i:DWORD

invoke CreateMenu
mov hMenu,eax

invoke LoadMenu,hInstance, CTEXT ("MenuFile")
mov hMenuPopup,eax

invoke LoadBitmap,hInstance, CTEXT ("BitmapFile")
invoke StretchBitmap,eax
mov hBitmap,eax


invoke AppendMenu,hMenu, MF_BITMAP or MF_POPUP, hMenuPopup,hBitmap

invoke LoadMenu,hInstance, CTEXT ("MenuEdit")
mov hMenuPopup,eax

invoke LoadBitmap,hInstance, CTEXT ("BitmapEdit")
invoke StretchBitmap,eax
mov hBitmap,eax

invoke AppendMenu,hMenu, MF_BITMAP or MF_POPUP,hMenuPopup,hBitmap
invoke CreateMenu
mov hMenuPopup,eax

mov i,0
@@:
invoke GetBitmapFont,i
mov hBitmap,eax
invoke AppendMenu,hMenuPopup, MF_BITMAP, IDM_FONT_COUR + i,hBitmap
inc i
cmp i,3
jNz @b
invoke LoadBitmap,hInstance, CTEXT ("BitmapFont")
invoke StretchBitmap,eax
mov hBitmap,eax

invoke AppendMenu,hMenu, MF_BITMAP or MF_POPUP, hMenuPopup,hBitmap

mov eax,hMenu
ret
CreateMyMenu endp

;DeleteAllBitmaps: Deletes all the bitmaps in the menu
DeleteAllBitmaps proc hwnd:HWND
LOCAL hMenu:HMENU
LOCAL i:DWORD
LOCAL mii:MENUITEMINFO
;MENUITEMINFOA STRUCT
; cbSize DWORD ?
; fMask DWORD ?
; fType DWORD ?
; fState DWORD ?
; wID DWORD ?
; hSubMenu DWORD ?
; hbmpChecked DWORD ?
; hbmpUnchecked DWORD ?
; dwItemData DWORD ?
; dwTypeData DWORD ?
; cch DWORD ?
;MENUITEMINFOA ENDS

mov eax,sizeof (MENUITEMINFO)
mov mii.cbSize,eax
mov mii.fMask,MIIM_SUBMENU or MIIM_TYPE
;Delete Help bitmap on system menu

invoke GetSystemMenu,hwnd, FALSE
mov hMenu,eax

invoke GetMenuItemInfo,hMenu, IDM_HELP, FALSE,addr mii

invoke DeleteObject,mii.dwTypeData
;Delete top-level menu bitmaps
invoke GetMenu,hwnd
mov hMenu,eax

mov i,0
@@:
invoke GetMenuItemInfo,hMenu, i, TRUE,addr mii
invoke DeleteObject,mii.dwTypeData
inc i
cmp i,3
jNz @b

;Delete bitmap items on Font menu
mov eax,mii.hSubMenu
mov hMenu,eax

mov i,0
@@:
invoke GetMenuItemInfo,hMenu, i, TRUE,addr mii
invoke DeleteObject,mii.dwTypeData
inc i
cmp i,3
jNz @b
ret
DeleteAllBitmaps endp

WndProc proc hwnd:DWORD,uMsg:DWORD,wParam :DWORD,lParam :DWORD
LOCAL hMenu:HMENU


.if uMsg == WM_CREATE
mov esi,lParam
mov eax,[esi+4]
invoke AddHelpToSys,eax,hwnd

mov esi,lParam
mov eax,[esi+4]
invoke CreateMyMenu,eax
mov hMenu,eax
invoke SetMenu,hwnd, hMenu
invoke CheckMenuItem,hMenu, iCurrentFont, MF_CHECKED
xor eax,eax
ret
.elseif uMsg == WM_SYSCOMMAND
mov eax,wParam
and eax,0FFFFh
.if eax==IDM_HELP
invoke MessageBox,hwnd,CTEXT ("Help not yet implemented!"),\
addr szAppName, MB_OK or MB_ICONEXCLAMATION
xor eax,eax
ret
.endif

.elseif uMsg == WM_COMMAND
mov eax,wParam
and eax,0FFFFh
.if (eax==IDM_FILE_NEW)||\
(eax==IDM_FILE_OPEN)||\
(eax==IDM_FILE_SAVE)||\
(eax==IDM_FILE_SAVE_AS)||\
(eax==IDM_EDIT_UNDO)||\
(eax==IDM_EDIT_CUT)||\
(eax==IDM_EDIT_COPY)||\
(eax==IDM_EDIT_PASTE)||\
(eax==IDM_EDIT_CLEAR)
invoke MessageBeep,0
xor eax,eax
ret
.elseif (eax==IDM_FONT_COUR)||\
(eax==IDM_FONT_ARIAL)||\
(eax==IDM_FONT_TIMES)
invoke GetMenu,hwnd
mov hMenu,eax
invoke CheckMenuItem,hMenu, iCurrentFont, MF_UNCHECKED
mov eax,wParam
and eax,0FFFFh
mov iCurrentFont,eax
invoke CheckMenuItem,hMenu, iCurrentFont, MF_CHECKED
xor eax,eax
ret
.endif

.elseif uMsg == WM_DESTROY
invoke DeleteAllBitmaps,hwnd
invoke PostQuitMessage,NULL
xor eax,eax
ret
.endif

invoke DefWindowProc,hwnd,uMsg,wParam,lParam
ret
WndProc endp
END START

         

EDITLABL.BMP
 

FILELABL.BMP
 

FONTLABL.BMP

 

BIGHELP.BMP
 

要将位图插入菜单,可以利用AppendMenu或InsertMenu。位图有两个来源:可以在Visual C++ Developer Studio建立位图,包括资源脚本中的位图文件,并在程序使用LoadBitmap时将位图资源加载到内存,然后呼叫AppendMenu或InsertMenu将位图附加到菜单上。但是用这种方法会有一些问题:位图 不适于所有显示模式的分辨率和纵横比;有时您需要缩放加载的位图以解决此问题。另一种方法是:在程序内部建立位图,并将它选进内存设备内容,画出来,然后再附加到菜单中。

GRAFMENU中的GetBitmapFont函数的参数为0、1或2,传回一个位图句柄。此位图包含字符串「Courier New」、「Arial」或「Times New Roman」,而且字体是各自对应的字体,大小是正常系统字体的两倍。让我们看看GetBitmapFont是怎么做的。(下面的程序代码与GRAFMENU.C文件中的有些不同。为了清楚起见,我用「Arial」字 体相应的值代替了引用szFaceName数组。)

第一步是用TEXTMETRIC结构来确定目前系统字体的大小,并建立一个与目前屏幕兼容的内存设备内容:

hdc = CreateIC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;
        
GetTextMetrics (hdc, &tm) ;
        
hdcMem = CreateCompatibleDC (hdc) ;
        

CreateFont函数建立了一种逻辑字体,该字体高是系统字体的两倍,而且逻辑名称为「Arial」:

hFont = CreateFont (2 * tm.tmHeight, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
        
                                                 TEXT ("Arial")) ;
        

从内存设备内容中选择该字体,然后储存内定字体句柄:

hFont = (HFONT) SelectObject (hdcMem, hFont) ;
        

现在,当我们向内存设备内容写一些文字时,Windows就会使用选进设备内容的TrueType Arial字体了。

但这个内存设备内容最初只有一个单像素单色设备平面。我们必须建立一个足够大的位图以容纳我们所要显示的文字。通过GetTextExtentPoint32函数,可以取得文字的大小,而用CreateBitmap 可以根据这些尺寸来建立位图:

GetTextExtentPoint32 (hdcMem, TEXT ("Arial"), 5, &size) ;
        
hBitmap = CreateBitmap (size.cx, size.cy, 1, 1, NULL) ;
        
SelectObject (hdcMem, hBitmap) ;
        

现在这个设备内容是一个单色的显示平面,大小也是严格的文字尺寸。我们现在要做的就是书写文字:

TextOut (hdcMem, 0, 0, TEXT ("Arial"), 5) ;
        

除了清除,所有的工作都完成了。要清除,我们可以用SelectObject将系统字体(带有句柄hFont)重新选进设备内容,然后删除SelectObject传回的前一个字体句柄,也就是Arial字体句柄:

DeleteObject (SelectObject (hdcMem, hFont)) ;
        

现在可以删除两个设备内容:

DeleteDC (hdcMem) ;
        
DeleteDC (hdc) ;
        

这样,我们就获得了一个位图,该位图上有Arial字体的字符串「Arial」。

当我们需要缩放字体以适应不同显示分辨率或纵横比时,内存设备内容也能解决问题。在GRAFMENU程序中,我建立了四个位图,这些位图只适用于系统字体高8像素、宽4像素的显示。对于其它尺 寸的系统字体,只能缩放位图。GRAFMENU中的StretchBitmap函数完成此功能。

第一步是获得显示的设备内容,然后取得系统字体的文字规格,接下来建立两个内存设备内容:

hdc = CreateIC (TEXT ("DISPLAY"), NULL, NULL, NULL) ;
        
GetTextMetrics (hdc, &tm) ;
        
hdcMem1 = CreateCompatibleDC (hdc) ;
        
hdcMem2 = CreateCompatibleDC (hdc) ;
        
DeleteDC (hdc) ;
        

传递给函数的位图句柄是hBitmap1。程序能用GetObject获得位图的大小:

GetObject (hBitmap1, sizeof (BITMAP), (PSTR) &bm1) ;
        

此操作将尺寸复制到BITMAP型态的结构bm1中。结构bm2等于结构bm1,然后根据系统字体大小来修改某些字段:

bm2 = bm1 ;
        
bm2.bmWidth                = (tm.tmAveCharWidth * bm2.bmWidth)  / 4 ;
        
bm2.bmHeight               = (tm.tmHeight       * bm2.bmHeight) / 8 ;
        
bm2.bmWidthBytes           = ((bm2.bmWidth + 15) / 16) * 2 ;
        

下一个位图带有句柄hBitmap2,可以根据动态的尺寸建立:

hBitmap2 = CreateBitmapIndirect (&bm2) ;
        

然后将这两个位图选进两个内存设备内容中:

SelectObject (hdcMem1, hBitmap1) ;
        
SelectObject (hdcMem2, hBitmap2) ;
        

我们想把第一个位图复制给第二个位图,并在此程序中进行拉伸。这包括StretchBlt呼叫:

StretchBlt (hdcMem2, 0, 0, bm2.bmWidth, bm2.bmHeight,
        
                                  hdcMem1, 0, 0, bm1.bmWidth, bm1.bmHeight, SRCCOPY) ;
        

现在第二幅图适当地缩放了,我们可将其用到菜单中。剩下的清除工作很简单:

DeleteDC (hdcMem1) ;
        
DeleteDC (hdcMem2) ;
        
DeleteObject (hBitmap1) ;
        

在建造菜单时,GRAFMENU中的CreateMyMenu函数呼叫了StretchBitmap和GetBitmapFont函数。GRAFMENU在资源文件中定义了两个菜单,在选择「File」和「Edit」选项时会弹出这两个菜单。函数 开始先取得一个空菜单的句柄:

hMenu = CreateMenu () ;
        

从资源文件加载「File」的弹出式菜单(包括四个选项:「New」、「Open」、「Save」和「Save as」):

hMenuPopup = LoadMenu (hInstance, TEXT ("MenuFile")) ;
        

从资源文件还加载了包含「FILE」的位图,并用StretchBitmap进行了拉伸:

hBitmapFile = StretchBitmap (LoadBitmap (hInstance, TEXT ("BitmapFile"))) ;
        

位图句柄和弹出式菜单句柄都是AppendMenu呼叫的参数:

AppendMenu (hMenu, MF_BITMAP | MF_POPUP, hMenuPopup, (PTSTR) (LONG)
        
hBitmapFile) ;
        

「Edit」菜单类似程序如下:

hMenuPopup = LoadMenu (hInstance, TEXT ("MenuEdit")) ;
        
hBitmapEdit = StretchBitmap (LoadBitmap (hInstance, TEXT ("BitmapEdit"))) ;
        
AppendMenu (hMenu, MF_BITMAP | MF_POPUP, hMenuPopup, (PTSTR)(LONG) hBitmapEdit) ;
        

呼叫GetBitmapFont函数可以构造这三种不同字体的弹出式菜单:

hMenuPopup = CreateMenu () ;
        
for (i = 0 ; i < 3 ; i++)
        
{
        
           hBitmapPopFont [i] = GetBitmapFont (i) ;
        
           AppendMenu (hMenuPopup, MF_BITMAP, IDM_FONT_COUR + i,
        
                        (PTSTR) (LONG) hMenuPopupFont [i]) ;
        
}
        

然后将弹出式菜单添加到菜单中:

hBitmapFont = StretchBitmap (LoadBitmap (hInstance, "BitmapFont")) ;
        
AppendMenu (hMenu, MF_BITMAP | MF_POPUP, hMenuPopup, (PTSTR) (LONG)                                  hBitmapFont) ;
        

WndProc通过呼叫SetMenu,完成了窗口菜单的建立工作。

GRAFMENU还改变了AddHelpToSys函数中的系统菜单。此函数首先获得一个系统菜单句柄:

hMenu = GetSystemMenu (hwnd, FALSE) ;
        

这将载入「HELP」位图,并将其拉伸到适当尺寸:

hBitmapHelp = StretchBitmap (LoadBitmap (hInstance, TEXT ("BitmapHelp"))) ;
        

这将给系统菜单添加一条分隔线和拉伸的位图:

AppendMenu (hMenu, MF_SEPARATOR, 0, NULL) ;
        
AppendMenu (hMenu, MF_BITMAP, IDM_HELP, (PTSTR)(LONG) hBitmapHelp) ;
        

GRAFMENU在退出之前呼叫一个函数来清除并删除所有位图。

下面是在菜单中使用位图的一些注意事项。

在顶层菜单中,Windows调整菜单列的高度以适应最高的位图。其它位图(或字符串)是根据菜单列的顶端对齐的。如果在顶层菜单中使用了位图,那么从使用常数SM_CYMENU的GetSystemMetrics得 到的菜单列大小将不再有效。

执行GRAFMENU期间可以看到:在弹出式菜单中,您可使用带有位图菜单项的勾选标记,但勾选标记是正常尺寸。如果不满意,您可以建立一个自订的勾选标记,并使用SetMenuItemBitmaps。

在菜单中使用非文字(或者使用非系统字体的文字)的另一种方法是「拥有者绘制」菜单。

菜单的键盘接口是另一个问题。当菜单含有文字时,Windows会自动添加键盘接口。要选择一个菜单项,可以使用Alt与字符串中的一个字母的组合键。而一旦在菜单中放置了位图,就删除了键盘 接口。即使位图表达了一定的含义,但Windows并不知道。

目前我们可以使用WM_MENUCHAR消息。当您按下Alt和与菜单项不相符的一个字符键的组合键时,Windows将向您的窗口消息处理程序发送一个WM_MENUCHAR消息。GRAFMENU需要截取WM_MENUCHAR消息 并检查wParam的值(即按键的ASCII码)。如果这个值对应一个菜单项,那么向Windows传回双字组:其中高字组为2,低字组是与该键相关的菜单项索引值。然后由Windows处理余下的事。

非矩形位图图像

位图都是矩形,但不需要都显示成矩形。例如,假定您有一个矩形位图图像,但您却想将它显示成椭圆形。

首先,这听起来很简单。您只需将图像加载Visual C++ Developer Studio或者Windows的「画图」程序,然后用白色的画笔将图像四周画上白色。这时将获得一幅椭圆形的图像,而椭圆的外面就 成了白色。只有当背景色为白色时此位图才能正确显示,如果在其它背景色上显示,您就会发现椭圆形的图像和背景之间有一个白色的矩形。这种效果不好。

有一种非常通用的技术可解决此类问题。这种技术包括「屏蔽(mask)」位图和一些位映像操作。屏蔽是一种单色位图,它与您要显示的矩形位图图像尺寸相同。每个屏蔽的像素都对应位图图像 的一个像素。屏蔽像素是1(白色),对应着位图像素显示;是0(黑色),则显示背景色。(或者屏蔽位图与此相反,这根据您使用的位映像操作而有一些相对应的变化。)

让我们看看BITMASK程序是如何实作这一技术的。如程序14-9所示。

程序14-9 BITMASK
        
BITMASK.ASM

;MASMPlus 代码模板 - 普通的 Windows 程序代码

.386
.Model Flat, StdCall
Option Casemap :None

Include windows.inc
Include user32.inc
Include kernel32.inc
Include gdi32.inc

includelib gdi32.lib
IncludeLib user32.lib
IncludeLib kernel32.lib
include macro.asm

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

.DATA
szAppName TCHAR "BitMask",0

.DATA?
hInstance HINSTANCE ?
hBitmapImag HBITMAP ?
hBitmapMask HBITMAP ?
cxClient DWORD ?
cyClient DWORD ?
cxBitmap DWORD ?
cyBitmap DWORD ?

.CODE
START:
invoke GetModuleHandle,NULL
invoke WinMain,eax,NULL,NULL,SW_SHOWDEFAULT
invoke ExitProcess,0
WinMain proc hInst:DWORD,hPrevInst:DWORD,szCmdLine:DWORD,iCmdShow:DWORD
LOCAL wndclass :WNDCLASSEX
LOCAL msg :MSG
LOCAL hWnd :HWND

mov wndclass.cbSize,sizeof WNDCLASSEX
mov wndclass.style,CS_HREDRAW or CS_VREDRAW
mov wndclass.lpfnWndProc,offset WndProc

mov wndclass.cbClsExtra,0
mov wndclass.cbWndExtra,0

push hInst
pop wndclass.hInstance

invoke LoadIcon,NULL, IDI_INFORMATION
mov wndclass.hIcon,eax

invoke LoadCursor,NULL,IDC_ARROW
mov wndclass.hCursor,eax

invoke GetStockObject,LTGRAY_BRUSH
mov wndclass.hbrBackground,eax

lea eax,szAppName
mov wndclass.lpszMenuName,eax
mov wndclass.lpszClassName,eax

mov wndclass.hIconSm,0

invoke RegisterClassEx, ADDR wndclass
.if (eax==0)
invoke MessageBox,NULL,CTXT("This program requires Windows NT!"),szAppName, MB_ICONERROR
ret
.endif


invoke CreateWindowEx,NULL,
ADDR szAppName, ;window class name
CTXT("Bitmap Masking Demo"),
WS_OVERLAPPEDWINDOW, ;window style
CW_USEDEFAULT, ;initial x position
CW_USEDEFAULT, ;initial y position
CW_USEDEFAULT, ;initial x size
CW_USEDEFAULT, ;initial y size
NULL, ;parent window handle
NULL, ;window menu handle
hInst, ;program instance handle
NULL ;creation parameters
mov hWnd,eax

invoke ShowWindow,hWnd,iCmdShow
invoke UpdateWindow,hWnd

StartLoop:
invoke GetMessage,ADDR msg,NULL,0,0
cmp eax, 0
je ExitLoop
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
jmp StartLoop
ExitLoop:

mov eax,msg.wParam
ret
WinMain endp

WndProc proc hwnd:DWORD,uMsg:DWORD,wParam :DWORD,lParam :DWORD
LOCAL bitmap:BITMAP
LOCAL hdc, hdcMemImag, hdcMemMask:HDC
LOCAL x, y:DWORD
LOCAL ps:PAINTSTRUCT

.if uMsg == WM_CREATE
mov esi,lParam
mov eax,[esi+4]
mov hInstance,eax

; Load the original image and get its size
invoke LoadBitmap,hInstance, CTEXT ("Matthew")
mov hBitmapImag,eax
invoke GetObject,hBitmapImag, sizeof (BITMAP),addr bitmap
mov eax,bitmap.bmWidth
mov cxBitmap,eax

mov eax,bitmap.bmHeight
mov cyBitmap,eax

;Select the original image into a memory DC
invoke CreateCompatibleDC,NULL
mov hdcMemImag,eax

invoke SelectObject,hdcMemImag, hBitmapImag

;Create the monochrome mask bitmap and memory DC
invoke CreateBitmap,cxBitmap, cyBitmap, 1, 1, NULL
mov hBitmapMask,eax

invoke CreateCompatibleDC,NULL
mov hdcMemMask,eax
invoke SelectObject,hdcMemMask, hBitmapMask

;Color the mask bitmap black with a white ellipse

invoke GetStockObject,BLACK_BRUSH
invoke SelectObject,hdcMemMask, eax
invoke Rectangle,hdcMemMask, 0, 0, cxBitmap, cyBitmap
invoke GetStockObject,WHITE_BRUSH
invoke SelectObject,hdcMemMask,eax

invoke Ellipse,hdcMemMask, 0, 0, cxBitmap, cyBitmap

;Mask the original image
invoke BitBlt,hdcMemImag, 0, 0, cxBitmap, cyBitmap, hdcMemMask, 0, 0, SRCAND
invoke DeleteDC,hdcMemImag
invoke DeleteDC,hdcMemMask
xor eax,eax
ret

.elseif uMsg == WM_SIZE
mov eax,lParam
and eax,0FFFFh
mov cxClient,eax

mov eax,lParam
shr eax,16
mov cyClient,eax

xor eax,eax
ret

.elseif uMsg == WM_PAINT
invoke BeginPaint,hwnd,addr ps
mov hdc,eax

;Select bitmaps into memory DCs
invoke CreateCompatibleDC,hdc
mov hdcMemImag,eax
invoke SelectObject,hdcMemImag, hBitmapImag
invoke CreateCompatibleDC,hdc
mov hdcMemMask,eax
invoke SelectObject,hdcMemMask, hBitmapMask
;Center image
mov eax,cxClient
sub eax,cxBitmap
shr eax,1
mov x,eax

mov eax,cyClient
sub eax,cyBitmap
shr eax,1
mov y,eax

;Do the bitblts

invoke BitBlt,hdc, x, y, cxBitmap, cyBitmap, hdcMemMask, 0, 0, 220326h
invoke BitBlt,hdc, x, y, cxBitmap, cyBitmap, hdcMemImag, 0, 0, SRCPAINT
invoke DeleteDC,hdcMemImag
invoke DeleteDC,hdcMemMask
invoke EndPaint,hwnd,addr ps

xor eax,eax
ret

.elseif uMsg == WM_DESTROY
invoke DeleteObject,hBitmapImag
invoke DeleteObject,hBitmapMask

invoke PostQuitMessage,NULL
xor eax,eax
ret
.endif

invoke DefWindowProc,hwnd,uMsg,wParam,lParam
ret
WndProc endp
END START

资源文件中的MATTHEW.BMP文件是原作者侄子的一幅黑白数字照片,宽200像素,高320像素,每像素8位。不过,另外制作个BITMASK只是因为此文件的内容是任何东西都可以。

注意,BITMASK将窗口背景设为亮灰色。这样就确保我们能正确地屏蔽位图,而不只是将其涂成白色。

下面让我们看一下WM_CREATE的处理程序:BITMASK用LoadBitmap函数获得hBitmapImag变量中原始图像的句柄。用GetObject函数可取得位图的宽度高度。然后将位图句柄选进句柄为hdcMemImag的 内存设备内容中。

程序建立的下一个单色位图与原来的图大小相同,其句柄储存在hBitmapMask,并选进句柄为hdcMemMask的内存设备内容中。在内存设备内容中,使用GDI函数,屏蔽位图就涂成了黑色背景和一个 白色的椭圆:

SelectObject (hdcMemMask, GetStockObject (BLACK_BRUSH)) ;
        
Rectangle (hdcMemMask, 0, 0, cxBitmap, cyBitmap) ;
        
SelectObject (hdcMemMask, GetStockObject (WHITE_BRUSH)) ;
        
Ellipse (hdcMemMask, 0, 0, cxBitmap, cyBitmap) ;
        

因为这是一个单色的位图,所以黑色区域的位是0,而白色区域的位是1。

然后BitBlt呼叫就按此屏蔽修改了原图像:

BitBlt (hdcMemImag, 0, 0, cxBitmap, cyBitmap,
        
                          hdcMemMask, 0, 0, SRCAND) ;
        

SRCAND位映像操作在来源位(屏蔽位图)和目的位(原图像)之间执行了位AND操作。只要屏蔽位图是白色,就显示目的;只要屏蔽是黑色,则目的就也是黑色。现在原图像中就形成了一个黑色包 围的椭圆区域。

现在让我们看一下WM_PAINT处理程序。此程序同时改变了选进内存设备内容中的图像位图和屏蔽位图。两次BitBlt呼叫完成了这个魔术,第一次在窗口上执行屏蔽位图的BitBlt:

BitBlt (hdc, x, y, cxBitmap, cyBitmap, hdcMemMask, 0, 0, 0x220326) ;
        

这里使用了一个没有名称的位映像操作。逻辑运算子是D & ~S。回忆来源-即屏蔽位图-是黑色(位值0)包围的一个白色(位值1)椭圆。位映像操作首先将来源反色,也就是改成白色包围的黑色椭圆。然后位操作在这个已转换的来源和目的(即窗口上 )之间执行位AND操作。当目的和位值1「AND」时保持不变;与位值0「AND」时,目的将变黑。因此,BitBlt操作将在窗口上画一个黑色的椭圆。

第二次的BitBlt呼叫则在窗口中绘制图像位图:

BitBlt (hdc, x, y, cxBitmap, cyBitmap, hdcMemImag, 0, 0, SRCPAINT) ;
        

位映像操作在来源和目的之间执行位「OR」操作。由于来源位图的外面是黑色,因此保持目的不变;而在椭圆区域内,目的是黑色,因此图像就原封不动地复制了过来。执行结果如图14-9所示。

注意事项:

有时您需要一个很复杂的屏蔽-例如,抹去原始图像的整个背景。您将需要在画图程序中手工建立然后将其储存到成文件。


 

图14-9 BITMASK的屏幕显示

如果正在为Windows NT/XP 编写类似的应用程序,那么您可以使用与MASKBIT程序类似的MaskBlt函数,而只需要更少的函数呼叫。Windows NT还包括另一个类似BitBlt的函数,Windows 98不支持该函数。此函数是PlgBlt(「平行四边形位块移动:parallelogram blt」)。这个函数可以对图像进行旋转或者倾斜位图图像。

最后,如果在您的机器上执行BITMASK程序,您就只会看见黑色、白色和两个灰色的阴影,这是因为您执行的显示模式是16色或256色。对于16色模式,显示效果无法改进,但在256色模式下可以 改变调色盘以显示灰阶。

简单的动画

小张的位图显示起来非常快,因此可以将位图和Windows定时器联合使用,来完成一些基本的动画。

现在开始这个弹球程序。

BOUNCE程序,如程序14-10所示,产生了一个在窗口显示区域弹来弹去的小球。该程序利用定时器来控制小球的行进速度。小球本身是一幅位图,程序首先通过建立位图来建立小球,将其选进内存 设备内容,然后呼叫一些简单的GDI函数。程序用BitBlt从一个内存设备内容将这个位图小球画到显示器上。

程序14-10 BOUNCE
        
BOUNCE.ASM
;MASMPlus 代码模板 - 普通的 Windows 程序代码

.386
.Model Flat, StdCall
Option Casemap :None

Include windows.inc
Include user32.inc
Include kernel32.inc
Include gdi32.inc

includelib gdi32.lib
IncludeLib user32.lib
IncludeLib kernel32.lib
include macro.asm

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
ID_TIMER equ 1
.DATA
szAppName TCHAR "Bounce",0
dbg DWORD 0
.DATA?
hInstance HINSTANCE ?

hBitmap HBITMAP ?
cxClient DWORD ?
cyClient DWORD ?
xCenter DWORD ?
yCenter DWORD ?
cxTotal DWORD ?
cyTotal DWORD ?
cxRadius DWORD ?
cyRadius DWORD ?
cxMove DWORD ?
cyMove DWORD ?
xPixel DWORD ?
yPixel DWORD ?

.CODE
START:
invoke GetModuleHandle,NULL
invoke WinMain,eax,NULL,NULL,SW_SHOWDEFAULT
invoke ExitProcess,0
WinMain proc hInst:DWORD,hPrevInst:DWORD,szCmdLine:DWORD,iCmdShow:DWORD
LOCAL wndclass :WNDCLASSEX
LOCAL msg :MSG
LOCAL hWnd :HWND

mov wndclass.cbSize,sizeof WNDCLASSEX
mov wndclass.style,CS_HREDRAW or CS_VREDRAW
mov wndclass.lpfnWndProc,offset WndProc

mov wndclass.cbClsExtra,0
mov wndclass.cbWndExtra,0

push hInst
pop wndclass.hInstance

invoke LoadIcon,NULL, IDI_INFORMATION
mov wndclass.hIcon,eax

invoke LoadCursor,NULL,IDC_ARROW
mov wndclass.hCursor,eax

invoke GetStockObject,WHITE_BRUSH
mov wndclass.hbrBackground,EAX

lea eax,szAppName
mov wndclass.lpszMenuName,eax
mov wndclass.lpszClassName,eax

mov wndclass.hIconSm,0

invoke RegisterClassEx, ADDR wndclass
.if (eax==0)
invoke MessageBox,NULL,CTXT("This program requires Windows NT!"),szAppName, MB_ICONERROR
ret
.endif


invoke CreateWindowEx,NULL,
ADDR szAppName, ;window class name
CTXT("Bouncing Ball"),
WS_OVERLAPPEDWINDOW, ;window style
CW_USEDEFAULT, ;initial x position
CW_USEDEFAULT, ;initial y position
CW_USEDEFAULT, ;initial x size
CW_USEDEFAULT, ;initial y size
NULL, ;parent window handle
NULL, ;window menu handle
hInst, ;program instance handle
NULL ;creation parameters
mov hWnd,eax

invoke ShowWindow,hWnd,iCmdShow
invoke UpdateWindow,hWnd

StartLoop:
invoke GetMessage,ADDR msg,NULL,0,0
cmp eax, 0
je ExitLoop
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
jmp StartLoop
ExitLoop:

mov eax,msg.wParam
ret
WinMain endp

WndProc proc hwnd:DWORD,uMsg:DWORD,wParam :DWORD,lParam :DWORD
LOCAL hBrush :HBRUSH
LOCAL hdc, hdcMem :HDC
LOCAL iScale :DWORD

.if uMsg == WM_CREATE
invoke GetDC,hwnd
mov hdc,eax

invoke GetDeviceCaps,hdc, ASPECTX
mov xPixel,eax

invoke GetDeviceCaps,hdc, ASPECTY
mov yPixel,eax

invoke ReleaseDC,hwnd, hdc
invoke SetTimer,hwnd, ID_TIMER, 30, NULL

xor eax,eax
ret
.elseif uMsg == WM_SIZE
mov eax,lParam
and eax,0FFFFh
mov cxClient,eax
shr eax,1
mov xCenter,eax

mov eax,lParam
shr eax,16
mov cyClient,eax
shr eax,1
mov yCenter,eax

mov eax,cxClient
mov ecx,xPixel
mul ecx
mov ebx,eax
mov eax,cyClient
mov ecx,yPixel
mul ecx
.if eax>ebx
mov eax,ebx
.endif
shr eax,4
mov iScale,eax

xor edx,edx
mov eax,iScale
mov ecx,xPixel
div ecx
mov cxRadius,eax

xor edx,edx
mov eax,iScale
mov ecx,yPixel
div ecx
mov cyRadius,eax

mov eax,cxRadius
shr eax,1
.if eax<1
mov eax,1
.endif
mov cxMove,eax

mov eax,cyRadius
shr eax,1
.if eax<1
mov eax,1
.endif
mov cyMove,eax

mov eax,cxRadius
add eax,cxMove
shl eax,1
mov cxTotal,eax

mov eax,cyRadius
add eax,cyMove
shl eax,1
mov cyTotal,eax

.if hBitmap!=0
invoke DeleteObject,hBitmap
.endif

invoke GetDC,hwnd
mov hdc,eax

invoke CreateCompatibleDC,hdc
mov hdcMem,eax

invoke CreateCompatibleBitmap,hdc, cxTotal, cyTotal
mov hBitmap,eax

invoke ReleaseDC,hwnd, hdc
invoke SelectObject,hdcMem, hBitmap
mov eax,cxTotal
inc eax
mov ebx,cyTotal
inc ebx
invoke Rectangle,hdcMem, -1, -1, eax,ebx

invoke CreateHatchBrush,HS_DIAGCROSS, 0
mov hBrush,eax

invoke SelectObject,hdcMem, hBrush
mov eax,0FF0FFh
invoke SetBkColor,hdcMem, eax
mov eax,cxTotal
sub eax,cxMove
mov ebx,cyTotal
sub ebx,cyMove
invoke Ellipse,hdcMem, cxMove, cyMove,eax,ebx
invoke DeleteDC,hdcMem
invoke DeleteObject,hBrush

xor eax,eax
ret

.elseif uMsg == WM_TIMER
.if (hBitmap==0)
jmp Go_Ret
.endif

invoke GetDC,hwnd
mov hdc,eax

invoke CreateCompatibleDC,hdc
mov hdcMem,eax
invoke SelectObject,hdcMem, hBitmap

mov eax,xCenter
mov ebx,cxTotal
shr ebx,1
sub eax,ebx

mov ecx,yCenter
mov edx,cyTotal
shr edx,1
sub ecx,edx

invoke BitBlt,hdc, eax,ecx, cxTotal, cyTotal,hdcMem, 0, 0, SRCCOPY
invoke ReleaseDC,hwnd, hdc
invoke DeleteDC,hdcMem

mov eax,cxMove
add xCenter,eax

mov eax,cyMove
add yCenter,eax

mov eax,xCenter
sub eax,cxRadius
cmp eax,0
jl @f

mov eax,xCenter
add eax,cxRadius
.if (eax >= cxClient)
@@:
mov eax,cxMove
neg eax
mov cxMove,eax
.endif

mov eax,yCenter
sub eax,cyRadius
cmp eax,0
jl @f

mov eax,yCenter
add eax,cyRadius
.if (eax >= cyClient)
@@:
mov eax,cyMove
neg eax
mov cyMove,eax
.endif

Go_Ret:
;invoke KillTimer,hwnd, ID_TIMER
xor eax,eax
ret

.elseif uMsg == WM_DESTROY
.if hBitmap!=0
invoke DeleteObject,hBitmap
.endif
invoke KillTimer,hwnd, ID_TIMER
invoke PostQuitMessage,NULL
xor eax,eax
ret
.endif

invoke DefWindowProc,hwnd,uMsg,wParam,lParam
ret
WndProc endp
END START

 

BOUNCE每次收到一个WM_SIZE消息时都重画小球。这就需要与视频显示器兼容的内存设备内容:

hdcMem = CreateCompatibleDC (hdc) ;
        

小球的直径设为窗口显示区域高度或宽度中较短者的十六分之一。不过,程序构造的位图却比小球大:从位图中心到位图四个边的距离是小球半径的1.5倍:

hBitmap = CreateCompatibleBitmap (hdc, cxTotal, cyTotal) ;
        

将位图选进内存设备内容后,整个位图背景设成白色:

Rectangle (hdcMem, -1, -1, xTotal + 1, yTotal + 1) ;
        

那些不固定的坐标使矩形边框在位图之外着色。一个对角线开口的画刷选进内存设备内容,并将小球画在位图的中央:

Ellipse (hdcMem, xMove, yMove, xTotal - xMove, yTotal - yMove) ;
        

当小球移动时,小球边界的空白会有效地删除前一时刻的小球图像。在另一个位置重画小球只需在BitBlt呼叫中使用SRCCOPY的ROP代码:

BitBlt (hdc, xCenter - cxTotal / 2, yCenter - cyTotal / 2, cxTotal, cyTotal,
        
                  hdcMem, 0, 0, SRCCOPY) ;
        

BOUNCE程序只是展示了在显示器上移动图像的最简单的方法。在一般情况下,这种方法并不能令人满意。如果您对动画感兴趣,那么除了在来源和目的之间执行或操作以外,您还应该研究其它的 ROP代码(例如SRCINVERT)。其它动画技术包括Windows调色盘(以及AnimatePalette函数)和CreateDIBSection函数。对于更高级的动画您只好放弃GDI而使用DirectX接口了。

窗口外的位图

SCRAMBLE程序,如程序14-11所示,编写非常粗糙,我本来不应该展示这个程序,但它示范了一些有趣的技术,而且在交换两个显示矩形内容的BitBlt操作的程序中,用内存设备内容作为临时储存 空间。

程序14-11  SCRAMBLE
        
SCRAMBLE.ASM
        
;MASMPlus 代码模板 - 普通的 Windows 程序代码

.386
.Model Flat, StdCall
Option Casemap :None

Include windows.inc
Include user32.inc
Include kernel32.inc
Include gdi32.inc
Include libc.inc

includelib gdi32.lib
IncludeLib user32.lib
IncludeLib kernel32.lib
IncludeLib Libc.lib
include macro.asm

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
NUM equ 300
.DATA
szAppName TCHAR "SCRAMBLE",0
.DATA?
hInstance HINSTANCE ?
iKeep DWORD NUM*4 dup(?)
.CODE
START:
invoke GetModuleHandle,NULL
invoke WinMain,eax,NULL,NULL,SW_SHOWDEFAULT
invoke ExitProcess,0
WinMain proc hInst:DWORD,hPrevInst:DWORD,szCmdLine:DWORD,iCmdShow:DWORD
LOCAL hdcScr, hdcMem:HDC
LOCAL cXX, cYY:DWORD
LOCAL hBitmap:HBITMAP
LOCAL hwnd :HWND
LOCAL i, j, x1, y1, x2, y2 :DWORD
LOCAL tmp:SYSTEMTIME

invoke GetDesktopWindow
mov hwnd,eax
invoke LockWindowUpdate,eax
.if eax!=0
invoke GetDCEx,hwnd, NULL, DCX_CACHE or DCX_LOCKWINDOWUPDATE
mov hdcScr,eax

invoke CreateCompatibleDC,hdcScr
mov hdcMem,eax
invoke GetSystemMetrics,SM_CXSCREEN
xor edx,edx
mov ecx,10
div ecx
mov cXX,eax

invoke GetSystemMetrics,SM_CYSCREEN
xor edx,edx
mov ecx,10
div ecx
mov cYY,eax

invoke CreateCompatibleBitmap,hdcScr, cXX, cYY
mov hBitmap,eax

invoke SelectObject,hdcMem, hBitmap
invoke GetSystemTime,addr tmp
movsx eax,tmp.wMilliseconds ;只取后面的DWORD
invoke srand,eax

mov i,0
Loopi:
mov j,0
Loopj:
.if i==0
mov eax,j
shl eax,4 ;数组和DWORD
lea esi,iKeep
add esi,eax

push esi
invoke rand
pop esi
xor edx,edx
mov ecx,10
div ecx
mov eax,edx
mov ecx,cXX
mul ecx
mov x1,eax
mov [esi],eax

push esi
invoke rand
pop esi
xor edx,edx
mov ecx,10
div ecx
mov eax,edx
mov ecx,cYY
mul ecx
mov y1,eax
mov [esi+4],eax

push esi
invoke rand
pop esi
xor edx,edx
mov ecx,10
div ecx
mov eax,edx
mov ecx,cXX
mul ecx
mov x2,eax
mov [esi+8],eax

push esi
invoke rand
pop esi
xor edx,edx
mov ecx,10
div ecx
mov eax,edx
mov ecx,cYY
mul ecx
mov y2,eax
mov [esi+12],eax
.else
mov eax,j
shl eax,4 ;数组和DWORD
lea esi,iKeep
add esi,(NUM-1)*16
sub esi,eax

mov eax,[esi]
mov x1,eax
mov eax,[esi+4]
mov y1,eax
mov eax,[esi+8]
mov x2,eax
mov eax,[esi+12]
mov y2,eax
.endif
invoke BitBlt,hdcMem, 0, 0, cXX, cYY, hdcScr, x1, y1, SRCCOPY
invoke BitBlt,hdcScr, x1, y1, cXX, cYY, hdcScr, x2, y2, SRCCOPY
invoke BitBlt,hdcScr, x2, y2, cXX, cYY, hdcMem, 0, 0, SRCCOPY
invoke Sleep,1
invoke MessageBeep,0
inc j
.if j!=NUM
jmp Loopj
.endif
inc i
.if i!=2
jmp Loopi
.endif
invoke DeleteDC,hdcMem
invoke ReleaseDC,hwnd, hdcScr
invoke DeleteObject,hBitmap

invoke LockWindowUpdate,NULL
.endif

ret
WinMain endp
END START


SCRAMBLE没有窗口消息处理程序。在WinMain中,它首先呼叫带有桌面窗口句柄的LockWindowUpdate。此函数暂时防止其它程序更新屏幕。然后SCRAMBLE通过呼叫带有参数DCX_LOCKWINDOWUPDATE的 GetDCEx来获得整个屏幕的设备内容。这样就只有SCRAMBLE可以更新屏幕了。

然后SCRAMBLE确定全屏幕的尺寸,并将长宽分别除以10。程序用这个尺寸(名称是cx和cy)来建立一个位图,并将该位图选进内存设备内容。

使用C语言的rand函数,SCRAMBLE计算出四个随机值(两个坐标点)作为cx和cy的倍数。程序透过三次呼叫BitBlt函数来交换两个矩形块中显示的内容。第一次将从第一个坐标点开始的矩形复制到 内存设备内容。第二次BitBlt将从第二坐标点开始的矩形复制到第一点开始的位置。第三次将内存设备内容中的矩形复制到第二个坐标点开始的区域。

此程序将有效地交换显示器上两个矩形中的内容。SCRAMBLE执行300次交换,这时的屏幕显示肯定是一团糟。但不用担心,因为SCRAMBLE记得是怎么把显示弄得这样一团糟的,接着在退出前它会按 相反的次序恢复原来的桌面显示(锁定屏幕前的画面)!

您也可以用内存设备内容将一个位图复制给另一个位图。例如,假定您要建立一个位图,该位图只包含另一个位图左上角的图形。如果原来的图像句柄为hBitmap,那么您可以将其尺寸复制到一个 BITMAP型态的结构中:

GetObject (hBitmap, sizeof (BITMAP), &bm) ;
        

然后建立一个未初始化的新位图,该位图的尺寸是原来图的1/4:

hBitmap2 = CreateBitmap (  bm.bmWidth / 2, bm.bmHeight / 2,
        
                        bm.bmPlanes, bm.bmBitsPixel, NULL) ;
        

现在建立两个内存设备内容,并将原来位图和新位图选分别进这两个内存设备内容:

hdcMem1 = CreateCompatibleDC (hdc) ;
        
hdcMem2 = CreateCompatibleDC (hdc) ;
        
SelectObject (hdcMem1, hBitmap) ;
        
SelectObject (hdcMem2, hBitmap2) ;
        

最后,将第一个位图的左上角复制给第二个:

BitBlt (    hdcMem2, 0, 0, bm.bmWidth / 2, bm.bmHeight / 2,
        
                          hdcMem1, 0, 0, SRCCOPY) ;
        

剩下的只是清除工作:

DeleteDC (hdcMem1) ;
        
DeleteDC (hdcMem2) ;
        
DeleteObject (hBitmap) ;
        

BLOWUP.C程序,如图14-21所示,也用窗口更新锁定来在程序窗口之外显示一个捕捉的矩形。此程序允许使用者用鼠标圈选屏幕上的矩形区域,然后BLOWUP将该区域的内容复制到位图。在WM_PAINT 消息处理期间,位图复制到程序的显示区域,必要时将拉伸或压缩。(参见程序14-12。)

程序14-12  BLOWUP
        
BLOWUP.ASM
;MASMPlus 代码模板 - 普通的 Windows 程序代码

.386
.Model Flat, StdCall
Option Casemap :None

Include windows.inc
Include user32.inc
Include kernel32.inc
Include gdi32.inc

includelib gdi32.lib
IncludeLib user32.lib
IncludeLib kernel32.lib
include macro.asm

WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD

IDM_EDIT_CUT equ 40001
IDM_EDIT_COPY equ 40002
IDM_EDIT_PASTE equ 40003
IDM_EDIT_DELETE equ 40004

.DATA
szAppName TCHAR "BLOWUP",0,0
ptBeg POINT <0,0>
ptEnd POINT <0,0>

bCapturing BOOL 0
bBlocking BOOL 0
.DATA?

hBitmap HBITMAP ?
hwndScr HWND ?


szBuffer TCHAR 100 dup(?)
.CODE
START:
invoke GetModuleHandle,NULL
invoke WinMain,eax,NULL,NULL,SW_SHOWDEFAULT
invoke ExitProcess,0
WinMain proc hInst:DWORD,hPrevInst:DWORD,szCmdLine:DWORD,iCmdShow:DWORD
LOCAL wndclass :WNDCLASSEX
LOCAL msg :MSG
LOCAL hWnd :HWND
LOCAL hAccel:HACCEL

mov wndclass.cbSize,sizeof WNDCLASSEX
mov wndclass.style,CS_HREDRAW or CS_VREDRAW
mov wndclass.lpfnWndProc,offset WndProc

mov wndclass.cbClsExtra,0
mov wndclass.cbWndExtra,0

push hInst
pop wndclass.hInstance

invoke LoadIcon,NULL, IDI_APPLICATION
mov wndclass.hIcon,eax

invoke LoadCursor,NULL,IDC_ARROW
mov wndclass.hCursor,eax

invoke GetStockObject,WHITE_BRUSH
mov wndclass.hbrBackground,EAX

lea eax,szAppName
mov wndclass.lpszMenuName,eax
mov wndclass.lpszClassName,eax

mov wndclass.hIconSm,0

invoke RegisterClassEx, ADDR wndclass
.if (eax==0)
invoke MessageBox,NULL,CTXT("This program requires Windows NT!"),szAppName, MB_ICONERROR
ret
.endif

invoke CreateWindowEx,NULL,
ADDR szAppName, ;window class name
CTXT("Blow-Up Mouse Demo"),
WS_OVERLAPPEDWINDOW, ;window style
CW_USEDEFAULT, ;initial x position
CW_USEDEFAULT, ;initial y position
CW_USEDEFAULT, ;initial x size
CW_USEDEFAULT, ;initial y size
NULL, ;parent window handle
NULL, ;window menu handle
hInst, ;program instance handle
NULL ;creation parameters
mov hWnd,eax
.if (hWnd == NULL)
invoke MessageBox,NULL, CTEXT ("Not enough memory to create bitmap!"),addr szAppName, MB_ICONERROR
xor eax,eax
ret
.endif
invoke ShowWindow,hWnd,iCmdShow
invoke UpdateWindow,hWnd

lea eax,szAppName
invoke LoadAccelerators,hInst,eax
mov hAccel,eax

StartLoop:
invoke GetMessage,ADDR msg,NULL,0,0
cmp eax, 0
je ExitLoop
invoke TranslateAccelerator,hInst, hAccel,addr msg
.if eax==0
invoke TranslateMessage, ADDR msg
invoke DispatchMessage, ADDR msg
.endif
jmp StartLoop
ExitLoop:

mov eax,msg.wParam
ret
WinMain endp

InvertBlock proc hwndScrIB:HWND,hwnd:HWND,ptBegAddr:DWORD,ptEndAddr:DWORD
LOCAL hdc:HDC
LOCAL aa,bb,cc,ff:DWORD

invoke GetDCEx,hwndScrIB, NULL, DCX_CACHE or DCX_LOCKWINDOWUPDATE
mov hdc,eax

invoke ClientToScreen,hwnd,ptBegAddr ;将用户坐标转换成屏幕坐标
invoke ClientToScreen,hwnd,ptEndAddr


mov esi,ptBegAddr
mov edi,ptEndAddr

push DSTINVERT
mov eax,[edi+4]
sub eax,[esi+4]
push eax
mov aa,eax

mov eax,[edi]
sub eax,[esi]
push eax
mov bb,eax

mov eax,[esi+4]
push eax
mov cc,eax

mov eax,[esi]
push eax
mov ff,eax
push hdc
call PatBlt

invoke wsprintf,addr szBuffer, CTXT ("[%d??%d] [%d??%d]"),aa,bb,cc,ff
invoke SetWindowText,hwnd,addr szBuffer

invoke ScreenToClient,hwnd,ptBegAddr
invoke ScreenToClient,hwnd,ptEndAddr

invoke ReleaseDC,hwndScrIB, hdc
ret
InvertBlock endp

CopyBitmap proc hBitmapSrc:HBITMAP
LOCAL bitmap:BITMAP
LOCAL hBitmapDst:HBITMAP
LOCAL hdcSrc, hdcDst:HDC
invoke GetObject,hBitmapSrc, sizeof (BITMAP),addr bitmap
invoke CreateBitmapIndirect,addr bitmap
mov hBitmapDst,eax

invoke CreateCompatibleDC,NULL
mov hdcSrc,eax
invoke CreateCompatibleDC,NULL
mov hdcDst,eax
invoke SelectObject,hdcSrc, hBitmapSrc
invoke SelectObject,hdcDst, hBitmapDst
invoke BitBlt,hdcDst, 0, 0, bitmap.bmWidth, bitmap.bmHeight,hdcSrc, 0, 0, SRCCOPY
invoke DeleteDC,hdcSrc
invoke DeleteDC,hdcDst
mov eax,hBitmapDst
ret
CopyBitmap endp

GetABS proc value:DWORD
cmp value,0
jl @f
mov eax,value
jmp GoEnd
@@:
mov eax,value
neg eax
GoEnd:
ret
GetABS endp

WndProc proc hwnd:DWORD,uMsg:DWORD,wParam :DWORD,lParam :DWORD
LOCAL bm:BITMAP
LOCAL hBitmapClip:HBITMAP
LOCAL hdc, hdcMem:HDC
LOCAL iEnable:DWORD
LOCAL ps:PAINTSTRUCT
LOCAL rect:RECT


.if uMsg == WM_LBUTTONDOWN ;按下左键
.if bCapturing==0 ;如果此时还没有开始抓图
invoke GetDesktopWindow ;则取得整个窗口的handle
mov hwndScr,eax
;invoke LockWindowUpdate,eax ;锁定指定窗口,禁止它更新
.if eax!=0
mov bCapturing,TRUE ;标记开始抓图
invoke SetCapture,hwnd ;在指定窗口里设置鼠标捕获
invoke LoadCursor,NULL, IDC_CROSS
invoke SetCursor,eax
.else
invoke MessageBeep,0
.endif
.endif
xor eax,eax
ret
.elseif uMsg == WM_RBUTTONDOWN ;按下右键

.if bCapturing!=0 ;如果已经开始抓图
mov bBlocking,TRUE ;设置抓图区域标志

mov eax,lParam
and eax,0FFFFh
mov ptBeg.x,eax

mov eax,lParam
shr eax,16
mov ptBeg.y,eax ;鼠标的坐标

mov esi,offset ptBeg
mov edi,offset ptEnd
mov eax,[esi]
mov [edi],eax
mov eax,[esi+4]
mov [edi+4],eax

invoke InvertBlock,hwndScr, hwnd,addr ptBeg,addr ptEnd

.endif

xor eax,eax
ret
.elseif uMsg == WM_MOUSEMOVE

.if bBlocking!=0
invoke InvertBlock,hwndScr, hwnd,addr ptBeg,addr ptEnd

mov eax,lParam
and eax,0FFFFh
mov ptEnd.x,eax

mov eax,lParam
shr eax,16
mov ptEnd.y,eax
invoke InvertBlock,hwndScr, hwnd,addr ptBeg,addr ptEnd

.endif
xor eax,eax
ret
.elseif (uMsg == WM_LBUTTONUP)||(uMsg == WM_RBUTTONUP)
.if bBlocking!=0
invoke InvertBlock,hwndScr, hwnd,addr ptBeg,addr ptEnd
mov eax,lParam
and eax,0FFFFh
mov ptEnd.x,eax

mov eax,lParam
shr eax,16
mov ptEnd.y,eax

.if hBitmap!=0
invoke DeleteObject,hBitmap
mov hBitmap,NULL
.endif
invoke GetDC,hwnd
mov hdc,eax
invoke CreateCompatibleDC,hdc
mov hdcMem,eax

mov esi,offset ptBeg
mov edi,offset ptEnd
mov eax,[edi+4]
sub eax,[esi+4]
invoke GetABS,eax
push eax
mov eax,[edi]
sub eax,[esi]
invoke GetABS,eax
push eax
push hdc
call CreateCompatibleBitmap
;invoke CreateCompatibleBitmap (hdc,abs (ptEnd.x - ptBeg.x),abs (ptEnd.y - ptBeg.y)) ;
mov hBitmap,eax
invoke SelectObject,hdcMem, hBitmap

mov esi,offset ptBeg
mov edi,offset ptEnd
push SRCCOPY
mov eax,[edi+4]
sub eax,[esi+4]
push eax
mov eax,[edi]
sub eax,[esi]
push eax
push [esi+4]
push [esi]
push hdc
mov eax,[edi+4]
sub eax,[esi+4]
invoke GetABS,eax
push eax
mov eax,[edi]
sub eax,[esi]
invoke GetABS,eax
push eax
push 0
push 0
push hdcMem
call StretchBlt
;invoke StretchBlt (hdcMem, 0, 0,abs (ptEnd.x - ptBeg.x),abs (ptEnd.y - ptBeg.y),
; hdc, ptBeg.x, ptBeg.y, ptEnd.x - ptBeg.x,ptEnd.y - ptBeg.y, SRCCOPY) ;
invoke DeleteDC,hdcMem
invoke ReleaseDC,hwnd, hdc
invoke InvalidateRect,hwnd, NULL, TRUE
.endif
.if (bBlocking!=0) || (bCapturing!=0)
mov eax,FALSE
mov bBlocking,eax
mov bCapturing,eax
invoke LoadCursor,NULL, IDC_ARROW
invoke SetCursor,eax
invoke ReleaseCapture
invoke LockWindowUpdate,NULL
.endif
xor eax,eax
ret
.elseif (uMsg == WM_INITMENUPOPUP)
invoke IsClipboardFormatAvailable,CF_BITMAP
.if eax==0
mov eax,MF_GRAYED
.else
mov eax,MF_ENABLED
.endif
invoke EnableMenuItem,wParam, IDM_EDIT_PASTE, iEnable
.if hBitmap==0
mov eax,MF_GRAYED
.else
mov eax,MF_ENABLED
.endif
mov iEnable,eax
invoke EnableMenuItem,wParam, IDM_EDIT_CUT, iEnable
invoke EnableMenuItem,wParam, IDM_EDIT_COPY, iEnable
invoke EnableMenuItem,wParam, IDM_EDIT_DELETE, iEnable

xor eax,eax
ret
.elseif (uMsg == WM_COMMAND)
mov eax,lParam
and eax,0FFFFh
.if (eax==IDM_EDIT_CUT)||(eax==IDM_EDIT_COPY)
.if hBitmap!=0
invoke CopyBitmap,hBitmap
mov hBitmapClip,eax
invoke OpenClipboard,hwnd
invoke EmptyClipboard
invoke SetClipboardData,CF_BITMAP, hBitmapClip
.endif
mov eax,lParam
and eax,0FFFFh
.if eax==IDM_EDIT_COPY
xor eax,eax
ret
.endif
jmp @f ;fall through for IDM_EDIT_CUT
.elseif eax==IDM_EDIT_DELETE
@@:
.if hBitmap!=0
invoke DeleteObject,hBitmap
mov hBitmap,NULL
.endif
invoke InvalidateRect,hwnd, NULL, TRUE
xor eax,eax
ret
.elseif eax== IDM_EDIT_PASTE
.if (hBitmap!=0)
invoke DeleteObject,hBitmap
mov hBitmap,NULL
.endif
invoke OpenClipboard,hwnd
invoke GetClipboardData,CF_BITMAP
mov hBitmapClip,eax
.if (hBitmapClip!=0)
invoke CopyBitmap,hBitmapClip
mov hBitmap,eax
.endif
invoke CloseClipboard
invoke InvalidateRect,hwnd, NULL, TRUE
xor eax,eax
ret
.endif
.elseif (uMsg == WM_PAINT)
invoke BeginPaint,hwnd,addr ps
mov hdc,eax
.if (hBitmap!=0)
invoke GetClientRect,hwnd,addr rect
invoke CreateCompatibleDC,hdc
mov hdcMem,eax
invoke SelectObject,hdcMem, hBitmap
invoke GetObject,hBitmap, sizeof (BITMAP), addr bm
invoke SetStretchBltMode,hdc, COLORONCOLOR
invoke StretchBlt,hdc, 0, 0, rect.right, rect.bottom,hdcMem, 0, 0, bm.bmWidth, bm.bmHeight, SRCCOPY
invoke DeleteDC,hdcMem
.endif
invoke EndPaint,hwnd,addr ps
xor eax,eax
ret
.elseif (uMsg == WM_DESTROY)
.if (hBitmap!=0)
invoke DeleteObject,hBitmap
.endif
invoke PostQuitMessage,0
xor eax,eax
ret
.endif

invoke DefWindowProc,hwnd,uMsg,wParam,lParam
ret
WndProc endp
END START

 


 

图14-10 BLOWUP显示的一个范例

由于鼠标拦截的限制,所以开始使用BLOWUP时会有些困难,需要逐渐适应。下面是使用本程序的方法:

  1. 在BLOWUP显示区域按下鼠标左键不放,鼠标指针会变成「+」字型。
  2. 继续按住左键,将鼠标移到屏幕上的任何其它位置。鼠标光标的位置就是您要圈选的矩形区域的左上角。
  3. 继续按住左键,按下鼠标右键,然后拖动鼠标到您要圈选的矩形区域的右下角。释放鼠标左键和右键。(释放鼠标左、右键次序无关紧要。)

鼠标光标恢复成箭头状,这时您圈选的矩形区域已复制到了BLOWUP的显示区域,并作了适当的压缩或拉伸变化。

如果您从右上角到左下角选取的话,BLOWUP将显示矩形区域的镜像。如果从左下到右上角选取,BLOWUP将显示颠倒的图像。如果从右上角至左上角选取,程序将综合两种效果。

BLOWUP还 包含将位图复制到剪贴簿,以及将剪贴簿中的位图复制到程序的处理功能。BLOWUP处理WM_INITMENUPOPUP消息来启用或禁用「Edit」菜单中的不同选项,并通过WM_COMMAND消息来处理这些菜单项。您应 该对这些程序代码的结构比较熟悉,因为它们与第十二章中的复制和粘贴文字项目的处理方式在本质上是一样的。

不过,对于位图,剪贴簿对象不是整体句柄而是位图句柄。当您使用CF_BITMAP时, GetClipboardData函数传回一个HBITMAP对象,而且SetClipboardData函数接收一个HBITMAP对象。如果您想将位图传送给剪贴簿又想保留副本以供程序本身使用,那么您必须复制位图。同样,如 果您从剪贴簿上粘贴了一幅位图,也应该做一个副本。BLOWUP中的CopyBitmap函数是通过取得现存位图的BITMAP结构,并在CreateBitmapIndirect函数中用这个结构建立一个新位图来完成此项操作的。( 变量名的后缀Src和Dst分别代表「来源」和「目的」。) 两个位图都被选进内存设备内容,而且通过呼叫BitBlt来复制位图内容。(另一种复制位的方法,可以先按位图大小配置一块内存,然后为来源位图呼叫GetBitmapBits,为目的位图呼叫 SetBitmapBits。)

我发现BLOWUP对于检查Windows及其应用程序中大量分散的小位图和图片非常有用。



<<<上一篇
欢迎访问AoGo汇编小站:http://www.aogosoft.com