Introduction to Controls
So what is a control? Good question. Before
Visual Basic (VB), it meant a child window created with one of
six predefined window classes. Creating a window with embedded
controls provides a "control panel", with the controls acting as
input and output channels for an application.
Nowadays, there are custom controls -- they are
created by non-MS programmers. Microsoft added the common
controls to Win95. And adding to that, VB (Visual Basic)
gave another meaning for control -- it's any
software module that provides a special "control" function (e.g.,
a timer).
We will ignore the VB meaning of control.
[ Back to Win32 ASM Page ]
Registering control classes
What Windows calls a control is implemented as a
window, so there is a window class associated with each control
type.
Six control classes are predefined by Windows. These window
classes are registered before an application starts. They are:
BUTTON, COMBOBOX, EDIT, LISTBOX, SCROLLBAR, and STATIC.
The common controls are registered by calling
InitCommonControls.
If you are using custom controls, whether it's yours or
someone else's, you will need to register their window classes by
calling RegisterWindowEx.
Creating child windows and controls
Child windows are created by calling CreateWindowEx with WS_CHILD
included in the style argument. They are usually created
as visible windows by or'ing in WS_VISIBLE. The high 16-bits of
the style argument are used to encode the general purpose
WS_ style options. The low 16-bits of the style argument
are used to encode style options specific to the window class.
The x-y coordinates must be client coordinates,
where (0, 0) is the top left corner of the client area in the
parent window. And, of course, the hParent argument will
be the parent window.
The hMenu argument is not used to create a menu,
instead it is used to assign a control ID to the
child window. Because the control ID provides a convenient
reference to child windows, it's considered good practice to give
unique numbers to the child windows of a parent window.
When a window is created, a WM_CREATE message is sent to its
window procedure after it's created. The handler for this message
is a convenient place to create child windows for the newly
created window.
Controls are usually created as visible child windows
(WS_CHILD or'ed with WS_VISIBLE).
Requests and queries to controls,
SendMessage
When we want to alter or query a control, we send messages to it
by calling SendMessage. Each control defines its own set of
messages for these purposes.
Most of the time, SendMessage acts like a subroutine call. At
this time, we won't worry about the complications of
multithreading.
Responding to controls
Most of the predefined controls (and common controls) have the
capability of sending a specific set of
notifications to their parent windows in the form
of WM_COMMAND messages. The control will send WM_COMMAND with its
window handle, its control ID, and a
notification code.
The "slider" controls are an exception. They send a
WM_VSCROLL or WM_HSCROLL message depending on whether the slider
has a vertical or horizontal orientation. These messages were
originally defined only for scroll bar controls (class
SCROLLBAR).
Custom controls have the option of notifying parent windows
with a WM_NOTIFY message. Some of the common controls also
generate WM_NOTIFY messages. The wParam is the control ID (same
as nmh_idFrom below), and the lParam is the address of a NMHEADER
(notification message header). Notification-specific information
immediately follows the NMHEADER.
NMHEADER STRUC
nmh_hwndFrom DD ? ; handle of control that sent WM_NOTIFY
nmh_idFrom DD ? ; ID of control that sent WM_NOTIFY
nmh_code DD ? ; NM or user defined code
NMHEADER ENDS
The predefined NM codes have negative values, and include:
NM_OUTOFMEMORY, NM_CLICK, NM_DBLCLICK, NM_RETURN, NM_RCLICK,
NM_RDBLCLK, NM_SETFOCUS, and NM_KILLFOCUS.
A simple scratchpad
editor--WINPAD.ASM
The following code implements a text editor with no file
processing -- a scratchpad. All we do is create an EDIT
control. Everything else, including the scrolling and the
right-click menu, is handled by the EDIT control.
Registration
First, we register the window class for the main window.
.386
.model flat
; If using TLINK32, don't include vclib.inc
include vclib.inc ; Microsoft VC++ .lib link names
include win32hst.inc ; constants, structures, and entry names
.data
align 4
wcx dd size WNDCLASSEX ; cbSize
dd CS_VREDRAW or CS_HREDRAW ; style
dd WndProc ; lpfnWndProc
dd 0,0 ; cbClsExtra, cbWndExtra
dd 0 ; hInstance
dd 0 ; hIcon
dd 0 ; hCursor
dd COLOR_WINDOW+1 ; hbrBackground
dd 0 ; lpszMenuName
dd wndclsname ; lpszClassName
dd 0 ; hIconSm
wndclsname db 'winpad',0
.code
public _start
extrn GetModuleHandle:near
extrn LoadIcon:near,LoadCursor:near
extrn RegisterClassEx:near
_start:
push large 0 ; NULL string pointer means
call GetModuleHandle ; get HINSTANCE/HMODULE of EXE file
mov [wcx.wcx_hInstance],eax
push large IDI_WINLOGO
push large 0 ; hInstance, 0 = stock icon
call LoadIcon
mov [wcx.wcx_hIcon],eax
push large IDC_ARROW
push large 0 ; hInstance, 0 = stock cursor
call LoadCursor
mov [wcx.wcx_hCursor],eax
push offset wcx
call RegisterClassEx
Creation and message loop
Unlike previous programs, we eliminate the sizing options (no
frame, minimizing, or maximizing), and add TranslateMessage to
our message loop. The EDIT control doesn't work if we don't call
TranslateMessage.
.data
align 4
cwargs dd 0 ; dwExStyle
dd wndclsname ; lpszClass
dd wnd_title ; lpszName
dd WS_VISIBLE or WS_OVERLAPPED or WS_SYSMENU ; style
dd 50 ; x
dd 50 ; y
dd 400 ; cx (width)
dd 400 ; cy (height)
dd 0 ; hwndParent
dd 0 ; hMenu
dd 0 ; hInstance
dd 0 ; lpCreateParams
msgbuf MSG <>
wnd_title db 'Scratch Pad Editor',0
.code
extrn CreateWindowEx:near
extrn GetMessage:near,TranslateMessage:near,DispatchMessage:near
extrn ExitProcess:near
sub esp,48 ; allocate argument list
mov esi,offset cwargs ; set block move source
mov edi,esp ; set block move destination
mov ecx,12 ; number of arguments
rep movsd
mov eax,[wcx.wcx_hInstance]
mov [esp+40],eax ; set hInstance argument in stack
call CreateWindowEx
msg_loop:
push large 0 ; uMsgFilterMax
push large 0 ; uMsgFilterMin
push large 0 ; hWnd (filter), 0 = all windows
push offset msgbuf ; lpMsg
call GetMessage ; returns FALSE if WM_QUIT
or eax,eax
jz end_loop
push offset msgbuf
call TranslateMessage
push offset msgbuf
call DispatchMessage
jmp msg_loop
end_loop:
push large 0 ; (error) return code
call ExitProcess
Message dispatch
We process WM_CREATE, a message that is sent to our window when
it is first created.
extrn DefWindowProc:near,PostQuitMessage:near
.code
WndProc:
mov eax,[esp+4+4] ; message ID
cmp eax,WM_CREATE ; window created
je on_create
cmp eax,WM_DESTROY ; about to start window destruction
je on_destroy
jmp DefWindowProc ; delegate other message processing
on_destroy:
push large 0
call PostQuitMessage
xor eax,eax
ret 16
Creating a control
We illustrate how to create a control when the parent window is
created.
GetClientRect gets the coordinates of the client area of our
main window. This way, we don't need to precalculate the size of
the EDIT control.
Because several of the CreateWindowEx arguments here aren't
precalculated constants, you may prefer to PUSH the arguments one
at a time.
.data
align 4
wndeditargs dd 0 ; dwExStyle
dd editclsname ; lpszClass
dd 0 ; lpszName
dd WS_CHILD+WS_HSCROLL or WS_VSCROLL or WS_VISIBLE or \
ES_AUTOHSCROLL or ES_AUTOVSCROLL or ES_MULTILINE
dd 0 ; x
dd 0 ; y
dd 0 ; cx (width)
dd 0 ; cy (height)
dd 0 ; hwndParent
dd 0 ; hMenu
dd 0 ; hInstance
dd 0 ; lpCreateParams
client_rect RECT <>
editclsname db 'edit',0
.code
extrn GetClientRect:near
on_create:
mov eax,[esp+4+0] ; grab hwnd before ESP changes
push offset client_rect
push eax ; hwnd
call GetClientRect
mov eax,[esp+4+0] ; grab hwnd before ESP changes
push esi ; must save ESI and EDI
push edi
sub esp,48 ; allocate args
mov esi,offset wndeditargs ; set block move source
mov edi,esp
mov ecx,12
rep movsd
mov [esp+32],eax ; make main window the parent of "edit"
mov eax,[wcx.wcx_hInstance]
mov [esp+40],eax ; set hInstance argument in stack
mov eax,client_rect.rc_left
mov [esp+16],eax ; set x argument
mov eax,client_rect.rc_top
mov [esp+20],eax ; set y argument
mov eax,client_rect.rc_right
inc eax
sub eax,client_rect.rc_left
mov [esp+24],eax ; set cx (width) argument
mov eax,client_rect.rc_bottom
inc eax
sub eax,client_rect.rc_top
mov [esp+28],eax ; set cy (height) argument
call CreateWindowEx
pop edi ; restore ESI and EDI
pop esi
xor eax,eax ; return OK
ret 16
end _start