子窗口控件
本节课程中,我们将讨论控件。这些控件作为是我们输入输出的设备。
理论:
Windows提供了几个预定义的窗口类供我们窗口使用。大多数时间内我们把它们用在对话框上,所以我们把它们叫做子窗口控件。子窗口控件会自己处理消息,并在自己状态发生改变时通知父窗口。这样大大减轻了我们编程的工作,我们应尽可能的利用它们。本课中我们把这些控件放在窗口中简化程序。但是大多数情况下子窗口控件都是放在对话框中的。我们示例中演示的子窗口控件包括: 按钮、下拉菜单、检查框、单选按钮、编辑框等。使用子窗口控件时,先调用CreateWindow函数或CreateWindowEx函数。这里由于windows已经注册了这些子控件,所以我们无需在注册。当然我们也不能改变它们的类名称。譬如你如果想产生一个按钮控件,则必须指令类名为“Button”。其他必须制定的参数还有父窗口的句柄和将要产生子窗口的控件ID。子窗口的控件ID号是用来标示子控件的,故也必须是唯一的。子窗口控件产生后,当状态发生改变时会像其父窗口发送消息。一般我们应在WM_CREATE消息中产生子控件。子控件向父窗口发送的消息是WM_COMMAND消息,并传递的wparam的低二位字节是包括控件的ID,消息号在wparam高两位字节,lparam中则是子控件的窗口句柄。各类控件有不同的消息代码集,详情参见Win32 API参考手册。父窗口也可以通过调用函数SendMessage向子控件发送消息,其中第一个参数是子控件的窗口句柄,第二个参数是要发送的消息号。附加的参数可以在wparam和lparam中传递,其实只要知道,其实只要知道某个窗口的句柄就可以用该函数发送消息相关消息。所以产生子窗口后必须处理WM_COMMAND消息以便可以接受到子控件的消息。
例子:
我们将产生一个窗口,在该窗口中有一个编辑框和一个按钮。当你按下按钮时,会弹出一个消息框显示你在编辑框中输入的内容。另外该应用程序还有四个菜单项。
1. say hello 把一段字符串输入到编辑框中。
2. Clear Edit Box 清除编辑框中的字符串。
3. Get Text 弹出对话框显示编辑空中的内容。
4. Exit 退出程序
代码:
format PE GUI 4.0
include 'win32ax.inc'
macro memmov [dst, src]
{
common
push [src]
pop [dst]
}
.data
;**************数据********************
szClassName db 'first Window',0
szWndName db 'My first program',0
szMenuName db 'MyMenu',0
szTestString db 'Wow! i am in an edit box now', 0
EditName db 'edit',0
ButtonName db 'button',0
ButtonText db 'My first button',0
szBuffer db 50 dup (?)
hInstanse rd 1
hIcon rd 1
hCursor rd 1
hWndow rd 1
lpCommand rd 1
hButton rd 1
hEdit rd 1
IDM_TEXT equ 1
IDM_HELLO equ 2
IDM_CLEAR equ 3
IDM_EXIT equ 4
BUTTONID = 5
EDITID = 6
.text
entry $
xor edi, edi
invoke GetModuleHandle, edi
or eax, eax
jz .fail
mov [hInstanse], eax
invoke GetCommandLine,edi
mov [lpCommand], eax
stdcall _WndMain, hInstanse, edi, [lpCommand],SW_SHOWDEFAULT
.fail:
invoke ExitProcess,NULL
proc _WndMain hInstance:DWORD, hPrevInstance:DWORD, lpCmdLine:DWORD, nCmdShow:DWORD
local @wc:WNDCLASSEX
local @Msg:MSG
local @hMenu:DWORD
invoke RtlZeroMemory, addr @wc, sizeof.WNDCLASSEX
invoke LoadIcon, NULL,IDI_ASTERISK
mov [hIcon], eax
invoke LoadCursor, NULL,IDC_ARROW
mov [hCursor], eax
mov [@wc.cbSize], sizeof.WNDCLASSEX
mov [@wc.style], CS_HREDRAW or CS_VREDRAW
mov [@wc.lpfnWndProc], _WndProc
mov [@wc.cbClsExtra], NULL
mov [@wc.cbWndExtra], NULL
memmov @wc.hInstance, hInstance
memmov @wc.hIcon, hIcon
memmov @wc.hCursor, hCursor
mov [@wc.hCursor], hCursor
mov [@wc.hbrBackground], COLOR_WINDOW + 1
mov [@wc.lpszMenuName], NULL
mov [@wc.lpszClassName], szClassName
invoke RegisterClassEx, addr @wc
invoke LoadMenu,[hInstanse], szMenuName
mov [@hMenu], eax
invoke CreateWindowEx, WS_EX_CLIENTEDGE, addr szClassName, szWndName, WS_OVERLAPPEDWINDOW,/
CW_USEDEFAULT, CW_USEDEFAULT, 300, 200,/
NULL, [@hMenu] , [hInstanse] , NULL
mov [hWndow], eax
invoke ShowWindow,[hWndow],SW_SHOWNORMAL
invoke UpdateWindow,[hWndow]
.GetMsg:
invoke GetMessage,addr @Msg,NULL, 0, 0
or eax, eax
je .QUIT
invoke TranslateMessage,addr @Msg
invoke DispatchMessage,addr @Msg
jmp .GetMsg
.QUIT:
mov eax, [@Msg.wParam]
ret
endp
proc _WndProc uses ebx esi edi, hWnd:DWORD, uMsg:DWORD, wParam:DWORD, lParam:DWORD
cmp [uMsg], WM_DESTROY
je finish
cmp [uMsg], WM_COMMAND
je .command
cmp [uMsg], WM_CREATE
je .create
invoke DefWindowProc,[hWnd],[uMsg],[wParam], [lParam]
ret
.create:
invoke CreateWindowEx,NULL, ButtonName, ButtonText, /
WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON, /
75, 70, 140, 25, [hWnd] , BUTTONID, [hInstanse], NULL
or eax, eax
je faild
mov [hButton], eax
invoke CreateWindowEx,WS_EX_CLIENTEDGE, EditName, NULL,/
WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT or ES_AUTOHSCROLL, /
50, 35, 200, 25, [hWnd], EDITID, [hInstanse], NULL
mov [hEdit], eax
invoke SetFocus, [hEdit]
jmp Myend
.command:
mov eax ,[wParam]
and eax, 0FFFFh
cmp eax, IDM_TEXT
je _text
cmp eax, IDM_HELLO
je _hello
cmp eax, IDM_CLEAR
je _clear
cmp eax, IDM_EXIT
je _exit
jmp Myend
_text:
invoke GetWindowText,[hEdit], szBuffer, 50
invoke MessageBox, NULL, szBuffer, 'test', MB_OK
jmp Myend
_hello:
invoke SetWindowText, [hEdit], szTestString
jmp Myend
_clear:
invoke SetWindowText, [hEdit], NULL
jmp Myend
_exit:
invoke DestroyWindow, [hWnd]
jmp Myend
faild:
invoke MessageBox,NULL, 'faild', 'test', MB_OK
jmp Myend
finish:
invoke PostQuitMessage, NULL
Myend:
xor eax, eax
ret
endp
.import
library kernel32, 'kernel32.dll',/
user32, 'user32.dll'
include 'api/kernel32.inc'
include 'api/user32.inc'
section '.rsrc' data readable resource from 'resourcename.RES'
分析:
.create:
invoke CreateWindowEx,NULL, ButtonName, ButtonText, /
WS_CHILD or WS_VISIBLE or BS_DEFPUSHBUTTON, /
75, 70, 140, 25, [hWnd] , BUTTONID, [hInstanse], NULL
or eax, eax
je faild
mov [hButton], eax
invoke CreateWindowEx,WS_EX_CLIENTEDGE, EditName, NULL,/
WS_CHILD or WS_VISIBLE or WS_BORDER or ES_LEFT or ES_AUTOHSCROLL, /
50, 35, 200, 25, [hWnd], EDITID, [hInstanse], NULL
mov [hEdit], eax
invoke SetFocus, [hEdit]
jmp Myend
我们在WM_CREATE消息中建立子窗口控件,在上面我也以及说了,我们一般都是在WM_CREATE消息中创建子窗口控件的。 我们通过CreatWindowEx函数来创建子窗口控件,因为这些子窗口控件都是windows预定义的类,这些类都是注册过的,且它们内部自己处理消息,当子窗口控件发生改变时向我们的父窗口发送WM_COMMADN消息。 创建子窗口控件没什么好讲的,我们在建立编辑框的时候给它附加了一个WS_EX_CLIENTEDGE外观,这使得看起来编辑下凹,具有立体感。每一个子控件的id都是预定义的,例如按钮的预定义类名是“button”,编辑框的预定义类名是“edit”。接下来的参数是窗口风格,除了通常的风格外,每一个控件也有自己的扩展风格,譬如按钮类的扩展风格前面加BS_,编辑框的类则是ES_。Win32 API参考中所有的风格描述,注意:您在CreateWindowsEx函数中本来要传递菜单句柄的地方传入子窗口空间的ID号不会有什么副作用,因为子窗口控件本身不能有菜单。产生控件后,我们保存它们的句柄,然后调用SetFocus把焦点设到编辑控件上以便用户立即可以输入。接下来的是如何处理控件发送的通知消息WM_COMMAND。
.command:
mov eax ,[wParam]
and eax, 0FFFFh
cmp eax, IDM_TEXT
je _text
cmp eax, IDM_HELLO
je _hello
cmp eax, IDM_CLEAR
je _clear
cmp eax, IDM_EXIT
je _exit
jmp Myend
_text:
invoke GetWindowText,[hEdit], szBuffer, 50
invoke MessageBox, NULL, szBuffer, 'test', MB_OK
jmp Myend
_hello:
invoke SetWindowText, [hEdit], szTestString
jmp Myend
_clear:
invoke SetWindowText, [hEdit], NULL
jmp Myend
_exit:
invoke DestroyWindow, [hWnd]
jmp Myend
faild:
invoke MessageBox,NULL, 'faild', 'test', MB_OK
jmp Myend
这里我们通过判断菜单项的ID号来进行处理。如果是选择say hello菜单,则通过调用SetWindowText设置编辑框一段字符串。 如果选择 Get Text 菜单则通过 GetWindowText函数获得编辑框中的内容并保存到缓冲区中。然后通过MessageBox显示缓冲区中的内容。其次如果选择的是Clear Edit Box消息,则调用SetWindowText设置编辑框的内容为NULL,(则此时编辑框的内容为空)。 如果选择exit菜单项,则调用DestroyWindow函数销毁窗口,从而结束程序。
cmp ax, BUTTONID
je _Button
_Button:
shr eax, 16
cmp eax, BN_CLICKED
jne Myend
invoke SendMessage,[hWnd] , WM_COMMAND, IDM_TEXT, NULL
jmp Myend
上面的片段是处理用户按钮事件的。他首先检查wParam的低字节看是否是按钮的ID 号,若是则检查高字节看发送的消息号是否BN_CLICKED,该消息是在按钮按下时发送的,如果一切都对,则转入处理该消息,我们可以从处理消息IDM_GETTEXT处复制全部的代码,但是更专业的办法是在发送一条IDM_GETTEXT消息让主窗口过程处理,这只要把传送的消息设置为WM_COMMAND,再把wParam的低字节中设置为IDM_GETTEXT即可。这样一来您的代码就简洁了许多,所以尽可能利用该技巧。最后,当然不是或有或无,必须在消息循环中调用函数TranslateMessage,因为您的应用程序需要在编辑框中输入可读的文字。如果省略了该函数,就不能在编辑框中输入任何东西。