实验4 Windows界面编程
在图形用户界面(GUI, Graphic User Interface)中,用户与计算机通过图形图像以及文本进行交互。在Window 系统中,GUI程序显示出特定的窗口、图标、按钮、对话框等对象,而用户通过鼠标或键盘控制、操作这些对象。
4.1 简单的窗口程序
1. 窗口的创建
为了创建一个窗口,需要执行以下4步:
(1) 定义一个WNDCLASSEX结构体,描述与“窗口类”紧密相关的属性,主要包括窗口对象的名称、窗口处理函数等;
(2) 调用RegisterClassEx函数注册“窗口类”;
(3) 调用CreateWindowEx函数创建一个窗口,需要指定“窗口类”的名称、窗口标题等。窗口创建成功后,返回一个HWND句柄指针,指向被创建的窗口对象;
(4) 执行一个负责处理消息队列的循环,形式为:
while(GetMessageA(&msg,0,0,0))
{
TranslateMessage(&msg);
DispatchMessageA(&msg);
}
窗口处理函数是一个回调函数,Windows会将所有窗口相关的消息发送给它来处理。窗口处理函数的原型为:
LRESULT CALLBACK WndProc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);
在窗口处理函数中,需要编程处理一部分消息,而对于其他的消息则需要调用DefWindowProc函数交给Windows系统处理。
在VC中打开generic.dsw,其中包含2个主要的源程序:generic.c和generic.rc。generic.rc是资源文件,在本例中只包含了一个菜单,含有2个菜单项。
运行generic程序,选择“Help”→“About…”后,窗口处理函数WndProc接收到一个WM_COMMAND消息,wParam参数等于IDM_ABOUT。
2. 窗口的销毁
选择“File”→“Exit”后,接收到WM_COMMAND消息,wParam参数等于IDM_EXIT。调用SendMessageA函数发送WM_CLOSE 消息到这个窗口,之后,系统产生WM_DESTROY 消息,收到这个消息后,再调用PostQuitMessage函数,该函数发送一个WM_QUIT消息到线程消息队列,WinMain中的GetMessageA函数从队列中读取到这个消息后,返回值为0,退出消息循环,程序结束。
3. 创建、销毁窗口的汇编程序
如下是与generic.c相对应的汇编程序。执行结果如图4-1所示。
图4-1 一个简单的窗口程序
它的执行入口点不再是WinMain,而是_start。_start获取hInst1、lpCmdLine1参数后,再调用WinMain。
;程序清单: generic.asm(窗口的创建、删除)
.386p
.model flat,stdcall
include windows.inc
include user32.inc
include kernel32.inc
include gdi32.inc
includelib user32.lib
includelib kernel32.lib
includelib gdi32.lib
include resource.inc
.stack 4096
.data
WindowClass byte 'GENERIC',0
WindowTitle byte 'Generic',0
AboutText byte 'Generic Version 1.00',0ah,
'2007.12.16 (ASM)',0
AboutTiltle byte 'About',0
hInst1 DWORD 0
lpCmdLine1 LPSTR 0
.code
WinMain PROC hInst:HINSTANCE,hPrevInst:HINSTANCE,
lpCmdLine:LPSTR,nShowCmd:DWORD
local wcex:WNDCLASSEX
local hWnd:HWND
local msg:MSG
.IF !hPrevInst
mov wcex.cbSize,SIZEOF WNDCLASSEX
mov wcex.style,CS_HREDRAW or CS_VREDRAW
mov wcex.cbClsExtra,0
mov wcex.cbWndExtra,0
mov wcex.lpfnWndProc,OFFSET WndProc
mov eax,hInst
mov wcex.hInstance,eax
invoke LoadIconA,hInst,IDI_APPLICATION
mov wcex.hIcon,eax
invoke LoadCursorA,0,IDC_ARROW
mov wcex.hCursor,eax
mov wcex.hbrBackground,COLOR_WINDOW+1
mov wcex.lpszMenuName,IDR_MAINMENU and 0000ffffh
mov wcex.lpszClassName,OFFSET WindowClass
invoke LoadIconA,hInst,IDI_APPLICATION
mov wcex.hIconSm,eax
invoke RegisterClassExA,ADDR wcex
.IF !eax
mov eax,FALSE
ret
.ENDIF
.ENDIF
invoke CreateWindowExA,0,ADDR WindowClass,ADDR WindowTitle,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,CW_USEDEFAULT,
CW_USEDEFAULT,CW_USEDEFAULT,
0,0,hInst,NULL
mov hWnd,eax
.IF !eax
mov eax,FALSE
ret
.ENDIF
invoke ShowWindow,hWnd,nShowCmd
invoke UpdateWindow,hWnd
.WHILE TRUE
invoke GetMessageA,ADDR msg,0,0,0
.BREAK .IF !eax
invoke TranslateMessage,ADDR msg
invoke DispatchMessageA,ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain ENDP
WndProc PROC hWnd:HWND,wMsg:UINT,wParam:DWORD,lParam:DWORD
local hDC:HDC
local ps:PAINTSTRUCT
.IF wMsg==WM_COMMAND
mov eax,wParam
.IF ax==IDM_EXIT
invoke SendMessageA,hWnd,WM_CLOSE,0,0
mov eax,0
ret
.ELSEIF ax==IDM_ABOUT
invoke MessageBoxA,hWnd,
OFFSET AboutText,OFFSET AboutTiltle,MB_OK
mov eax,0
ret
.ELSE
invoke DefWindowProcA,hWnd,wMsg,wParam,lParam
ret
.ENDIF
.ELSEIF wMsg==WM_PAINT
invoke BeginPaint,hWnd,ADDR ps
mov hDC,eax
invoke EndPaint,hWnd,ADDR ps
mov eax,0
ret
.ELSEIF wMsg==WM_DESTROY
invoke PostQuitMessage,0
mov eax,0
ret
.ELSE
invoke DefWindowProcA,hWnd,wMsg,wParam,lParam
ret
.ENDIF
mov eax,0ffffffffh
ret
WndProc ENDP
_start:
invoke GetModuleHandleA,NULL
mov hInst1,eax
invoke GetCommandLineA
mov lpCmdLine1,eax
invoke WinMain,hInst1,NULL,lpCmdLine1,SW_SHOWDEFAULT
invoke ExitProcess,eax
end _start
除了generic.asm之外,还需要其他一些文件,它们的作用如下:
generic.rc ; 资源文件。可以用VC打开它,进行编辑修改
resource.h ; 定义generic.rc所需的数字常量,如IDM_EXIT,IDM_ABOUT。
resource.inc ; 定义generic.asm所需的数字常量,必须与resource.h一致。
用Visual C++对资源文件generic.rc 进行修改后,resource.h会自动更新,resource.h采用C语言格式,必须将被改动或者增加的部分转换为汇编格式,加入到resource.inc中。
首先执行c:/asm/bin中的asmvars.bat,设定编译环境。
再执行makegen.bat,编译generic.asm和generic.rc,连接生成generic.exe。
ml /c /coff /Cp generic.asm
rc generic.rc
link /subsystem:windows /entry:_start generic.obj generic.res
4.2 对话框及子窗口控件
一个对话框中包括了多个的子窗口控件,例如:编辑框、列表框、滚动条、复选框、单选框、按钮等等。在对话框中,这些子窗口能够自行处理与用户的交互,而程序通过Windows API获得用户输入的数据、或者将数据放入到子窗口控件中。
1. 程序与子窗口控件之间的通讯
对话框和菜单一样被定义成一种资源,在资源文件.rc中定义,可以用Visual C++来编辑。每一个子窗口控件都有一个唯一标识。
在Visual C++中生成如图4-2所示的对话框。保存后,生成的资源文件generic2.rc中包括了8个子窗口控件,每一个控件后面有4个数字,表示控件在对话框中的位置和大小。例如“9,14,44,8”,表示该控件的坐标为(9,14),宽度为44,高度为8。
LTEXT "UserName:",IDC_STATIC,9,14,44,8
EDITTEXT IDC_USER,53,13,53,12
PUSHBUTTON "Login(&L)",IDC_LOGIN,115,12,45,14,WS_DISABLED
PUSHBUTTON "LogOut(&X)",IDC_LOGOUT,167,12,45,14,WS_DISABLED
LISTBOX IDC_INFO,0,34,212,106,LBS_SORT | WS_VSCROLL
LTEXT "Input: ",IDC_STATIC,12,149,22,8
EDITTEXT IDC_TEXT,44,147,112,14,ES_AUTOHSCROLL | WS_DISABLED
DEFPUSHBUTTON "Send(&S)",IDOK,167,147,45,14,WS_DISABLED
图4-2 对话框及其子窗口控件
其中有2个静态框LTEXT,其标识为IDC_STATIC,它的作用是在对话框的固定位置显示一些字符串。
2个编辑框EDITTEXT,其标识分别为IDC_USER、IDC_TEXT,后面一个编辑框的属性中含有WS_DISABLED,初始状态为“禁止”,显示为灰色,不允许在该编辑框中输入。
3个按钮PUSHBUTTON,其标识分别为IDC_USER、IDC_TEXT、IDOK。IDOK按钮为DEFPUSHBUTTON,在对话框中按回车键就相当于按下了这个按钮。这些按钮的初始状态为“禁止”,不能被操作。
1个列表框LISTBOX,其标识分别为IDC_INFO。列表框中可以显示多个字符串,每个字符串占一行。
2. 子窗口控件的允许与禁止
在输入了用户名后,程序将“允许”Login按钮,需要首先调用GetDlgItem(hWinMain, IDC_LOGIN)获得Login按钮的窗口句柄,窗口句柄保存在eax中。再调用EnableWindow(eax, TRUE)“允许”该按钮。EnableWindow(eax, FALSE)则“禁止”该按钮。
invoke GetDlgItem,hWinMain,IDC_LOGIN
invoke EnableWindow,eax,TRUE
对编辑框等其他控件的允许、禁止与此类似。
3. 程序与子窗口控件之间的通讯
为获取用户在子窗口控件中的输入,可以向该控件发送消息,要求它将输入内容传送到程序指定的缓冲区,例如:
invoke GetDlgItemText,hWinMain,IDC_USER,addr szUserName,sizeof szUserName
向IDC_USER控件发送一个消息,将用户在编辑框中输入的字符串拷贝到szUserName字符串中,最大长度为sizeof szUserName。
还可以向控件发送消息,例如:
SendDlgItemMessage,hWinMain,IDC_INFO,LB_INSERTSTRING,0,addr @szBuffer
向列表框IDC_INFO发送LB_INSERTSTRING消息,将字符串szBuffer加入到列表框的最前面(指定位置为0)。
4. 对话框的回调函数
和窗口回调函数一样,对话框的回调函数也会接收到一些消息,进行特别处理。消息经过处理后就返回TRUE,否则返回FALSE。
主程序中调用DialogBoxParam函数创建对话框。需要指定对话框的标识DLG_MAIN、对话框的回调函数等。
创建对话框后,回调函数收到WM_INITDIALOG消息,在处理该消息时完成该对话框的初始化操作。程序第1次被执行时,它将对话框标题设为“Chat 1”,第2次执行时,对话框标题设为“Chat 2”。
;程序清单: generic2.asm(对话框及窗口控件)
.386
.model flat, stdcall
option casemap :none ; case sensitive
include windows.inc
include user32.inc
include kernel32.inc
includelib user32.lib
includelib kernel32.lib
ICO_MAIN equ 1000
DLG_MAIN equ 2000
IDC_USER equ 2001
IDC_LOGIN equ 2002
IDC_LOGOUT equ 2003
IDC_INFO equ 2004
IDC_TEXT equ 2005
.data
hInstance dword ?
hWinMain dword ?
bLogin dword 0
szUserName byte 12 dup (?)
szText byte 256 dup (?)
szMyTitle byte 20 dup (?)
szOtherTitle byte 20 dup (?)
szFmt byte '[%s]: %s',0
szChat1 byte 'Chat 1',0
szChat2 byte 'Chat 2',0
hwndOther dword ?
.code
_ProcDlgMain proc uses ebx edi esi hWnd,wMsg,wParam,lParam
local @szBuffer[512]:byte
mov eax,wMsg
.if eax == WM_INITDIALOG
push hWnd
pop hWinMain
invoke FindWindow,NULL,addr szChat1
; 是否已经存在标题为"Chat 1"的窗口?
.if eax == NULL
; 如果不存在,设定本对话框的标题为"Chat 1"
invoke lstrcpy,addr szMyTitle,addr szChat1
invoke lstrcpy,addr szOtherTitle,addr szChat2
.else
; 如果已存在,设定本对话框的标题为"Chat 2"
invoke lstrcpy,addr szMyTitle,addr szChat2
invoke lstrcpy,addr szOtherTitle,addr szChat1
.endif
invoke SetWindowText,hWinMain,addr szMyTitle
; 设定本对话框的图标
invoke LoadIcon,hInstance,ICO_MAIN
invoke SendMessage,hWnd,WM_SETICON,ICON_BIG,eax
; 用户名编辑框允许最多输入11个字符
invoke SendDlgItemMessage,hWinMain,IDC_USER,
EM_SETLIMITTEXT,11,0
; 发送文本编辑框允许最多输入250个字符
invoke SendDlgItemMessage,hWinMain,IDC_TEXT,
EM_SETLIMITTEXT,250,0
.elseif eax == WM_COMMAND
mov eax,wParam
; 输入用户名后,允许"Login"按钮
.if ax == IDC_USER
invoke GetDlgItemText,hWinMain,IDC_USER,
addr szUserName,sizeof szUserName
invoke GetDlgItem,hWinMain,IDC_LOGIN
.if szUserName && (bLogin == 0)
invoke EnableWindow,eax,TRUE
.else
invoke EnableWindow,eax,FALSE
.endif
; 输入聊天语句后,允许"Send"按钮
.elseif ax == IDC_TEXT
invoke GetDlgItemText,hWinMain,IDC_TEXT,
addr szText,sizeof szText
invoke GetDlgItem,hWinMain,IDOK
.if szText
invoke EnableWindow,eax,TRUE
.else
invoke EnableWindow,eax,FALSE
.endif
.elseif ax == IDC_LOGIN
mov bLogin, 1
; Login后,禁止Login按钮, 允许Logout按钮、发送文本编辑框
invoke GetDlgItem,hWinMain,IDC_LOGIN
invoke EnableWindow,eax,FALSE
invoke GetDlgItem,hWinMain,IDC_LOGOUT
invoke EnableWindow,eax,TRUE
invoke GetDlgItem,hWinMain,IDC_TEXT
invoke EnableWindow,eax,TRUE
.elseif ax == IDC_LOGOUT
mov bLogin, 0
; 允许Login按钮, 禁止Logout按钮、发送文本编辑框、发送按钮
invoke GetDlgItem,hWinMain,IDC_LOGIN
invoke EnableWindow,eax,TRUE
invoke GetDlgItem,hWinMain,IDC_LOGOUT
invoke EnableWindow,eax,FALSE
invoke GetDlgItem,hWinMain,IDC_TEXT
invoke EnableWindow,eax,FALSE
invoke GetDlgItem,hWinMain,IDOK
invoke EnableWindow,eax,FALSE
.elseif ax == IDOK
; 构造一个字符串,包括用户名和待发送的文本
invoke wsprintfA,addr @szBuffer,addr szFmt,
addr szUserName,addr szText
; 将字符串添加到列表框的第1行
invoke SendDlgItemMessage,hWinMain,IDC_INFO,
LB_INSERTSTRING,0,addr @szBuffer
; 查找到另一个对话框窗口
invoke FindWindow,NULL,addr szOtherTitle
mov hwndOther,eax
; 将字符串添加到另一个对话框的列表框的第1行
invoke SendDlgItemMessage,hwndOther,IDC_INFO,
LB_INSERTSTRING,0,addr @szBuffer
; 清除发送文本编辑框中的内容
invoke SetDlgItemText,hWinMain,IDC_TEXT,NULL
; 将输入焦点设置到发送文本编辑框上
invoke GetDlgItem,hWinMain,IDC_TEXT
invoke SetFocus,eax
.else
mov eax,FALSE
ret
.endif
.elseif eax == WM_CLOSE
invoke EndDialog,hWinMain,NULL
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
_ProcDlgMain endp
_start:
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke DialogBoxParam,eax,DLG_MAIN,NULL,offset _ProcDlgMain,0
invoke ExitProcess,NULL
end _start
可以对上述示例程序进行以下改进:
(1) 列表框不具有水平滚动功能。发送文本较长时,在列表框不能完全显示,试思考如何增加水平滚动功能。
(2) 与第1个窗口程序相结合,从菜单中选择File→Chat后,再创建对话框。可以创建2个以上的对话框。
(3) 检查用户名是否重复,禁止一个用户在2个对话框中同时Login。
(4) 在主窗口程序中定义用户名并保存,只有被定义的用户才被允许登录。
4.3 GDI编程
图形设备接口(Graphics Device Interface,GDI)是Windows的一个核心部件,它接受来自Windows应用程序的绘图请求,即GDI函数调用,将这些请求传给相应的设备驱动程序,在硬件上完成特定的输出,比如打印机、屏幕等等。
GDI可以完成三种类型的图形输出:
(1) 矢量输出:绘制线条和填充图形,包括点、直线、曲线、多边形、扇形和矩形等;
(2) 光栅图形输出:显示各种位图和图标等;
(3) 文本输出:显示字符串,可以设定字体、颜色等。
1. 设备描述表
要想在屏幕或者其它输出设备上输出图形或者文字,程序必须首先获得一个称为设备描述表(Device Context, DC)的对象的句柄,即HDC。
当应用程序接受到WM_PAINT消息后,就需要更新窗口中的显示区域。Windows为每个窗口都保留了一个绘图结构(PAINTSTRUCT),程序调用BeginPaint函数,获取一个设备描述表句柄HDC,以及绘图结构。之后,就可以利用这个HDC调用GDI函数完成绘图操作,操作结束后,调用Endpaint函数释放设备描述表句柄。即:
case WM_PAINT:
hDC=BeginPaint(hWnd,&ps);
……
EndPaint(hWnd,&ps);
return 0;
2. 画矩形
缺省情况下,设备描述表使用的坐标系将窗口左上角的坐标定为(0,0),以像素点为单位。例如,图4-3上的四个矩形,其左上角、右下角坐标分别为(20,10)、(90,220);(110,10)、(180,220); (200,10)、(270,220);(290,10)、(360,220)。
调用Rectangle函数在窗口中画出矩形,矩形内部的填充部分则由设备描述表中的当前画刷来决定。程序在窗口初始化时,创建了4个画刷,在画矩形时,选择这些画刷作为填充图像。
3. 显示位图
将BMP文件定义为一个资源,在窗口初始化时调用LoadBitmap函数取得位图句柄。按以下步骤显示位图:
(1) 调用CreateCompatibleDC函数创建屏幕DC的内存映像hMemDC。
(2) 调用SelectObject函数将位图画在“屏幕内存映像”上。
(3) 调用BitBlt函数将位图从hMemDC复制到hDC中。
使用hMemDC的目的是避免直接在hDC上画图所引起的图像抖动,这就是所谓的“双缓冲区”技术。
图4-3 GDI函数效果
4. 显示文本
在屏幕上显示字符串可以使用TextOut、ExtTextOut、DrawText等函数,可以先调用其他函数设置前景色(SetTextColor)、背景色(SetBkColor)、字体(CreateFontIndirect和SelectObject)等。
在示例程序中,收到鼠标移动WM_MOUSEMOVE消息后,将鼠标的x坐标和y坐标显示在窗口的左下角。
;程序清单: generic3.asm(GDI编程)
.386p
.model flat, stdcall
include windows.inc
include user32.inc
include kernel32.inc
include gdi32.inc
includelib user32.lib
includelib kernel32.lib
includelib gdi32.lib
IDB_BITMAP equ 103 ; 位图的整数宏,应该与resource.h一致
.data
WindowClass byte 'Generic3',0 ; 窗口类
WindowTitle byte 'GDI API sample',0 ; 窗口标题
szMousePos byte 30 dup (?) ; 要显示的字符串
nStrLen dword ? ; 要显示的字符串的长度
szFmt byte '鼠标位置(%d,%d) ',0 ; 字符串格式
hBitmap dword ? ; 位图句柄
hBrsh1 dword ? ; 画刷1句柄
hBrsh2 dword ? ; 画刷2句柄
hBrsh3 dword ? ; 画刷3句柄
hBrsh4 dword ? ; 画刷4句柄
hInst1 dword 0 ; 当前程序的实例句柄
lpCmdLine1 LPSTR 0 ; 命令行参数的指针
.code
; WinMain函数与generic.asm一致,除了2个地方不同:
; (1) mov wcex.lpszMenuName,0 窗口上不再有菜单
; (2) 100,100,720,300, 窗口的大小设定为(720,300)
WinMain PROC hInst:HINSTANCE,hPrevInst:HINSTANCE,
lpCmdLine:LPSTR,nShowCmd:DWORD
local wcex:WNDCLASSEX
local hWnd:HWND
local msg:MSG
.IF !hPrevInst
mov wcex.cbSize,SIZEOF WNDCLASSEX
mov wcex.style,CS_HREDRAW or CS_VREDRAW
mov wcex.cbClsExtra,0
mov wcex.cbWndExtra,0
mov wcex.lpfnWndProc,OFFSET WndProc
mov eax,hInst
mov wcex.hInstance,eax
invoke LoadIconA,hInst,IDI_APPLICATION
mov wcex.hIcon,eax
invoke LoadCursorA,0,IDC_ARROW
mov wcex.hCursor,eax
mov wcex.hbrBackground,COLOR_WINDOW+1
mov wcex.lpszMenuName,0
mov wcex.lpszClassName,OFFSET WindowClass
invoke LoadIconA,hInst,IDI_APPLICATION
mov wcex.hIconSm,eax
invoke RegisterClassExA,ADDR wcex
.IF !eax
mov eax,FALSE
ret
.ENDIF
.ENDIF
invoke CreateWindowExA,0,ADDR WindowClass,ADDR WindowTitle,
WS_OVERLAPPEDWINDOW,
100,100,720,300,
0,0,hInst,NULL
mov hWnd,eax
.IF !eax
mov eax,FALSE
ret
.ENDIF
invoke ShowWindow,hWnd,nShowCmd
invoke UpdateWindow,hWnd
.WHILE TRUE
invoke GetMessageA,ADDR msg,0,0,0
.BREAK .IF !eax
invoke TranslateMessage,ADDR msg
invoke DispatchMessageA,ADDR msg
.ENDW
mov eax,msg.wParam
ret
WinMain ENDP
WndProc proc uses ebx edi esi, hWnd:DWORD, wMsg:DWORD,
wParam:DWORD, lParam:DWORD
local hDC:HDC,hMemDC:HDC
local ps:PAINTSTRUCT
.IF wMsg==WM_CREATE
; 从资源中装入位图
invoke LoadBitmap,hInst1,IDB_BITMAP
mov hBitmap, eax
; 创建4个画刷
invoke CreateSolidBrush,0ff0000H
mov hBrsh1,eax
invoke CreateSolidBrush, 000ff00H
mov hBrsh2,eax
invoke CreateHatchBrush,HS_HORIZONTAL,00000ffH
mov hBrsh3,eax
invoke CreateHatchBrush,HS_DIAGCROSS,0ffff00h
mov hBrsh4,eax
; 返回0,表示WM_CREATE消息已被处理
mov eax,0
ret
.ELSEIF wMsg==WM_DESTROY
; 删除创建的画刷
invoke DeleteObject,hBrsh1
invoke DeleteObject,hBrsh2
invoke DeleteObject,hBrsh3
invoke DeleteObject,hBrsh4
; 删除位图资源
invoke DeleteObject,HBITMAP
; 发送一个WM_QUIT消息
invoke PostQuitMessage,0
mov eax,0
ret
.ELSEIF wMsg==WM_MOUSEMOVE
; lParam的高16位为y坐标,低16位为x坐标
mov eax,lParam
movzx ebx,ax
shr eax,16
invoke wsprintfA,offset szMousePos,offset szFmt,ebx,eax
mov nStrLen, eax
; 调用GetDC(hWnd)得到窗口区域的设备描述表句柄
invoke GetDC,hWnd
mov hDC,eax
; 在(20,230)处显示字符串
invoke TextOutA,hDC,20,230,offset szMousePos,nStrLen
; 释放设备描述表句柄
invoke ReleaseDC,hWnd,hDC
xor eax,eax
ret
.ELSEIF wMsg==WM_PAINT
; 得到显示区域的设备描述表句柄hDC
invoke BeginPaint,hWnd,addr ps
mov hDC,eax
; 选择已创建的画刷
invoke SelectObject,hDC,hBrsh1
; 画矩形
invoke Rectangle,hDC,20,10,90,220
invoke SelectObject,hDC,hBrsh2
invoke Rectangle,hDC,110,10,180,220
invoke SelectObject,hDC,hBrsh3
invoke Rectangle,hDC,200,10,270,220
invoke SelectObject,hDC,hBrsh4
invoke Rectangle,hDC,290,10,360,220
; 创建hDC的内存映像hMemDC
invoke CreateCompatibleDC,hDC
mov hMemDC,eax
; 将位图画在"屏幕内存映像"上
invoke SelectObject,hMemDC,hBitmap
; 将位图从hMemDC复制到hDC中
invoke BitBlt,hDC,400,10,290,210,hMemDC,0,0,SRCCOPY
; 删除hMemDC
invoke DeleteDC,hMemDC
; EndPaint()和BeginPaint()配对使用,使无效区域有效
invoke EndPaint,hWnd,addr ps
xor eax,eax
ret
.ELSE
invoke DefWindowProcA,hWnd,wMsg,wParam,lParam
ret
.ENDIF
mov eax,0ffffffffh
ret
WndProc ENDP
_start:
invoke GetModuleHandleA,NULL
mov hInst1,eax
invoke GetCommandLineA
mov lpCmdLine1,eax
invoke WinMain,hInst1,NULL,lpCmdLine1,SW_SHOWDEFAULT
invoke ExitProcess,eax
end _start
4.4 实验题:鼠标作图程序
操作鼠标,在窗口区域内画图。图形有直线、矩形、椭圆形3种,颜色有红、绿、蓝、黑4种,通过菜单选择图形和颜色。
要求:
1. 创建如图4-4所示的两个子菜单Shape和Color:
图4-4 在VC中编辑菜单
2. 选中的菜单前自动加标记,如图4-5所示:
图4-5 菜单被复选时的状态
为复选菜单,需要调用CheckMenuItem函数。该函数所需的hMenu句柄在WinMain函数中由LoadMenu得到,并且hMenu句柄必须作为CreateWindowExA的一个参数。
3. 在窗口按下鼠标左键后,移动鼠标就按照菜单选中的形状、颜色,画出直线、矩形、椭圆形,释放左键后,绘图结束。
提示:程序中可以设置2个变量pt1、pt2。鼠标左键按下时,当前鼠标位置记录在pt1和pt2中,开始追踪鼠标移动。鼠标移动时,处于追踪状态下,则将前鼠标位置记录在pt2中。鼠标左键释放时,不再追踪鼠标移动。相关的消息为WM_LBUTTONDOWN、WM_LBUTTONUP和WM_MOUSEMOVE。
在处理WM_PAINT消息时,根据选中的图形类型,调用MoveToEx、LineTo画出直线;调用Rectangle画出矩形;调用Ellipse画出椭圆。图形的颜色需要调用CreatePen创建画笔,并选择到设备描述表中。
需要更新窗口中的图形时,调用InvalidateRect(hWnd,NULL,TRUE),使窗口的显示区域变为无效,生成WM_PAINT消息。
4. 可以进一步扩展作图程序的功能:显示鼠标位置、画点、增加更多种类的图形和颜色、在屏幕上同时显示多个已完成的图形、图形叠加顺序调整、将结果保存在文件中、从文件中装入上次结果等等。