iczelion tut18
Tutorial 18: Common Controls
We will learn what common controls are and how to use them. This tutorial will be a quick introduction to them only.
Download the example source code here.
Theory:
Windows 95 comes with several user-interface enhancements over Windows 3.1x. They make the GUI richer. Several of them are in widely used before Windows 95 hit the shelf, such as status bar, toolbars etc. Programmers have to code them themselves. Now Microsoft has included them with Windows 9x and NT. We will learn about them here.These are the new controls:
- Toolbar
 - Tooltip
 - Status bar
 - Property sheet
 - Property page
 - Tree view
 - List view
 - Animation
 - Drag list
 - Header
 - Hot-key
 - Image list
 - Progress bar
 - Right edit
 - Tab
 - Trackbar
 - Up-down
 
You can load comctl32.dll by including a call to InitCommonControls in your program. InitCommonControls is a function in comctl32.dll, so referring to it anywhere in your code will make PE loader load comctl32.dll when your program runs.You don't have to execute it, just include it in your code somewhere. This function does NOTHING! Its only instruction is "ret". Its sole purpose is to include reference to comctl32.dll in the import section so that PE loader will load it whenever the program is loaded. The real workhorse is the DLL entrypoint function which registers all common control classes when the dll is loaded. Common controls are created based on those classes just like other child window controls such as edit, listbox etc.
Rich edit is another matter entirely. If you want to use it, you have to call LoadLibrary to load it explicitly and call FreeLibrary to unload it.
Now we learn how to create them. You can use a resource editor to incorporate them into dialog boxes or you can create them yourself. Nearly all common controls are created by calling CreateWindowEx or CreateWindow, passing it the name of the control class. Some common controls have specific creation functions , however, they are just wrappers around CreateWindowEx to make it easier to create those controls. Existing specific creation functions are listed below:
- CreateToolbarEx
 - CreateStatusWindow
 - CreatePropertySheetPage
 - PropertySheet
 - ImageList_Create
 
| ToolbarWindow32 | Toolbar | 
| tooltips_class32 | Tooltip | 
| msctls_statusbar32 | Status bar | 
| SysTreeView32 | Tree view | 
| SysListView32 | List view | 
| SysAnimate32 | Animation | 
| SysHeader32 | Header | 
| msctls_hotkey32 | Hot-key | 
| msctls_progress32 | Progress bar | 
| RICHEDIT | Rich edit | 
| msctls_updown32 | Up-down | 
| SysTabControl32 | Tab | 
Those common controls can use general window styles such as WS_CHILD etc. They also have their own specific styles such as TVS_XXXXX for tree view control, LVS_xxxx for list view control, etc. Win32 api reference is your best friend in this regard.
Now that we know how to create common controls, we can move on to communication method between common controls and their parent. Unlike child window controls, common controls don't communicate with the parent via WM_COMMAND. Instead they send WM_NOTIFY messages to the parent window when some interesting events occur with the common controls. The parent can control the children by sending messages to them. There are also many new messages for those new controls. You should consult your win32 api reference for more detail.
Let's examine progress bar and status bar controls in the following example.
Sample code:
.386.model flat,stdcall
option casemap:none
include masm32includewindows.inc
include masm32includeuser32.inc
include masm32includekernel32.inc
include masm32includecomctl32.inc
includelib masm32libcomctl32.lib
includelib masm32libuser32.lib
includelib masm32libkernel32.lib
WinMain PROTO :DWORD,:DWORD,:DWORD,:DWORD
.const 
IDC_PROGRESS equ 1            ; control IDs 
IDC_STATUS equ 2 
IDC_TIMER  equ 3 
.data 
ClassName  db "CommonControlWinClass",0 
AppName    db "Common Control Demo",0 
ProgressClass  db "msctls_progress32",0       ; the class name of the progress bar 
Message  db "Finished!",0 
TimerID  dd 0 
.data? 
hInstance  HINSTANCE ? 
hwndProgress dd ? 
hwndStatus dd ? 
CurrentStep dd ? 
.code 
start: 
    invoke GetModuleHandle, NULL 
    mov    hInstance,eax 
    invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT 
    invoke ExitProcess,eax 
    invoke InitCommonControls 
WinMain proc hInst:HINSTANCE,hPrevInst:HINSTANCE,CmdLine:LPSTR,CmdShow:DWORD 
    LOCAL wc:WNDCLASSEX 
    LOCAL msg:MSG 
    LOCAL hwnd:HWND 
    mov   wc.cbSize,SIZEOF WNDCLASSEX 
    mov   wc.style, CS_HREDRAW or CS_VREDRAW 
    mov   wc.lpfnWndProc, OFFSET WndProc 
    mov   wc.cbClsExtra,NULL 
    mov   wc.cbWndExtra,NULL 
    push  hInst 
    pop   wc.hInstance 
    mov   wc.hbrBackground,COLOR_APPWORKSPACE 
    mov   wc.lpszMenuName,NULL 
    mov   wc.lpszClassName,OFFSET ClassName 
    invoke LoadIcon,NULL,IDI_APPLICATION 
    mov   wc.hIcon,eax 
    mov   wc.hIconSm,eax 
    invoke LoadCursor,NULL,IDC_ARROW 
    mov   wc.hCursor,eax 
    invoke RegisterClassEx, addr wc 
    invoke CreateWindowEx,WS_EX_CLIENTEDGE,ADDR ClassName,ADDR AppName, 
WS_OVERLAPPED+WS_CAPTION+WS_SYSMENU+WS_MINIMIZEBOX+WS_MAXIMIZEBOX+WS_VISIBLE,CW_USEDEFAULT, 
           CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,NULL,NULL, 
           hInst,NULL 
    mov   hwnd,eax 
    .while TRUE 
         invoke GetMessage, ADDR msg,NULL,0,0 
        .BREAK .IF (!eax) 
        invoke TranslateMessage, ADDR msg 
        invoke DispatchMessage, ADDR msg 
    .endw 
    mov eax,msg.wParam 
    ret 
WinMain endp 
WndProc proc hWnd:HWND, uMsg:UINT, wParam:WPARAM, lParam:LPARAM 
    .if uMsg==WM_CREATE 
         invoke CreateWindowEx,NULL,ADDR ProgressClass,NULL, 
            WS_CHILD+WS_VISIBLE,100, 
            200,300,20,hWnd,IDC_PROGRESS, 
            hInstance,NULL 
        mov hwndProgress,eax 
        mov eax,1000               ; the lParam of PBM_SETRANGE message contains the range 
        mov CurrentStep,eax 
        shl eax,16                   ; the high range is in the high word 
        invoke SendMessage,hwndProgress,PBM_SETRANGE,0,eax 
        invoke SendMessage,hwndProgress,PBM_SETSTEP,10,0 
        invoke CreateStatusWindow,WS_CHILD+WS_VISIBLE,NULL,hWnd,IDC_STATUS 
        mov hwndStatus,eax 
        invoke SetTimer,hWnd,IDC_TIMER,100,NULL        ; create a timer 
        mov TimerID,eax 
    .elseif uMsg==WM_DESTROY 
        invoke PostQuitMessage,NULL 
        .if TimerID!=0 
            invoke KillTimer,hWnd,TimerID 
        .endif 
    .elseif uMsg==WM_TIMER        ; when a timer event occurs 
        invoke SendMessage,hwndProgress,PBM_STEPIT,0,0    ; step up the progress in the progress bar 
        sub CurrentStep,10 
        .if CurrentStep==0 
            invoke KillTimer,hWnd,TimerID 
            mov TimerID,0 
            invoke SendMessage,hwndStatus,SB_SETTEXT,0,addr Message 
            invoke MessageBox,hWnd,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION 
            invoke SendMessage,hwndStatus,SB_SETTEXT,0,0 
            invoke SendMessage,hwndProgress,PBM_SETPOS,0,0 
        .endif 
    .else 
        invoke DefWindowProc,hWnd,uMsg,wParam,lParam 
        ret 
    .endif 
    xor eax,eax 
    ret 
WndProc endp 
end start 
Analysis:
-     invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT 
 
invoke ExitProcess,eax
invoke InitCommonControls
-     .if uMsg==WM_CREATE 
 
invoke CreateWindowEx,NULL,ADDR ProgressClass,NULL,
WS_CHILD+WS_VISIBLE,100,
200,300,20,hWnd,IDC_PROGRESS,
hInstance,NULL
mov hwndProgress,eax
-         mov eax,1000 
 
mov CurrentStep,eax
shl eax,16
invoke SendMessage,hwndProgress,PBM_SETRANGE,0,eax
invoke SendMessage,hwndProgress,PBM_SETSTEP,10,0
-         invoke CreateStatusWindow,WS_CHILD+WS_VISIBLE,NULL,hWnd,IDC_STATUS 
 
mov hwndStatus,eax
invoke SetTimer,hWnd,IDC_TIMER,100,NULL ; create a timer
mov TimerID,eax
- SetTimer PROTO hWnd:DWORD, TimerID:DWORD, TimeInterval:DWORD, lpTimerProc:DWORD 
 
TimerID : a nonzero timer identifier. You can create your own identifier.
TimerInterval : the timer interval in milliseconds that must pass before the timer calls the timer procedure or sends a WM_TIMER message
lpTimerProc : the address of the timer function that will be called when the time interval expires. If this parameter is NULL, the timer will send WM_TIMER message to the parent window instead.
If this call is successful, it will return the TimerID. If it failed, it returns 0. This is why the timer identifer must be a nonzero value.
-     .elseif uMsg==WM_TIMER 
 
invoke SendMessage,hwndProgress,PBM_STEPIT,0,0
sub CurrentStep,10
.if CurrentStep==0
invoke KillTimer,hWnd,TimerID
mov TimerID,0
invoke SendMessage,hwndStatus,SB_SETTEXT,0,addr Message
invoke MessageBox,hWnd,addr Message,addr AppName,MB_OK+MB_ICONINFORMATION
invoke SendMessage,hwndStatus,SB_SETTEXT,0,0
invoke SendMessage,hwndProgress,PBM_SETPOS,0,0
.endif
