因为通用控件的数量非常多,把它们全部装入内存并注册它们是非常浪费内存的。除了“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,只是包装后的函数更方便使用而已。经过包装的函数有:
|
|
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消息来在状态条中设置文本。这时,弹出一个对话框,当用户关闭掉对话框后,我们去除掉进度条和状态条中的文本。