实验4 Windows界面编程

来源:互联网 发布:网络接口层的功能 编辑:程序博客网 时间:2024/06/09 18:33
 

实验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.cgeneric.rcgeneric.rc是资源文件,在本例中只包含了一个菜单,含有2个菜单项。

运行generic程序,选择“HelpAbout…”后,窗口处理函数WndProc接收到一个WM_COMMAND消息,wParam参数等于IDM_ABOUT

2. 窗口的销毁

选择“FileExit”后,接收到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获取hInst1lpCmdLine1参数后,再调用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.asmgeneric.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_USERIDC_TEXT,后面一个编辑框的属性中含有WS_DISABLED,初始状态为“禁止”,显示为灰色,不允许在该编辑框中输入。

3个按钮PUSHBUTTON,其标识分别为IDC_USERIDC_TEXTIDOKIDOK按钮为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个窗口程序相结合,从菜单中选择FileChat后,再创建对话框。可以创建2个以上的对话框。

(3)      检查用户名是否重复,禁止一个用户在2个对话框中同时Login

(4)      在主窗口程序中定义用户名并保存,只有被定义的用户才被允许登录。

4.3 GDI编程

图形设备接口(Graphics Device InterfaceGDI)是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. 显示文本

在屏幕上显示字符串可以使用TextOutExtTextOutDrawText等函数,可以先调用其他函数设置前景色(SetTextColor)、背景色(SetBkColor)、字体(CreateFontIndirectSelectObject)等。

在示例程序中,收到鼠标移动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所示的两个子菜单ShapeColor

 

4-4  VC中编辑菜单

2.    选中的菜单前自动加标记,如图4-5所示:

 

4-5  菜单被复选时的状态

为复选菜单,需要调用CheckMenuItem函数。该函数所需的hMenu句柄在WinMain函数中由LoadMenu得到,并且hMenu句柄必须作为CreateWindowExA的一个参数。

3.    在窗口按下鼠标左键后,移动鼠标就按照菜单选中的形状、颜色,画出直线、矩形、椭圆形,释放左键后,绘图结束。

提示:程序中可以设置2个变量pt1pt2。鼠标左键按下时,当前鼠标位置记录在pt1pt2中,开始追踪鼠标移动。鼠标移动时,处于追踪状态下,则将前鼠标位置记录在pt2中。鼠标左键释放时,不再追踪鼠标移动。相关的消息为WM_LBUTTONDOWNWM_LBUTTONUPWM_MOUSEMOVE

在处理WM_PAINT消息时,根据选中的图形类型,调用MoveToExLineTo画出直线;调用Rectangle画出矩形;调用Ellipse画出椭圆。图形的颜色需要调用CreatePen创建画笔,并选择到设备描述表中。

需要更新窗口中的图形时,调用InvalidateRect(hWnd,NULL,TRUE),使窗口的显示区域变为无效,生成WM_PAINT消息。

4.    可以进一步扩展作图程序的功能:显示鼠标位置、画点、增加更多种类的图形和颜色、在屏幕上同时显示多个已完成的图形、图形叠加顺序调整、将结果保存在文件中、从文件中装入上次结果等等。