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