Introduction to Controls
What is a control? Good question. In earlier years, it meant a window created
with one of the predefined window classes. There was a further stipulation
that the control be created as a child window, despite the ability to create
popup windows with these predefined window classes. These controls provided
specialized components that could be used in application-specific windows
and dialog boxes to "control" the application.
There are now custom controls (created by non-MS
programmers) and common controls, and some of them don't appear to require
being a child window. So now it seems controls are simply windows which
are neither dialog boxes nor application-specific windows.
Registering control classes
There are seven control classes which are already registered when an application
starts. They are: BUTTON, COMBOBOX, EDIT, LISTBOX, MDICLIENT, SCROLLBAR,
and STATIC.
The common controls are registered by calling InitCommonControls.
If you are designing your own custom control, you
will need to register it as a window class by calling RegisterWindowEx.
Creating controls
A dialog box will create the controls specified in its template when the
dialog box is created. Otherwise, controls are created by calling CreateWindowEx.
Most controls are created as visible child windows (WS_CHILD + WS_VISIBLE).
They (and any other child window) can be created when handling the parent
window's WM_CREATE message.
When controls are created as child windows, the
x-y coordinates in CreateWindowEx are in client coordinates,
where (0, 0) is the top left corner of the client area in the parent window.
Also, the menu argument is used to assign a control ID to
the control. The control ID, within each window, should be unique for each
control. You may want to have a matching menu item ID or accelerator key
ID.
Each of the predefined controls (and custom controls)
has a set of style options that are specific to it.
For a custom control, the low 16-bits of the style
argument are available for specifying control-specific style options.
Requests and queries to controls
In order to change a control's appearance or status, messages can be sent
by calling SendMessage. Messages can also be sent to obtain status information.
Each control defines its own set of messages for
these purposes.
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, a control ID, and a notification
code.
An exception is the scroll bar control (class SCROLLBAR)
which sends a WM_VSCROLL or WM_HSCROLL message.
Custom controls have the option of notifying parent
windows with a WM_NOTIFY message. The wParam is the control ID (same as
nmhFromID below), and the lParam is the address of a NMHEADER (notification
message header). Notification-specific information immediately follows
the NMHEADER.
NMHEADER STRUC
nmhFromWnd DD ? ; handle of control that sent WM_NOTIFY
nmhFromID DD ? ; ID of control that sent WM_NOTIFY
nmhNotifyCode 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 add an EDIT control, and resize it when
the main window changes size. Everything else, including the scrolling
and the right-click menu, is done by the EDIT control.
If you aren't using the supplied WINMAIN module, you'll need to add
a call to TranslateMessage in the message loop. The window class
EDIT requires WM_CHAR messages to work, and TranslateMessage provides it.
extrn GetMessage:near,TranslateMessage:near,DispatchMessage:near
.data
msgbuf MSG <>
.code
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:
We use the WS_OVERLAPPEDWINDOW style (notice the extra word WINDOW) which
is the shorthand for the standard main window style. This style gives
us all the title bar options, and allows resizing by pulling on the main
window edges and corners.
We also set up the CreateWindowEx arguments for
the EDIT window. EDIT is already registered when the program starts.
.data
wc WNDCLASSEX <size WNDCLASSEX,CS_HREDRAW+CS_VREDRAW,WndProc,0,0, 0, \
0,0,COLOR_WINDOW+1, 0,wndclsname,0>
wndmain CREATEARGS <0,wndclsname,caption,WS_OVERLAPPEDWINDOW+WS_VISIBLE,\
100,100,200,200, 0,0,0,0>
wndedit CREATEARGS <0,editclsname,0,WS_CHILD+WS_HSCROLL+WS_VSCROLL+\
WS_VISIBLE+ES_AUTOHSCROLL+ES_AUTOVSCROLL+ES_MULTILINE,\
0,0,0,0, 0,0,0,0>
wndclsname db 'ScratchPad',0
editclsname db 'edit',0
caption db 'Scratch Pad Editor',0
hEdit dd 0
We process three messages in the main window. The new message WM_SIZE
is sent when the main window is resized.
extrn DefWindowProc:near
.code
WndProc:
mov eax,[esp+4+4] ; message ID
cmp eax,WM_CREATE ; window created
je finish_create
cmp eax,WM_SIZE ; about to draw resized window
je resizing_window
cmp eax,WM_DESTROY ; about to start window destruction
je start_destroy
jmp DefWindowProc ; delegate other message processing
When the main window is created, we create an edit window and make it a
child of the main window. We save the handle of the edit window because
we will need it to resize it.
extrn CreateWindowEx:near
finish_create:
mov eax,[esp+4+0] ; grab hwnd before ESP changes
push esi
push edi
mov esi,offset wndedit
mov [esi].cwargParent,eax ; make "wndmain" a parent of "edit"
mov eax,[wc].wcxInstance
mov [esi].cwargInstance,eax ; set edit instance
sub esp,48 ; allocate args
mov edi,esp
mov ecx,12
rep movsd
call CreateWindowEx
mov [hEdit],eax ; save edit window handle
pop edi
pop esi
xor eax,eax ; signal a successful CREATE
ret 16
We didn't add a border to the edit window, so you can't see if the following
code resizes the edit window properly. Add WS_BORDER to the edit
window arguments to see the resizing effects.
We use the client area size supplied by lParam.
This only provides us with 16-bit values, which is adequate for Win95,
but not necessarily for NT. To get the 32-bit values, use GetClientRect.
extrn MoveWindow:near
.code
resizing_window:
; grab arguments before ESP changes
xor eax,eax
xor ecx,ecx
mov ax,[esp+4+12+2] ; y, height
mov cx,[esp+4+12+0] ; x, width
push large 1 ; repaint
push eax ; height
push ecx ; width
push large 0 ; y = 0, top left corner of client area
push large 0 ; x = 0
push [hEdit] ; resize edit window
call MoveWindow
xor eax,eax
ret 16