本課中我們將學習什麼是通用控件和如何使用它們。
理論:
WIN95相對於WIN3X有幾個加強的用戶界面控件。其實在WIN95正式發行前這些控件就在使用,譬如:狀態條、工具條等。以前程序員要自己去編程使用它們,現在微軟已經把它們包含到了WIN9X和WINNT中了。
Toolbar ---工具條
Tooltip ---提示文本
Status bar ---狀態條
Property sheet ---屬性頁單
Property page ---屬性頁
Tree view ---樹型視圖
List view ---列表視圖
Animation ---動畫
Drag list ---能夠處理Drag-Drop的列表框
Header ---
Hot-key ---熱鍵
Image list ---圖象鏈表
Progress bar ---進程狀態條
Right edit ---
Tab ---跳格表
Trackbar ---跟蹤條
Up-down ---滾動條
因為通用控件的數量非常多,把它們全部裝入內存並注冊它們是非常浪費內存的。除了“RTF文本編輯”控件外其他控件的可執行代碼都放在comctl32.dll中,這樣其他的應用程序就可以使用它們了。“RTF文本編輯”控件在richedXX.dll中,由於該控件非常的復雜,所以也比其它控件大。
要加載comctl32.dll可以在您的應用程序中調用函數InitCommonControls。InitCommonControls函數是動態鏈接庫comctl32.dll中的一個函數,只要在您的程序中的任意地方引用了該函數就、會使得WINDOWS的程序加載器PE Loader加載該庫。函數InitCommonControls其實只有一條指令“ret”,它的唯一目的是為了使得在調用了個該函數的應用程序的可執行文件的PE頭中的“引入”段中包含有comctl32.dll,這樣無論什麼時候該應用程序都會為您加載該庫。所以真正初始化的工作是在該庫的入口點處做的,在這裡會注冊所有的通用控件類,然後所有的通用控件就可以在這些類上進行創建,這就象創建其它的子窗口控件一樣。
RTF文本編輯控件則不同。如果您要使用它,就必須調用LoadLibrary函數來動態加載,並調用FreeLibrary來動態地卸載。
現在我們學習如何創建這些通用控件。您可以用資源編輯器把它們放到一個對話框中,或者您也可以自己調用相關的函數來手動創建它們。幾乎所有的通用控件都是調用函數CreateWindowEx或CreateWindow來創建的,您只要在其中傳遞通用控件的類名即可。有一些通用控件有一些特別的創建函數,但是其實這些函數在內部都調用了CreateWindowEx,只是包裝後的函數更方便使用而已。經過包裝的函數有:
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
Property sheets、property pages和image list控件有它們自己的創建函數。Drag list其實是可以伸縮的listbox控件,所以它沒有自己的類名。上面的類名是VC++的資源編輯器提供的,它們和Borland公司的WIN32 API指南中提出的不一樣,和Petzold的書《Programming Windows 95》也不一樣。可以肯定的是我們上面列出的類名絕對准確。 這些通用控件可以有通用的窗口類的一些風格,譬如WS_CHILD等。它們當然還有其他的特殊風格,譬如樹型視圖控件就有TVS_XXXXX風格,列表控件就有LVS_xxxx風格。具體的最好查找有關的WIN32 API函數指南。 既然我們已經知道了如何創建一個通用控件,我們就可以討論這些通用控件之間以及和它們的父窗口之間是如何通訊的了。不象子窗口控件,通用控件在某些狀態發生變化時不是通過發送WM_COMMAND而是發送WM_NOTIFY消息和父窗口通訊的。父窗口可以通過發送消息來控制子窗口的行為。對於那些新的通用控件,還有一些新的消息類型。您可以參考您的WIN32 API手冊。
在下面的例子中我們將要實驗一下進度條和狀態條。
例子代碼:
.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
分析:
invoke WinMain, hInstance,NULL,NULL, SW_SHOWDEFAULT
invoke ExitProcess,eax
invoke InitCommonControls
我故意把函數InitCommonControls放到ExitProcess後,這樣就可以驗證調用該函數僅僅是為了在我們程序的可執行文件的PE頭中的引入段中放入引用了comctl32.dll的信息。您可以看到,即使該函數什麼都沒有做,我們的通用控件對話框依舊可以正常工作。
.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
在這裡我們創建了通用控件。注意CreateWindowEx函數中的參數hWnd是父窗口的句柄。另外它也指定了通用控件的ID號。因為我們直接使用控件的窗口句柄,所以就沒有使用該ID號。所有的窗口都必須具有WS_CHILD風格。
mov eax,1000
mov CurrentStep,eax
shl eax,16
invoke SendMessage,hwndProgress,PBM_SETRANGE,0,eax
invoke SendMessage,hwndProgress,PBM_SETSTEP,10,0
在創建了進度條後我們先設定它的范圍。缺省的范圍是0-100。如果您不滿意,可以重新設置,這通過傳遞PBM_SETRANGE消息來實現。參數lParam中包含了范圍值,其中底字和高字分別是范圍的起始和終了的值。您可以指定進度條每移動一格的步長。本例子中把步長設置成10,意味著每發送一次PBM_STEPIT消息給進度條,它的顯示指針就會移動10。當然您可以調用PBM_SETPOS 來直接設定進度條上的指針的位置。用該消息您可以更方便地設定進度條了。
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
下面我們調用CreateStatusWindow來創建狀態條。這個調用很好理解,無需我多解釋。在狀態條創建後我們創建一個計時器。在本例中我們每隔100毫秒就更新一次進度條。下面時創建記時器的函數原型:
SetTimer PROTO hWnd:DWORD, TimerID:DWORD, TimeInterval:DWORD, lpTimerProc:DWORD
hWnd : 父窗口的句柄。
TimerID : 計時器的ID號。您可以指定一個唯一的非零值。
TimerInterval : 以毫秒計的時間間隔。
lpTimerProc : 計時器回調函數的地址。每當時間間隔到了的時候,該函數就會被系統調用。如果該值為NULL,計時器就會把WM_TIMER消息發送到父窗口。
如果SetTimer調用成功的話就會返回計時器的ID號值,否則返回0。這也是為什麼計時器的ID號必須為非零值的原因。
.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
當指定的時間到了的時候,計時器將發送WM_TIMER消息。您可以在處理該消息時作適當的處理。本例中我們將更新進度條,並檢查進度條是否超過最大的值。如果超過了的話,我們通過發送SB_SETTEXT消息來在狀態條中設置文本。這時,彈出一個對話框,當用戶關閉掉對話框後,我們去除掉進度條和狀態條中的文本。