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
Since
there are many of them, loading them all into memory and registering them
would be a waste of resource. All of them, with the exception of rich edit
control, are stored in comctl32.dll with applications can load when they
want to use the controls. Rich edit control resides in its own dll, richedXX.dll,
because it's very complicated and hence larger than its brethren.
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
In order
to create common controls, you have to know their class names. They are
listed below:
Class
Name
|
Common
Control
|
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 |
Property
sheets and property pages and image list control have their own specific
creation functions. Drag list control are souped-up listbox so it doesn't
have its own class. The above class names are verified by checking resource
script generated by Visual C++ resource editor. They differ from the class
names listed by Borland's win32 api reference and Charles Petzold's Programming
Windows 95. The above list is the accurate one.
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
\masm32\include\windows.inc
include
\masm32\include\user32.inc
include
\masm32\include\kernel32.inc
include
\masm32\include\comctl32.inc
includelib
\masm32\lib\comctl32.lib
includelib
\masm32\lib\user32.lib
includelib
\masm32\lib\kernel32.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
I deliberately
put InitCommonControls after ExitProcess to demonstrate that InitCommonControls
is just there for putting a reference to comctl32.dll in the import section.
As you can see, the common controls work even if InitCommonControls doesn't
execute.
.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
Here is
where we create the common control. Note that this CreateWindowEx call
contains hWnd as the parent window handle. It also specifies a control
ID for identifying this control. However, since we have the control's window
handle, this ID is not used. All child window controls must have WS_CHILD
style.
mov eax,1000
mov CurrentStep,eax
shl eax,16
invoke SendMessage,hwndProgress,PBM_SETRANGE,0,eax
invoke SendMessage,hwndProgress,PBM_SETSTEP,10,0
After
the progress bar is created, we can set its range. The default range is
from 0 to 100. If you are not satisfied with it, you can specify your own
range with PBM_SETRANGE message. lParam of this message contains the range,
the maximum range is in the high word and the minimum one is in the low
word. You can specify how much a step takes by using PBM_SETSTEP message.
The example sets it to 10 which means that when you send a PBM_STEPIT message
to the progress bar, the progress indicator will rise by 10. You can also
set your own indicator level by sending PBM_SETPOS messages. This message
gives you tighter control over the progress bar.
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
Next,
we create a status bar by calling CreateStatusWindow. This call is easy
to understand so I'll not comment on it. After the status window is created,
we create a timer. In this example, we will update the progress bar at
a regular interval of 100 ms so we must create a timer control. Below is
the function prototype of SetTimer.
SetTimer
PROTO hWnd:DWORD, TimerID:DWORD, TimeInterval:DWORD, lpTimerProc:DWORD
hWnd
: Parent window handle
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
When the
specified time interval expires, the timer sends a WM_TIMER message. You
will put your code that will be executed here. In this example, we update
the progress bar and then check if the maximum limit has been reached.
If it has, we kill the timer and then set the text in the status window
with SB_SETTEXT message. A message box is displayed and when the user clicks
OK, we clear the text in the status bar and the progress bar.
[Iczelion's
Win32 Assembly Homepage]