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