WIN32汇编(通用控件1)
核心提示:9.1.1 通用控件的分类 大部分的通用控件由Comctl32.dll模块提供,所以在使用之前要在源程序中包含相应的include和includelib语句: include Comctl32.inc includelib Comctl32.libComctl32.dll中提供的通用控件如表9.1所...
9.1.1 通用控件的分类
大部分的通用控件由Comctl32.dll模块提供,所以在使用之前要在源程序中包含相应的include和includelib语句:
include Comctl32.inc
includelib Comctl32.lib
Comctl32.dll中提供的通用控件如表9.1所示。
表9.1 通用控件
控 件 名 称 预定义的窗口类 说 明 特 殊 风 格
Animation Controls SysAnimate32 动画 ACS_
Header Controls SysHeader32 标题栏 HDS_
ListView Controls SysListView32 列表视图 LVS_
TreeView Controls SysTreeView32 树型视图 TVS_
Tab Controls SysTabControl32 项目列表 TCS_
Progress Bars msctls_progress32 进度条
Status Windows msctls_statusbar32 状态栏 SBARS_
HotKey Controls msctls_hotkey32 热键
Trackbars msctls_trackbar32 跟踪条 TBS_
Up-Down Controls msctls_updown32 滚动条 UDS_
Toolbars ToolbarWindow32 工具 TBSTYLE_
Tooltip Controls Tooltips_class32 提示文本
ImageLists
图像列表
PropertySheets
属性表格
PropertySheetsPage
属性页
DragList
能处理拖放功能的列表框
在高版本的Comctl32.dll(IE4.0以上版本更新的Comctl32.dll文件)中,还包括了一些扩展的通用控件,这些扩展控件如表9.2所示。
表9.2 扩展通用控件
控 件 名 称 预定义的窗口类 说 明 特 殊 风 格
Rebar Controls ReBarWindow32 IE风格工具栏 RBS_
Date & Time Picker SysDateTimePick32 日期时间 DTS_
IP Address Picker SysIPAddress32 IP地址输入
Pager Controls SysPager PGS_
ComboBoxEx ComboBoxEx32 扩展ComboBox CBS_
Windows系统自身附带的软件中也大量使用通用控件,以图9.1中所示的“资源管理器”程序界面为例,窗口的上方使用标题栏控件,标题栏控件上显示的说明文字是提示文本控件,窗口下方使用状态栏控件;左边列出目录的地方是树型视图控件,右边列出文件的地方是列表视图控件,列表视图控件中的标题栏本身就使用另一个控件——标题栏控件。
其他的一些控件在操作系统中也随处可见,如跳格表控件通常在属性设置对话框中使用;拷贝大文件时的进度窗口中有个进度条控件。
除了这些控件之外,Richedit控件也是一个很常用的控件,Richedit控件是Edit控件的增强版本,包含了很完整的文本编辑功能,可以用来编辑带格式的rtf文件和不带格式的txt文件,由于该控件非常复杂,代码的规模比较大,单独一个Richedit控件的代码规模就和Comctl32.dll中全部代码的规模相当,所以Windows系统将Richedit控件单独放到另一个dll文件中,有关Richedit控件的情况将在9.4节中详细介绍。
9.1.2 使用通用控件
1.库初始化
通用控件的数量非常多,平时把它们全部装入并注册是非常浪费内存的,所以在默认状态下Comctl32.dll并不会被装入内存,因此,在使用通用控件之前必须将通用控件库装入内存,专用函数InitCommonControls可以用来完成这个工作,调用这个函数的惟一目的是保证系统加载Comctl32.dll库文件。
当库文件被装入的时候,库的入口函数会注册所有的通用控件类,然后用户程序就可以使用这些预定义的类来创建各种类型的通用控件窗口,这就像创建其他的子窗口控件一样。InitCommonControls函数没有参数,也没有定义返回值,它的使用方法是:
invoke InitCommonControls
InitCommonControls函数仅注册表9.1中所列的通用控件类,并不注册表9.2中的扩展通用控件。如果需要使用扩展通用控件,那么需要使用InitCommonControlsEx函数来进行装入和注册的工作:
invoke InitCommonControlsEx,lpInitCtrls
lpInitCtrls参数指向一个INITCOMMONCONTROLSEX结构:
INITCOMMONCONTROLSEX STRUCT
dwSize dd ? ;结构长度
dwICC dd ? ;需要初始化的类
INITCOMMONCONTROLSEX ENDS
结构中的dwICC字段指定了需要注册的扩展通用控件类,与InitCommonControls注册所有它支持的通用控件类不同,InitCommonControlsEx函数只注册dwICC字段指明的扩展通用控件类,字段可以是下面取值的组合:
● ICC_BAR_CLASSES——注册工具栏、状态栏、Trackbar和Tooltip类。
● ICC_COOL_CLASSES——注册Rebar类。
● ICC_DATE_CLASSES——注册Date and Time Picker类。
● ICC_HOTKEY_CLASS——注册Hot Key类。
● ICC_INTERNET_CLASSES——注册IP Address Picker类。
● ICC_LISTVIEW_CLASSES——注册ListView和Header类。
● ICC_PAGESCROLLER_CLASS——注册Pager类。
● ICC_PROGRESS_CLASS——注册Progress Bar类。
● ICC_TAB_CLASSES——注册Tab和Tooltip类。
● ICC_TREEVIEW_CLASSES——注册TreeView和Tooltip类。
● ICC_UPDOWN_CLASS——注册Up-Down类。
● ICC_USEREX_CLASSES——注册ComboBoxEx类。
● ICC_WIN95_CLASSES——注册InitCommonControls函数注册的所有类。
InitCommonControlsEx函数是InitCommonControls函数的扩充,使用它也可以注册InitCommonControls函数能够注册的所有类(也可以仅注册其中的一部分),如果只用到通用控件,两个初始化函数都可以使用,但若用到扩展通用控件,那就只能使用InitCommonControlsEx函数来进行初始化了。
创建通用控件的代码一般放在主窗口的WM_CREATE消息中,所以InitCommonControls和InitCommonControlsEx函数的调用需要在此之前完成,一般在程序一开始的地方就调用它们
核心提示:2. 创建通用控件大部分的通用控件都以窗口类的方法实现(惟一的例外是图像列表),所以创建通用控件窗口的方法和使用自定义窗口类建立窗口的方法是一样的,只要在CreateWindowEx函数中使用通用控件的类名就可以了。如果要在对话框中使用通用控件,也可以在资源文件中用定义子窗口控件同样的方法来定义通用...
2. 创建通用控件
大部分的通用控件都以窗口类的方法实现(惟一的例外是图像列表),所以创建通用控件窗口的方法和使用自定义窗口类建立窗口的方法是一样的,只要在CreateWindowEx函数中使用通用控件的类名就可以了。如果要在对话框中使用通用控件,也可以在资源文件中用定义子窗口控件同样的方法来定义通用控件(见5.4.4小节)。
在建立通用控件的时候,可以使用WS_CHILD等通用的窗口风格,除此之外,不同的通用控件也有自己的特殊风格,如树型视图控件有TVS_XXXXX风格、列表控件有LVS_xxxx风格等,表9.1和表9.2中列出了一些特殊风格的前缀,这些风格的具体含义可以参考Win32 API函数指南。
可以在对话框资源定义中如下定义一个列表视图控件:
CONTROL "", IDC_LISTVIEW, "SysListView32",
LVS_REPORT | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_TABSTOP,
11, 13, 216, 82, WS_EX_CLIENTEDGE
也可以在程序中使用CreateWindowsEx函数如下创建:
szClass db "SysListView32",0
...
invoke CreateWindowEx,WS_EX_CLIENTEDGE,offset szClass,NULL,\
LVS_REPORT or WS_CHILD or WS_VISIBLE or WS_BORDER,\
11,13,216,82,\
hWinMain,IDC_LISTVIEW,hInstance,NULL
上述两种方法的使用效果是一样的,程序代码中的WS_CHILD等以WS开头的风格是窗口的通用风格,而LVS_REPORT风格是列表视图控件的特有风格。由于大多数时候通用控件都是当做子窗口创建的,所以窗口风格中必须包括WS_CHILD风格。另外,使用CreateWindowEx创建的时候必须指定WS_VISIBLE风格,否则控件不会被显示;而在对话框资源中定义的时候,系统总是默认加上WS_VISIBLE风格,所以有没有WS_VISIBLE是无所谓的。
除了调用CreateWindowEx或CreateWindow函数来创建通用控件外,某些通用控件的创建可以使用一些专用的函数,这些函数其实在内部都调用了CreateWindowEx,只是由于包装后的函数是量身定做的,使用起来更方便而已。经过包装的函数有:
● CreateToolbarEx函数——用来创建工具栏。
● CreateStatusWindow函数——用来创建状态栏。
● CreateUpDownControl函数——用来创建滚动条控件。
另外,有些通用控件并没有自己的窗口类名称,或者说根本就不是窗口类,比如属性表格和属性页控件是基于项目列表控件(Table Control)的,DragList控件是基于下拉式列表框的,它们是其他控件的扩展;而图像列表控件本身是一幅图像而不是窗口类。创建这些控件的时候,由于无法用类名来指定它们,所以无法使用CreateWindowEx函数来创建,必须使用下面这些专用的创建函数:
● PropertySheet函数——用来创建属性表格。
● CreatePropertySheetPage函数——用来创建属性页。
● ImageList_Create函数——用来创建图像列表。
● MakeDragList函数——用来创建DragList控件。
在使用控件时要牢记的是:大部分的控件是窗口,它们是特殊的窗口,所以所有适用于窗口的概念都可以使用在控件上,包括创建与使用的方法,和父窗口的通信方式及内部的工作原理等。
3. 通用控件和父窗口之间的通信
当在对话框中使用子窗口控件时,父窗口通过SendMessage函数发送控制消息来管理子窗口控件,而子窗口控件通过发送WM_COMMAND或WM_NOTIFY消息来将用户的动作通知父窗口。
通用控件的通信方法和子窗口控件使用的方法是一样的——父窗口发送控制消息来管理通用控件,不同类型的通用控件使用不同的控制消息,如状态栏的控制消息都是以SB_开头的(Status Bar的缩写);TreeView控件的控制消息是以TVM_开头的(Tree View Message的缩写);ListView控件的控制消息是以LVM_开头的(List View Message的缩写)。不同的消息都有特定的用法和参数,在使用时需要查阅Win32函数手册。
通用控件也通过发送通知消息来和父窗口通信,不同通用控件使用的通知消息可能有所不同,归纳起来情况如下:
● 工具栏控件使用WM_COMMAND消息将按钮动作通知父窗口,这是为了便于和菜单、加速键使用同一份代码来处理用户按下工具栏按钮的动作。
● 滚动条控件使用WM_VSCROLL或者WM_HSCROLL消息通知父窗口,和窗口自身滚动条使用的消息名称保持一致可以便于使用已经存在的滚动条消息处理代码。
● 除了上面这两种特殊情况外,大部分通用控件使用WM_NOTIFY消息通知父窗口。这样可以避免和菜单或加速键等使用的WM_COMMAND消息相混淆。
当父窗口收到WM_NOTIFY消息时,wParam参数的内容是通用控件的ID,也就是使用CreateWindowEx函数创建控件时使用的第10个参数,通过这个参数可以判别WM_NOTIFY消息是由哪个通用控件发送的;消息的lParam参数指向一个NMHDR结构:
NMHDR STRUCT
hwndFrom DWORD ? ;发送WM_NOTIFY的通用控件的窗口句柄
idFrom DWORD ? ;发送WM_NOTIFY的通用控件的ID
code DWORD ? ;通知码
NMHDR ends
通过NMHDR结构中的hwndFrom字段和idFrom字段也可以判别发送WM_NOTIFY消息的控件,由于使用CreateWindowEx函数创建多个通用控件的时候可以使用同样的ID值,所以有时候使用ID并不能惟一确定控件,只有在创建的时候对不同的控件使用了不同的ID值,才能用ID值来惟一确定控件。而系统中每个窗口的窗口句柄是惟一的,所以使用hwndFrom字段是肯定能惟一确定控件的。
结构中的code字段是通知码,通过这个字段可以了解到控件上发生的动作,每种控件都有自己独特的通知码集合,但下面的通知码是大部分控件都使用的:
● NM_CLICK——用户在控件上按下了鼠标左键。
● NM_DBLCLK——用户在控件上双击鼠标左键。
● NM_KILLFOCUS——控件失去了键盘输入焦点。
● NM_OUTOFMEMORY——控件在运行中内存耗尽。
● NM_RCLICK——用户在控件上按下了鼠标右键。
● NM_RDBLCLK——用户在控件上双击鼠标右键。
● NM_RETURN——用户在控件上按下了回车键。
● NM_SETFOCUS——控件得到了键盘输入焦点。
核心提示:状态栏一般位于主窗口的底部(当然,如果愿意的话,也可以把它放在主窗口的上方,不过几乎没有人这样做),用来显示程序运行中的一些状态信息。本节中的例子程序创建了一个带状态栏的对话框,运行界面如图9.2的左边窗口所示。状态栏中分别显示了时间、编辑器中的总字节数和插入状态等3栏内容,随着时间的改变和字符的输...
状态栏一般位于主窗口的底部(当然,如果愿意的话,也可以把它放在主窗口的上方,不过几乎没有人这样做),用来显示程序运行中的一些状态信息。本节中的例子程序创建了一个带状态栏的对话框,运行界面如图9.2的左边窗口所示。状态栏中分别显示了时间、编辑器中的总字节数和插入状态等3栏内容,随着时间的改变和字符的输入,这些信息会随时被更新。状态栏的另一个重要应用是显示菜单项的说明信息。在例子程序中,随着鼠标移到不同的菜单项上,状态栏上的说明信息也随之改变,如图9.2的右边窗口所示。
图9.2 状态栏示例
一般来说,状态栏仅用于输出信息,并不用来输入信息,但有时也会使用状态栏来获取有限功能的输入,如在例子程序中状态栏的第3栏上单击鼠标,文字会在“插入”和“改写”之间切换,一些文本编辑软件就是用这种办法来改变文字输入方式的。
本节提供的例子位于所附光盘的Chapter09\StatusBar目录中,包括StatusBar.asm文件和StatusBar.rc文件。StatusBar.asm文件的内容如下:
.386
.model flat, stdcall
option casemap :none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
include Comctl32.inc
includelib Comctl32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Equ 等值定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN equ 1000
DLG_MAIN equ 1000
IDM_MAIN equ 1000
IDM_EXIT equ 1104
IDM_MENUHELP equ 1300
ID_STATUSBAR equ 1
ID_EDIT equ 2
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
hInstance dd ?
hWinMain dd ?
hWinStatus dd ?
hWinEdit dd ?
lpsz1 dd ?
lpsz2 dd ?
.const
szClass db 'EDIT',0
szFormat0 db '%02d:%02d:%02d',0
szFormat1 db '字节数:%d',0
sz1 db '插入',0
sz2 db '改写',0
dwStatusWidth dd 60,140,172,-1
dwMenuHelp dd 0,IDM_MENUHELP,0,0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Resize proc
local @stRect:RECT,@stRect1:RECT
invoke MoveWindow,hWinStatus,0,0,0,0,TRUE
invoke GetWindowRect,hWinStatus,addr @stRect
invoke GetClientRect,hWinMain,addr @stRect1
mov ecx,@stRect1.right
sub ecx,@stRect1.left
mov eax,@stRect1.bottom
sub eax,@stRect1.top
sub eax,@stRect.bottom
add eax,@stRect.top
invoke MoveWindow,hWinEdit,0,0,ecx,eax,TRUE
ret
_Resize endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProcDlgMain proc uses ebx edi esi hWnd,wMsg,wParam,lParam
local @szBuffer[128]:byte
local @stST:SYSTEMTIME
local @stPoint:POINT,@stRect:RECT
mov eax,wMsg
;********************************************************************
.if eax == WM_TIMER
invoke GetLocalTime,addr @stST
movzx eax,@stST.wHour
movzx ebx,@stST.wMinute
movzx ecx,@stST.wSecond
invoke wsprintf,addr @szBuffer,addr szFormat0,\
eax,ebx,ecx
invoke SendMessage,hWinStatus,SB_SETTEXT,\
0,addr @szBuffer
;********************************************************************
.elseif eax == WM_CLOSE
invoke KillTimer,hWnd,1
invoke EndDialog,hWnd,NULL
;********************************************************************
.elseif eax == WM_INITDIALOG
mov eax,hWnd
mov hWinMain,eax
invoke CreateStatusWindow,WS_CHILD OR WS_VISIBLE OR \
SBS_SIZEGRIP,NULL,hWinMain,ID_STATUSBAR
mov hWinStatus,eax
invoke SendMessage,hWinStatus,SB_SETPARTS,\
4,offset dwStatusWidth
mov lpsz1,offset sz1
mov lpsz2,offset sz2
invoke SendMessage,hWinStatus,SB_SETTEXT,2,lpsz1
invoke CreateWindowEx,WS_EX_CLIENTEDGE,\
addr szClass,NULL,WS_CHILD or WS_VISIBLE or\
ES_MULTILINE or ES_WANTRETURN or WS_VSCROLL or\
ES_AUTOHSCROLL,\
0,0,0,0,hWnd,ID_EDIT,hInstance,NULL
mov hWinEdit,eax
call _Resize
invoke SetTimer,hWnd,1,300,NULL
;********************************************************************
.elseif eax == WM_COMMAND
mov eax,wParam
.if ax == IDM_EXIT
invoke EndDialog,hWnd,NULL
.elseif ax == ID_EDIT
invoke GetWindowTextLength,hWinEdit
invoke wsprintf,addr @szBuffer,\
addr szFormat1,eax
invoke SendMessage,hWinStatus,SB_SETTEXT,\
1,addr @szBuffer
.endif
;********************************************************************
.elseif eax == WM_MENUSELECT
invoke MenuHelp,WM_MENUSELECT,wParam,lParam,\
lParam,hInstance,hWinStatus,offset dwMenuHelp
.elseif eax == WM_SIZE
call _Resize
;********************************************************************
; 检测用户在第3栏的按鼠标动作并将文字在“插入”和“改写”之间切换
;********************************************************************
.elseif eax == WM_NOTIFY
.if wParam == ID_STATUSBAR
mov eax,lParam
mov eax,[eax + NMHDR.code]
.if eax == NM_CLICK
;********************************************************************
invoke GetCursorPos,addr @stPoint
invoke GetWindowRect,hWinStatus,addr @stRect
mov eax,@stRect.left
mov ecx,eax
add eax,140
add ecx,172
.if (@stPoint.x >= eax) &&(@stPoint.x <= ecx)
mov eax,lpsz1
xchg eax,lpsz2
mov lpsz1,eax
invoke SendMessage,hWinStatus,SB_SETTEXT,2,lpsz1
.endif
;********************************************************************
.endif
.endif
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
_ProcDlgMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
invoke InitCommonControls
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke DialogBoxParam,hInstance,DLG_MAIN,\
NULL,offset _ProcDlgMain,NULL
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
资源脚本文件StatusBar.rc的内容如下:
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#include
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#define ICO_MAIN 1000
#define DLG_MAIN 1000
#define IDM_MAIN 1000
#define IDM_OPEN 1101
#define IDM_SAVE AS 1102
#define IDM_PAGESETUP 1103
#define IDM_EXIT 1104
#define IDM_FIND 1201
#define IDM_REPLACE 1202
#define IDM_SELFONT 1203
#define IDM_SELCOLOR 1204
#define IDM_MENUHELP 1300
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN ICON "Main.ico"
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DLG_MAIN DIALOG 150, 180, 250, 130
MENU IDM_MAIN
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION |
WS_SYSMENU | WS_THICKFRAME
CAPTION "状态栏示例"
FONT 9, "宋体"
{
}
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
IDM_MAIN menu discardable
BEGIN
popup "文件(&F)"
BEGIN
menuitem "打开文件(&O)...",IDM_OPEN
menuitem "文件另存为(&C)...",IDM_SAVEAS
menuitem separator
menuitem "页面设置(&P)...",IDM_PAGESETUP
menuitem separator
menuitem "退出(&X)",IDM_EXIT
END
popup "查看(&V)"
BEGIN
menuitem "查找字符串(&S)...",IDM_FIND
menuitem "替换字符串(&R)...",IDM_REPLACE
menuitem separator
menuitem "选择字体(&F)...",IDM_SELFONT
menuitem "选择颜色(&B)...",IDM_SELCOLOR
END
END
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
stringtable discardable
BEGIN
IDM_MENUHELP "包含文件操作的命令"
IDM_MENUHELP+1 "包含操作视图的命令"
IDM_OPEN "打开需要编辑的文件"
IDM_SAVEAS "以另外一个文件名保存文件"
IDM_PAGESETUP "选择打印机以及设置页边距、纸张大小等打印参数"
IDM_EXIT "退出本程序"
IDM_FIND "在窗口中搜索文字"
IDM_REPLACE "在窗口中搜索文字并替换"
IDM_SELFONT "选择窗口中文字使用的字体"
IDM_SELCOLOR "选择窗口的背景颜色"
END
核心提示:工具栏一般位于主窗口菜单栏的下方。工具栏也是一个子窗口,它包含多个由位图组成的按钮,工具栏上的按钮从功能上看和菜单项是类似的,用户可以通过按动按钮来选择程序提供的各种功能。工具栏上可以有不同种类的按钮,有的按钮按下后会自动弹起,有的按钮按下后保留在“选中”状态,再按一次后恢复弹起状态,按钮的“选中”...
工具栏一般位于主窗口菜单栏的下方。工具栏也是一个子窗口,它包含多个由位图组成的按钮,工具栏上的按钮从功能上看和菜单项是类似的,用户可以通过按动按钮来选择程序提供的各种功能。
工具栏上可以有不同种类的按钮,有的按钮按下后会自动弹起,有的按钮按下后保留在“选中”状态,再按一次后恢复弹起状态,按钮的“选中”状态可以是不互斥的或是互斥的,另外,按钮也可以被灰化或隐藏。所有这些按钮的属性和菜单项的属性是非常相似的,所以工具栏往往用做菜单的补充,为用户提供一个快捷的程序功能选择方式。
由于工具栏的主要用途是当做菜单的补充,为了和菜单逻辑使用同一套代码,当用户按下工具栏上的按钮时,工具栏向父窗口发送WM_COMMAND消息,除了按动按钮的通知消息之外,工具栏同样使用WM_NOTIFY消息将其他动作通知父窗口,如用户拖动按钮来调整按钮的位置等。
工具栏上面的按钮看起来和对话框中的按钮很相似,但实际上它们不是真正的按钮,而仅是被工具栏控件绘画成按钮的样子罢了,也就是说,对话框中的按钮是子窗口,而工具栏上的按钮并不是子窗口,工具栏控件处理这些“仿真”按钮的方式就和一些图形界面的游戏一样,在屏幕上绘画“模拟”的按钮样子并自行处理用户的鼠标动作,以此检测用户在“按钮”上的动作。
本节的例子程序创建一个如图9.4所示的平面样式的工具栏,当鼠标箭头移动到按钮上面的时候,按钮会以凸起的形状显示,鼠标停留片刻后,会出现一条简短的工具提示信息(图中鼠标箭头下方显示的“新建文件”)。
该例子程序代码在所附光盘的Chapter09\Toolbar目录中,目录中包含汇编源文件Toolbar.asm以及资源脚本文件Toolbar.rc文件。
图9.4 工具栏例子的运行结果
Toolbar.asm文件的内容如下:
.386
.model flat, stdcall
option casemap :none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
include Comctl32.inc
includelib Comctl32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Equ 等值定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN equ 1000
IDM_MAIN equ 1000
IDM_NEW equ 1101
IDM_OPEN equ 1102
IDM_SAVE equ 1103
IDM_PAGESETUP equ 1104
IDM_PRINT equ 1105
IDM_EXIT equ 1106
IDM_CUT equ 1201
IDM_COPY equ 1202
IDM_PASTE equ 1203
IDM_FIND equ 1204
IDM_REPLACE equ 1205
IDM_HELP equ 1301
ID_TOOLBAR equ 1
ID_EDIT equ 2
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
hInstance dd ?
hWinMain dd ?
hMenu dd ?
hWinToolbar dd ?
hWinEdit dd ?
.const
szClass db 'EDIT',0
szClassName db 'ToolbarExample',0
szCaptionMain db '工具栏示例',0
szCaption db '命令消息',0
szFormat db '收到WM_COMMAND 消息,命令ID:%d',0
stToolbar equ this byte
TBBUTTON
TBBUTTON
TBBUTTON
TBBUTTON <0,0,TBSTATE_ENABLED,TBSTYLE_SEP,0,0,-1>
TBBUTTON
TBBUTTON
TBBUTTON <0,0,TBSTATE_ENABLED,TBSTYLE_SEP,0,0,-1>
TBBUTTON
TBBUTTON
TBBUTTON
TBBUTTON <0,0,TBSTATE_ENABLED,TBSTYLE_SEP,0,0,-1>
TBBUTTON
TBBUTTON
TBBUTTON <0,0,TBSTATE_ENABLED,TBSTYLE_SEP,0,0,-1>
TBBUTTON
TBBUTTON <0,0,TBSTATE_ENABLED,TBSTYLE_SEP,0,0,-1>
NUM_BUTTONS EQU 16
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_Resize proc
local @stRect:RECT,@stRect1:RECT
invoke SendMessage,hWinToolbar,TB_AUTOSIZE,0,0
invoke GetClientRect,hWinMain,addr @stRect
invoke GetWindowRect,hWinToolbar,addr @stRect1
mov eax,@stRect1.bottom
sub eax,@stRect1.top
mov ecx,@stRect.bottom
sub ecx,eax
invoke MoveWindow,hWinEdit,0,eax,@stRect.right,ecx,TRUE
ret
_Resize endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProcWinMain proc uses ebx edi esi hWnd,uMsg,wParam,lParam
local @szBuffer[128]:byte
mov eax,uMsg
;********************************************************************
.if eax == WM_CLOSE
invoke DestroyWindow,hWinMain
invoke PostQuitMessage,NULL
;********************************************************************
.elseif eax == WM_CREATE
mov eax,hWnd
mov hWinMain,eax
invoke CreateWindowEx,WS_EX_CLIENTEDGE,addr szClass,\
NULL,WS_CHILD or WS_VISIBLE or ES_MULTILINE \
or ES_WANTRETURN or WS_VSCROLL or\
ES_AUTOHSCROLL,0,0,0,0,\
hWnd,ID_EDIT,hInstance,NULL
mov hWinEdit,eax
invoke CreateToolbarEx,hWinMain,WS_VISIBLE or\
WS_CHILD or TBSTYLE_FLAT or TBSTYLE_TOOLTIPS or\
CCS_ADJUSTABLE,ID_TOOLBAR,0,HINST_COMMCTRL,\
IDB_STD_SMALL_COLOR,offset stToolbar,\
NUM_BUTTONS,0,0,0,0,sizeof TBBUTTON
Mov hWinToolbar,eax
Call _Resize
;********************************************************************
.elseif eax == WM_COMMAND
mov eax,wParam
.if ax == IDM_EXIT
invoke EndDialog,hWnd,NULL
.elseif ax != ID_EDIT
invoke wsprintf,addr @szBuffer,\
addr szFormat,wParam
invoke MessageBox,hWnd,addr @szBuffer,\
addr szCaption,\
MB_OK or MB_ICONINFORMATION
.endif
;********************************************************************
.elseif eax == WM_SIZE
call _Resize
;********************************************************************
; 处理用户定制工具栏消息
;********************************************************************
.elseif eax == WM_NOTIFY
mov ebx,lParam
;********************************************************************
; 因为印刷宽度,请注意缩进格式!
;********************************************************************
.if [ebx + NMHDR.code] == TTN_NEEDTEXT
assume ebx:ptr TOOLTIPTEXT
mov eax,[ebx].hdr.idFrom
mov [ebx].lpszText,eax
push hInstance
pop [ebx].hinst
assume ebx:nothing
.elseif ([ebx + NMHDR.code] == TBN_QUERYINSERT) || \
([ebx + NMHDR.code] == TBN_QUERYDELETE)
mov eax,TRUE
ret
.elseif [ebx + NMHDR.code] == TBN_GETBUTTONINFO
assume ebx:ptr TBNOTIFY
mov eax,[ebx].iItem
.if eax < NUM_BUTTONS
mov ecx,sizeof TBBUTTON
mul ecx
add eax,offset stToolbar
invoke RtlMoveMemory,addr [ebx].tbButton,eax,sizeof TBBUTTON
invoke LoadString,hInstance,[ebx].tbButton.idCommand,\
addr @szBuffer,sizeof @szBuffer
lea eax,@szBuffer
mov [ebx].pszText,eax
invoke lstrlen,addr @szBuffer
mov [ebx].cchText,eax
assume ebx:nothing
mov eax,TRUE
ret
.endif
.endif
;********************************************************************
.else
invoke DefWindowProc,hWnd,uMsg,wParam,lParam
ret
.endif
;********************************************************************
xor eax,eax
ret
_ProcWinMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_WinMain proc
local @stWndClass:WNDCLASSEX
local @stMsg:MSG
invoke InitCommonControls
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke LoadMenu,hInstance,IDM_MAIN
mov hMenu,eax
;********************************************************************
; 注册窗口类
;********************************************************************
invoke RtlZeroMemory,addr @stWndClass,sizeof @stWndClass
invoke LoadIcon,hInstance,ICO_MAIN
mov @stWndClass.hIcon,eax
mov @stWndClass.hIconSm,eax
invoke LoadCursor,0,IDC_ARROW
mov @stWndClass.hCursor,eax
push hInstance
pop @stWndClass.hInstance
mov @stWndClass.cbSize,sizeof WNDCLASSEX
mov @stWndClass.style,CS_HREDRAW or CS_VREDRAW
mov @stWndClass.lpfnWndProc,offset _ProcWinMain
mov @stWndClass.hbrBackground,COLOR_BTNFACE+1
mov @stWndClass.lpszClassName,offset szClassName
invoke RegisterClassEx,addr @stWndClass
;********************************************************************
; 建立并显示窗口
;********************************************************************
invoke CreateWindowEx,NULL,\
offset szClassName,offset szCaptionMain,\
WS_OVERLAPPEDWINDOW,\
CW_USEDEFAULT,CW_USEDEFAULT,700,500,\
NULL,hMenu,hInstance,NULL
mov hWinMain,eax
invoke ShowWindow,hWinMain,SW_SHOWNORMAL
invoke UpdateWindow,hWinMain
;********************************************************************
; 消息循环
;********************************************************************
.while TRUE
invoke GetMessage,addr @stMsg,NULL,0,0
.break .if eax == 0
invoke TranslateMessage,addr @stMsg
invoke DispatchMessage,addr @stMsg
.endw
ret
_WinMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
call _WinMain
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
资源脚本文件Toolbar.rc的内容如下:
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#include
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#define ICO_MAIN 1000
#define IDM_MAIN 1000
#define IDM_NEW 1101
#define IDM_OPEN 1102
#define IDM_SAVE 1103
#define IDM_PAGESETUP 1104
#define IDM_PRINT 1105
#define IDM_EXIT 1106
#define IDM_CUT 1201
#define IDM_COPY 1202
#define IDM_PASTE 1203
#define IDM_FIND 1204
#define IDM_REPLACE 1205
#define IDM_HELP 1301
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN ICON "Main.ico"
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
IDM_MAIN menu discardable
BEGIN
popup "文件(&F)"
BEGIN
menuitem "新文件(&N)...", IDM_NEW
menuitem "打开文件(&O)...", IDM_OPEN
menuitem "保存文件(&S)", IDM_SAVE
menuitem separator
menuitem "页面设置(&A)...", IDM_PAGESETUP
menuitem "打印(&P)...", IDM_PRINT
menuitem separator
menuitem "退出(&X)", IDM_EXIT
END
popup "编辑(&E)"
BEGIN
menuitem "剪切(&U)", IDM_CUT
menuitem "拷贝(&C)", IDM_COPY
menuitem "粘贴(&P)", IDM_PASTE
menuitem separator
menuitem "查找字符串(&S)...",IDM_FIND
menuitem "替换字符串(&R)...",IDM_REPLACE
END
popup "帮助(&H)"
BEGIN
Menuitem "帮助(&H)", IDM_HELP
END
END
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
stringtable discardable
BEGIN
IDM_NEW "新建文件"
IDM_OPEN "打开文件"
IDM_SAVE "保存文件"
IDM_PAGESETUP "页面设置"
IDM_PRINT "打印"
IDM_COPY "拷贝"
IDM_CUT "剪切"
IDM_PASTE "粘贴"
IDM_FIND "查找"
IDM_REPLACE "替换"
IDM_HELP "帮助"
END
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
资源脚本文件中定义了一些菜单,和状态栏例子程序一样,这些菜单也是仅用来做演示用途的,源程序中并没有书写处理菜单项的代码。
字符串表中定义的字符串是供工具提示信息使用的。
例子程序在主窗口的WM_CREATE消息中使用CreateToolbarEx函数建立工具栏。可以注意到,主程序建立了一个标准的窗口,而不是使用对话框做主窗口,这是为什么呢?这是因为在对话框中无法实现工具栏的高级功能,原因如下:
对话框过程的返回值是用来通知“对话框管理器”是否处理了相关消息的,这个返回值并不会被对话框管理器返回到工具栏子窗口去,对于大部分的控件来说,向父窗口发送WM_NOTIFY消息时并不需要父窗口回应一个返回值,但对于工具栏来说,父窗口必须根据WM_NOTIFY消息的处理情况返回TRUE或FALSE,工具栏要根据返回值再做不同的动作,如果返回值无法返回,就意味着工具栏无法做正确的动作。
所以在对话框中是无法使用工具栏的高级功能(如用户定制按钮等)的,但仅为了使用对话框的按钮当做菜单快捷按钮的情况不在此列,因为这时只需要处理WM_COMMAND消息,WM_COMMAND消息并不需要返回一个值。
例子程序中要演示用户定制工具栏按钮的功能,无法使用对话框当做主窗口,所以程序建立了一个常规的窗口。
9.3.1 创建工具栏
创建工具栏的专用函数是CreateToolbarEx,使用CreateWindowEx函数利用类名“ToolbarWindow32”也可以创建工具栏,但CreateWindowEx函数仅创建一个空的工具栏,在创建完成后还要初始化工具栏以及分多次插入按钮,而CreateToolbarEx函数可以一次创建工具栏以及上面的全部按钮。
CreateToolbarEx函数的用法是:
invoke CreateToolbarEx,hwnd,ws,wID,nBitmaps,hBMInst,wBMID,lpButtons,\
iNumButtons,dxButton,dyButton,dxBitmap,dyBitmap,uStructSize
mov hToolbar,eax
各参数的说明如下。
hwnd参数是父窗口的句柄,ws是工具栏的风格,wID是工具栏的子窗口ID,这几个参数也可以用在使用CreateWindowEx函数创建工具栏的调用中,ws参数使用的窗口风格必须包括WS_CHILD和WS_VISIBLE,另外还可以组合使用下面的特殊风格:
WIN32汇编(工具栏2)
时间:2009-7-24 11:24:02 点击:130
核心提示:● TBSTYLE_FLAT——按钮的样式为平面样式,如果指定TBSTYLE_FLAT风格,创建的工具栏如图9.5上方的工具栏所示,如果不指定这个风格,则创建如图9.5下方所示的传统样式的工具栏。● CCS_NODIVIDER——工具栏边上没有分隔线,如果指定这个风格,那么图9.5中就不会有数字(1...
● TBSTYLE_FLAT——按钮的样式为平面样式,如果指定TBSTYLE_FLAT风格,创建的工具栏如图9.5上方的工具栏所示,如果不指定这个风格,则创建如图9.5下方所示的传统样式的工具栏。
● CCS_NODIVIDER——工具栏边上没有分隔线,如果指定这个风格,那么图9.5中就不会有数字(1)标出的横线。
● TBSTYLE_WRAPABLE——工具栏支持多行显示。
● CCS_TOP,CCS_BOTTOM或CCS_NOMOVEY——指定工具栏的位置,分别表示工具栏位于窗口的上方(默认风格)、底部和不会自动在垂直方向移动。
● CCS_NOPARENTALIGN——工具栏自动设置自己的高度,但不自动设置宽度和位置。
● CCS_NORESIZE——禁止工具栏的自动缩放功能,相当于禁止了上面的CCS_TOP、CCS_BOTTOM,CCS_NOMOVEY和CCS_NOPARENTALIGN风格。
● CCS_ADJUSTABLE——允许用户在按下Shift键的同时通过拖动工具栏上的按钮来调整按钮位置以及删除按钮。
● TBSTYLE_ALTDRAG——对CCS_ADJUSTABLE风格的工具栏将拖动时的按键由Shift键改为Alt键。
● TBSTYLE_TOOLTIPS——工具栏支持工具提示信息。
图9.5 工具栏的风格
hBMInst和wBMID参数用来指定绘画工具栏上的按钮使用的位图,位图可以在资源中定义也可以是已经装入内存的位图。如果使用资源中的位图,那么hBMInst指定包含位图资源的模块的实例句柄,wBMID指定位图资源的ID;如果hBMInst指定为NULL,那就表示要使用一个已经装入内存的位图,这时wBMID必须指定一个合法的位图句柄。
指定了使用的位图以后,nBitmaps,dxBitmap和dyBitmap参数继续给出了位图的属性,工具栏中的每个按钮并不使用一幅单独的位图,而是所有按钮的位图水平排列在一起组成一幅大的位图,nBitmaps参数说明了这幅大位图中包含多少个按钮位图,dxBitmap和dyBitmap参数指出了单个按钮位图的宽度和高度。显然,整个位图的高度就等于dyBitmap,宽度等于nBitmaps乘以dxBitmap。
图9.6是一幅典型的在工具栏中使用的位图,位图中包含了15个按钮。由于工具栏按照位置来分隔并使用位图,所以位图中各按钮的位置必须严格按照尺寸等距离排列,而且位图中不同按钮位图的尺寸必须是相同的。
图9.6 工具栏使用的位图
除了使用自定义的位图以外,Comctl32.dll库文件中也提供一些通用的位图供工具栏使用。为了使用这组位图,可以在hBMInst参数中使用预定义的模块实例句柄HINST_COMMCTRL。
Comctl32.dll包含了两组可以使用的位图。第一组就是如图9.6所示的位图,可以在wBMID中指定IDB_STD_LARGE_COLOR(24×24像素)或IDB_STD_SMALL_COLOR(16×16像素)来引用它们。这组位图包括15个按钮,Windows.inc文件中已经为每个按钮的位置索引定义了预定义值,从左到右分别是STD_CUT,STD_COPY,STD_PASTE,STD_UNDO,STD_REDOW(不知道REDO后面为什么有个W),STD_DELETE,STD_FILENEW,STD_FILEOPEN,STD_FILESAVE,STD_PRINTPRE,STD_PROPERTIES,STD_HELP,STD_FIND,STD_REPLACE和STD_PRINT。
第二组位图可以在wBMID中使用IDB_VIEW_LARGE_COLOR或IDB_VIEW_SMALL_COLOR来引用它们,这个位图包含12个按钮图像,索引值0~11分别被预定义为VIEW_LARGEICONS,VIEW_SMALLICONS,VIEW_LIST,VIEW_DETAIL,VIEW_SORTNAME,VIEW_SORTSIZE,VIEW_SORTDATE,VIEW_SORTTYPE,VIEW _PARENTFOLDER,VIEW_NETCONNECT,VIEW_NETDISCONNECT和VIEW _NEWFOLDER,读者从字面上就可以想出这些位图究竟是什么样的。
参数dxButton和dyButton指定工具栏上按钮的尺寸,按钮的尺寸一般比按钮图像的尺寸要大一点。
剩余的3个参数lpButtons,iNumButtons和uStructSize用来定义工具栏上的按钮,函数根据它们给出的数据创建工具栏上的全部按钮。lpButtons参数指向一组按顺序排列的TBBUTTON结构,每个TBBUTTON结构定义一个按钮,TBBUTTON结构的排列顺序决定了按钮在工具栏上的排列顺序;iNumButtons参数指定工具栏上的按钮总数,也就是lpButtons指向的数据中包含的TBBUTTON结构的总数;uStructSize参数指明TBBUTTON结构的长度。
TBBUTTON结构的定义如下:
TBBUTTON STRUCT
iBitmap DWORD ? ;按钮使用的位图编号
idCommand DWORD ? ;按钮按下时在WM_COMMAND中使用的ID
fsState BYTE ? ;按钮状态
fsStyle BYTE ? ;按钮风格
_wPad1 WORD ? ;
dwData DWORD ? ;自定义数据
iString DWORD ? ;按钮字符串索引
TBBUTTON ENDS
上面的结构定义取自MASM32软件包所附带的Windows.inc文件,但是Microsoft Win32 API手册中的结构定义并没有_wPad1字段,现在并没有资料判定Windows.inc中的定义是否正确,但这个定义在实际使用中并不会出错,所以本书中的例子沿用这个定义,这一点请读者注意。结构中各字段的含义如下。
● iBitmap——按钮使用的图像在wBMID 参数指定的位图中的位置索引。位置索引从0开始,也就是说第一个按钮图像的位置索引是0。
● idCommand——按下按钮以后,工具栏会向父窗口发送WM_COMMAND消息,这个字段指定消息附带的命令ID号,一般在这里指定与按钮对应的菜单项使用的ID。
● fsState——按钮的类型和初始状态,可以是下面取值的组合:
■ TBSTATE_CHECKED——按钮的类型是复选框按钮,并且按钮初始化为选中状态(即保持按下状态)。
■ TBSTATE_ENABLED——按钮被允许,如果不指定这个标志,按钮将显示为灰色,并且不会接收用户的动作。
■ TBSTATE_HIDDEN——隐藏状态,按钮不显示在工具栏上。
■ TBSTATE_INDETERMINATE——按钮处于灰化状态,但可以接收用户的动作。
■ TBSTATE_PRESSED——按钮处于按下状态。
■ TBSTATE_WRAP——在包含TBSTYLE_WRAPABLE风格的多行工具栏中,从此按钮开始换行。
● fsStyle——按钮风格,可以是下面取值的组合:
■ TBSTYLE_BUTTON——标准按钮。
■ TBSTYLE_CHECK——复选框按钮(按钮状态在按下和凸起之间切换)。
■ TBSTYLE_GROUP——指定复选框按钮的分组边界。
■ TBSTYLE_CHECKGROUP——TBSTYLE_CHECK风格和TBSTYLE_GROUP风格的组合。
■ TBSTYLE_SEP——按钮之间的分隔线。
● dwData——用户自定义数据。设置后可以通过TB_GETBUTTON消息查询。
● iString——按钮标记的索引。
在例子程序中,使用模块句柄HINST_COMMCTRL和位图句柄IDB_STD_SMALL _COLOR来指定使用Comctl32.dll中的预定义位图,并预定义了16个TBBUTTON结构,存放在常量stToolbar开始的地址中。
当使用预定义位图的时候,函数自己知道位图的大小和按钮的大小,所以位图和按钮的尺寸参数都可以设置为0。函数的返回值是工具栏窗口的句柄,把它存放到hWinToolbar变量中以便在以后使用。
有关代码如下:
...
stToolbar equ this byte
TBBUTTON
TBBUTTON
TBBUTTON
TBBUTTON <0,0,TBSTATE_ENABLED,TBSTYLE_SEP,0,0,-1>
TBBUTTON
...
NUM_BUTTONS EQU 16
...
invoke CreateToolbarEx,hWinMain,WS_VISIBLE or WS_CHILD or TBSTYLE_FLAT \
or TBSTYLE_TOOLTIPS or CCS_ADJUSTABLE,ID_TOOLBAR,0,HINST_COMMCTRL,\
IDB_STD_SMALL_COLOR,offset stToolbar,NUM_BUTTONS,\
0,0,0,0,sizeof TBBUTTON
mov hWinToolbar,eax
9.3.2 工具栏的控制消息
程序可以通过向工具栏控件发送消息来控制工具栏,工具栏的控制消息比较多,下面分类进行讨论。
1. 工具栏的创建和维护消息
如果使用CreateWindowEx函数来创建工具栏,那么创建的是一个空白的工具栏,还需要对工具栏进行初始化。初始化的工作包括指定位图、指定TBBUTTON结构长度和添加按钮。
指定工具栏使用的位图使用TB_ADDBITMAP消息:
invoke SendMessage,hToolbar,TB_ADDBITMAP,nButtons,lptbab
wParam中的nButtons指定位图中包含的按钮图像数量,lptbab指向一个TBADDBITMAP结构,结构中定义了两个字段:位图资源的模块句柄和位图ID,这两个字段的定义方法和CreateToolbarEx函数的hBMInst和wBMID参数的定义方法是一样的。结构定义如下:
TBADDBITMAP STRUCT
hInst DWORD ? ;包含位图的模块实例句柄
nID DWORD ? ;位图资源的ID
TBADDBITMAP ENDS
指定了位图以后,需要发送TB_SETBITMAPSIZE和TB_SETBUTTONSIZE消息来指定按钮图像的大小和按钮的大小,这一步相当于指定CreateToolbarEx函数中使用的dxButton,dyButton,dxBitmap和dyBitmap参数。
宽度和高度参数分别由lParam参数的高16位和低16位指定:
invoke SendMessage,hToolbar,TB_SETBITMAPSIZE,0,dwWidth + dwHeight shl 16
invoke SendMessage,hToolbar,TB_SETBUTTONSIZE,0,dwWidth + dwHeight shl 16
CreateToolbarEx函数的最后一个参数uStructSize是为了向系统通知TBBUTTON的结构长度,如果使用CreateWindowEx函数来创建工具栏,那么这一步必须通过发送TB_BUTTONSTRUCTSIZE消息来完成:
invoke SendMessage,hToolbar,TB_BUTTONSTRUCTSIZE,sizeof TBBUTTON,0
接下来可以使用TB_ADDBUTTONS消息来添加按钮,uNumButtons参数指定要添加的按钮数量,lpButtons参数指向一组TBBUTTON结构,结构的数量和uNumButtons参数相对应,在发送这个消息前必须先发送TB_BUTTONSTRUCTSIZE消息指定结构的长度:
invoke SendMessage,hToolbar,TB_ADDBUTTONS,uNumButtons,lpButtons
TB_ADDBUTTONS消息总是在工具栏的最后添加按钮,使用TB_INSERTBUTTON消息可以在几个按钮的中间插入新的按钮,但一次只能插入一个按钮,消息的wParam参数指定插入位置,lParam消息指向一个TBBUTTON结构:
invoke SendMessage,hToolbar,TB_INSERTBUTTON,iButtons,lpButton
当然,如果使用CreateToolbarEx函数创建工具栏,那么上面的步骤就全部由函数包办了,这就是使用专用函数的好处。
也可以通过发送TB_DELETEBUTTON消息来删除工具栏上的按钮,iButton参数指定按钮的位置索引,第一个按钮用0表示:
invoke SendMessage,hToolbar,TB_DELETEBUTTON,iButton,0
除了这些消息,还有一些消息可以用来获取工具栏的当前状态,如表9.3所示。
表9.3 获取工具栏状态的消息
消 息
WParam
Iparam
说 明
TB_BUTTONCOUNT
0
0
返回工具栏上按钮的数量
TB_GETBITMAP
idButton
0
返回指定按钮的图像索引
TB_GETBUTTON
iButton
lpButton
返回指定按钮的TBBUTTON结构
TB_GETROWS
0
0
返回多行工具栏当前包含的行数
TB_GETITEMRECT
iButton
lpRect
在lParam指定的位置返回包含指定按钮
的位置的RECT结构
所有消息的参数中,iButton指按钮的位置索引,idButton指按钮的命令ID值。
2. 移动和缩放工具栏
用默认参数建立的工具栏能够自动移动和缩放大小,当主窗口的宽度变宽的时候,即使不对工具栏进行调整,工具栏的宽度还是会自动扩展到父窗口的宽度。但有个小缺陷就是工具栏自动变宽的时候,图9.5中数字(1)所示的分隔线却不会自动变长,结果工具栏的外观似乎不是很好看,所以在主窗口的WM_SIZE消息中还是需要对工具栏进行调整。不过调整的方法很简单,只要对工具栏发送TB_AUTOSIZE消息就可以了:
invoke SendMessage,hToolbar,TB_AUTOSIZE,0,0
消息中不必指定位置和大小参数,工具栏会自动计算新的大小,消息发送以后分隔线也会被调整到正确的宽度,一切看起来就完美了。
3. 工具栏按钮的维护消息
工具栏按钮可以像菜单项一样有选中、允许和灰化等状态,在程序中可以使用一组TB_ISBUTTONxxxx类型的消息来检测按钮的状态:
invoke SendMessage,hToolbar,TB_ISBUTTONCHECKED,idButton,0 ;是否在选中状态
invoke SendMessage,hToolbar,TB_ISBUTTONENABLED,idButton,0 ;是否在允许状态
invoke SendMessage,hToolbar,TB_ISBUTTONHIDDEN,idButton,0 ;是否在隐藏状态
invoke SendMessage,hToolbar,TB_ISBUTTONINDETERMINATE,idButton,0 ;是否灰化
invoke SendMessage,hToolbar,TB_ISBUTTONPRESSED,idButton,0 ;是否在按下状态
对于上面这些消息,如果答案是肯定的,那么消息返回TRUE,否则消息返回FALSE。如果嫌每次调用只能检测一种状态显得比较麻烦,也可以发送TB_GETSTATE消息:
invoke SendMessage,hToolbar,TB_GETSTATE,idButton,0
函数会返回按钮所有状态的组合值(TBSTATE_INDETERMINATE,TBSTATE _CHECKED,TBSTATE_ENABLED,TBSTATE_HIDDEN或TBSTATE_PRESSED等状态的组合)。在上面这些消息中,idButton用来指定按钮对应的命令ID。
设置按钮的状态也可以通过一组消息来完成:
invoke SendMessage,hToolbar,TB_CHECKBUTTON,idButton,uState ;选中按钮
invoke SendMessage,hToolbar,TB_ENABLEBUTTON,idButton,uState ;允许按钮
invoke SendMessage,hToolbar,TB_HIDEBUTTON,idButton,uState ;隐藏按钮
invoke SendMessage,hToolbar,TB_PRESSBUTTON,idButton,uState ;按下按钮
对于这些消息,如果uState指定为TRUE,那么按钮会分别被设置为选中、允许、隐藏和按下状态;如果uState指定为FALSE,按钮会被设置为非选中、灰化、显示和凸起的状态。
同样,要一次性设置所有状态,可以发送TB_SETSTATE消息:
invoke SendMessage,hToolbar,TB_PRESSBUTTON,idButton,uState
uState参数可以指定为TBSTATE_INDETERMINATE,TBSTATE_CHECKED,TBSTATE _ENABLED,TBSTATE_HIDDEN或TBSTATE_PRESSED等按钮状态的组合值。
9.3.3 工具栏的通知消息
大部分通用控件向父窗口发送的通知消息是WM_NOTIFY,为了便于和菜单消息使用同一段命令处理的逻辑代码,当按动工具栏按钮的时候,工具栏控件向父窗口发送的是WM_COMMAND消息,但其他情况下发送的通知消息仍然是WM_NOTIFY消息。
工具栏发送的WM_NOTIFY消息主要用于显示工具提示和定制工具栏。
1. 工具提示
当工具栏的风格包含TBSTYLE_TOOLTIPS的时候,CreateToolbarEx函数自动创建一个工具提示控件(Tool Tip),并为工具栏上的每个按钮注册提示文本,当鼠标指针移动到按钮上并停留片刻的时候,工具提示信息会自动显示出来。
工具提示信息是工具提示控件通过包含TTN_NEEDTEXT通知码的WM_NOTIFY消息向父窗口索取的,所以这个WM_NOTIFY消息严格地说应该属于工具提示控件的通知消息而不是工具栏的通知消息,但这里的工具提示控件是CreateToolbarEx函数自动创建的,所以还是一起介绍。
在包含TTN_NEEDTEXT通知码的WM_NOTIFY消息中,lParam指向一个TOOLTIPTEXT结构——慢着!前面不是说WM_NOTIFY消息的lParam参数指向一个NMHDR吗?怎么又是TOOLTIPTEXT结构呢?由于不同控件的通知消息都使用WM_NOTIFY消息,有些通知消息可能需要附带其他数据,这时仅使用一个NMHDR结构来表达是不够的,Windows的处理办法是为需要附带其他数据的WM_NOTIFY消息定义不同的数据结构,但这些结构头部都是一个NMHDR结构,NMHDR结构以后才是其他字段,这样在得知通知码之前,把lParam参数指针当做一个NMHDR结构来处理总是正确的。而且只有先把lParam参数指针当做NMHDR结构处理并从中获取通知码以后,才真正知道lParam指向的究竟是什么结构。
好了,问题解决了,言归正传。TTN_NEEDTEXT通知码的lParam指向一个TOOLTIPTEXT结构,这个结构的定义是:
TOOLTIPTEXT STRUCT
hdr NMHDR <> ;头部位置是一个NMHDR结构
lpszText DWORD ? ;工具提示字符串指针
szText BYTE 80 dup (?) ;工具提示字符串缓冲区
hInst DWORD ? ;包含字符串资源的模块句柄
uFlags DWORD ? ;标志
TOOLTIPTEXT ENDS
当需要显示工具提示信息的时候,工具提示控件向父窗口发送TTN_NEEDTEXT通知码,父窗口将需要显示的提示字符串放在TOOLTIPTEXT结构中并返回以后,工具提示控件就会把它显示出来。设置TOOLTIPTEXT结构的办法有3种,读者可以任选其一:
(1)字符串包含在资源中,这时可以将hInst字段设置为包含资源的模块句柄,并把lpszText字段设置为字符串ID,其他字段保持为NULL,工具提示会自己使用LoadString函数装入字符串。
(2)将字符串放在内存中,将内存指针放入lpszText字段中,其他字段保持NULL。
(3)将字符串拷入szText字段中,其他字段保持NULL。
例子程序使用了第一种办法。由于NMHDR结构的idFrom字段已经返回了按钮的命令ID,所以在资源脚本文件中将字符串的ID和命令ID一一对应定义,然后使用第一种方法是最方便的,代码如下:
.elseif eax == WM_NOTIFY
mov ebx,lParam
.if [ebx + NMHDR.code] == TTN_NEEDTEXT
assume ebx:ptr TOOLTIPTEXT
mov eax,[ebx].hdr.idFrom
mov [ebx].lpszText,eax
push hInstance
pop [ebx].hinst
assume ebx:nothing
...
读者可以自己尝试一下其他的方法。
心提示:2. 定制工具栏定制功能是工具栏中最令人兴奋的特征:当工具栏包含CCS_ADJUSTABLE风格的时候,用户可以通过按下Shift键并拖动工具栏上的按钮来移动按钮位置;如果将按钮拖出工具栏的边界,按钮会被删除;更重要的是,如果向工具栏发送TB_CUSTOMIZE消息或者在工具栏的空白处双击鼠标,会显...
2. 定制工具栏
定制功能是工具栏中最令人兴奋的特征:当工具栏包含CCS_ADJUSTABLE风格的时候,用户可以通过按下Shift键并拖动工具栏上的按钮来移动按钮位置;如果将按钮拖出工具栏的边界,按钮会被删除;更重要的是,如果向工具栏发送TB_CUSTOMIZE消息或者在工具栏的空白处双击鼠标,会显示出一个如图9.7所示的“自定义工具栏”对话框,对话框右边的列表框中列出了当前显示在工具栏上的按钮,左边列表框列出了可以添加到工具栏上的按钮,用户可以将一个按钮随意在使用和不使用之间切换,并且可以通过拖动按钮的上下位置来决定按钮在工具栏上的位置。
工具栏通过一系列的通知信息来和父窗口交互,共同维护“自定义工具栏”对话框,在这个对话框建立和关闭的时候,工具栏通过TBN_BEGINADJUST和TBN_ENDADJUST通知码来通知父窗口;每次按钮被调整的时候,发送的是TBN_TOOLBARCHANGE通知码;在按下对话框中的“帮助”按钮和“重置”按钮的时候,发送的是TBN_CUSTHELP和TBN_RESET通知码,对于这些通知码,父窗口可以不必响应,这并不会影响对话框的使用。
与对话框是否能够正常运行有关的通知码是TBN_QUERYINSERT,TBN_QUERYDELETE和TBN_GETBUTTONINFO,父窗口必须应答这些通知码。
图9.7 自定义工具栏对话框
当用户在指定位置插入一个按钮的时候,工具栏发送TBN_QUERYINSERT通知码询问父窗口是否允许此操作,这时lParam指向一个TBNOTIFY结构,这个结构定义如下:
TBNOTIFY STRUCT
hdr NMHDR <> ;显然,这里肯定是NMHDR结构
iItem DWORD ? ;按钮的位置索引
tbButton TBBUTTON <> ;包含按钮信息的TBBUTTON结构
cchText DWORD ? ;pszText中字符串的长度
pszText DWORD ? ;按钮的说明字符串
TBNOTIFY ENDS
如果程序允许在此按钮前面插入一个新按钮,那么返回TRUE,否则返回FALSE。另外当自定义对话框刚显示的时候,父窗口也会收到这个通知码,这时必须返回TRUE,否则对话框在屏幕上一闪就消失了。
当用户要删除一个按钮的时候,工具栏发送TBN_QUERYDELETE通知码,询问父窗口是否允许此操作,这时lParam也指向一个TBNOTIFY结构,用来说明将要删除的按钮,如果程序允许此操作则返回TRUE,否则返回FALSE。
.elseif ([ebx + NMHDR.code] == TBN_QUERYINSERT) || \
([ebx + NMHDR.code] == TBN_QUERYDELETE) ;现在ebx = lParam
mov eax,TRUE
ret
在例子程序中使用上面的代码来处理这两个通知码,也就是说对于全部的情况均返回TRUE,表示允许用户随意进行移动按钮和删除按钮的操作。
TBN_GETBUTTONINFO通知码的处理就比较复杂了,当工具栏需要全部按钮的信息的时候,会多次发送TBN_GETBUTTONINFO通知码,在例子程序中是这样处理的:
.elseif [ebx + NMHDR.code] == TBN_GETBUTTONINFO ;现在ebx = lParam
assume ebx:ptr TBNOTIFY ;lParam也是指向一个TBNOTIFY结构
mov eax,[ebx].iItem
.if eax < NUM_BUTTONS
mov ecx,sizeof TBBUTTON
mul ecx
add eax,offset stToolbar
invoke RtlMoveMemory,addr [ebx].tbButton,eax,sizeof TBBUTTON
invoke LoadString,hInstance,[ebx].tbButton.idCommand,\
addr @szBuffer,sizeof @szBuffer
lea eax,@szBuffer
mov [ebx].pszText,eax
invoke lstrlen,addr @szBuffer
mov [ebx].cchText,eax
assume ebx:nothing
mov eax,TRUE
ret
.endif
首先来分析为什么要这样处理TBN_GETBUTTONINFO通知码。
工具栏控件每次总是发送一组TBN_GETBUTTONINFO通知码,并且每次TBNOTIFY结构中的iItem字段递增,父窗口需要每次在结构中返回一个按钮的信息,如果还有剩余的按钮信息没有告诉工具栏(比如在用按钮和可选按钮加起来总共有15个,现在返回了10个,那么还剩5个按钮信息没有告诉工具栏),则在消息的返回值中返回TRUE,工具栏由此知道还有多余的按钮,于是马上将iItem字段加1再次发送TBN_GETBUTTONINFO通知码,如此循环直到某一次消息的返回值是FALSE为止。
为什么工具栏不知道需要获取的按钮的数量,而需要由父窗口来确定呢?这是因为工具栏只维护栏上现存的按钮,当工具栏上当前有10个按钮的时候,如果在一组TBN_GETBUTTONINFO通知码中返回了15个按钮,这15个按钮中包括了已经在使用的10个按钮和可以添加上去的另外5个按钮,那么工具栏就会将这15个按钮和栏上现存的所有按钮比较,并把现存的10个按钮放在“自定义工具栏”对话框的右边,把剩余的5个放在对话框的左边。
在例子中可用的按钮总共是16个,如果在初始化的时候只需要显示前面10个按钮,那么在使用CreateToolbarEx函数的时候可以只指定10个按钮,在这种情况下,当定制工具栏时在一组TBN_GETBUTTONINFO通知码中返回全部16个按钮的时候,多余的6个按钮就会出现在对话框的左边。
另外,TBNOTIFY结构的pszText需要返回按钮的说明文字,否则对话框中左右两个列表框中只会显示按钮图像而没有说明文字。程序在这里使用和工具提示信息同样的文字,这些文字存放在资源中,所以例子代码从TBNOTIFY结构包含的TBBUTTON结构中取出idCommand字段,使用LoadString函数从资源中读取以idCommand为ID的字符串并将其放入pszText所指的缓冲区中,最后使用lstrlen函数求出字符串的长度并放入cchText字段中,这样对话框的列表框中就可以显示出按钮的名称字符串了。
WIN32汇编(窗口子类化)
时间:2009-8-7 18:07:20 点击:53
核心提示:9.5.1 什么是窗口的子类化 在使用控件的过程中,常常会遇到这样一种情况:我们需要一种窗口,它和某种现成的控件提供的功能很相似,如果使用现成控件的话,那么控件几乎能提供所有需要的功能,仅我们要求的某个细节无法实现。举例来说,要编写一个16进制与10进制的转换程序,程序中需要两个编辑控件来输入数值,...
9.5.1 什么是窗口的子类化
在使用控件的过程中,常常会遇到这样一种情况:我们需要一种窗口,它和某种现成的控件提供的功能很相似,如果使用现成控件的话,那么控件几乎能提供所有需要的功能,仅我们要求的某个细节无法实现。举例来说,要编写一个16进制与10进制的转换程序,程序中需要两个编辑控件来输入数值,输入10进制数值可以使用现成的Edit控件,只要指定ES_NUMBER风格就能让编辑框只能输入数字0~9,但输入16进制数值的时候就不行了,因为指定ES_NUMBER风格的话就无法输入A~F,不指定的话用户就可能输入0~9和A~F之外的东西,那么该如何处理呢?
解决的办法有两种,第一种当然是自己创建一个窗口类,然后在自己的窗口过程中完成所有的功能,这显然是一项费时又费力的工作,因为我们几乎要自己重新写一遍Edit控件的全部功能;第二种方法就是使用本节要介绍的窗口子类化,窗口子类化最适合做的就是这一类工作。
窗口子类化的含义是接管被子类化的控件窗口,以达到对它进行控制的目的。虽然控件的窗口过程被封装在Windows内部,无法对它进行直接修改,但只要能截获Windows给控件的窗口过程发送的消息,就能够控制控件窗口。以上面的要求为例,只要截获Windows向编辑控件发送的WM_CHAR消息,就能够根据需要丢弃包含非16进制字符的WM_CHAR消息,只把包含16进制字符的WM_CHAR转发给控件的窗口过程,这样编辑控件将根本收不到16进制字符之外的字符,我们的要求也就达到了。
控件窗口子类化的流程如图9.8所示。
子类化的操作并不局限于控件窗口,实际上任何窗口都可以子类化。但是对于应用程序自身使用的窗口类来说,它的控制权本来就是100%属于应用程序自身的,要实现某种功能就直接修改源代码好了,没有必要再进行一个子类化的过程,所以子类化的操作往往是对“黑匣子”类型的控件窗口进行的。
图9.8 窗口子类化的工作原理
9.5.2 窗口子类化的实现
窗口子类化的要点是截获窗口的窗口过程,如何实现这一点呢?每个窗口的内部都保存有它所属的窗口类的WNDCLASSEX 结构,结构中的lpfnWndProc字段指出了窗口过程的地址,如果能用自己的窗口过程地址来替换这个地址,那么Windows就会把消息发送到自定义的窗口过程中来了。通过调用函数SetWindowLong可以实现这个功能,SetWindowLong函数的用法是这样的:
invoke SetWindowLong,hWnd,nIndex,dwNewLong
mov dwOldLong
hWnd参数指定要子类化窗口的窗口句柄,nIndex参数指定需要修改窗口的哪个属性,它可以是以下的取值:
● GWL_EXSTYLE——窗口的扩展风格。
● GWL_STYLE——窗口风格。
● GWL_WNDPROC——窗口过程地址(这就是我们需要的)。
● GWL_HINSTANCE——窗口所属的模块实例句柄。
● GWL_ID——窗口ID。
● GWL_USERDATA——窗口附带的32位自定义数值。
dwNewLong参数指定新的属性值。如果nIndex为GWL_WNDPROC,dwNewLong表示新的窗口过程地址;如果nIndex为GWL_STYLE,dwNewLong则表示新的窗口风格,依此类推。函数的返回值是指定属性的原先数值。当函数用于窗口子类化的时候,在nIndex参数中使用GWL_WNDPROC,以便将窗口过程地址设置到自定义的子程序中,这时函数返回的是控件窗口原来的窗口过程地址,由于窗口子类化的出发点就是为了尽量使用控件窗口原有的功能,程序为了“偷懒”而不去处理的大部分消息还要靠原来的窗口过程来处理,所以这个地址必须被保存下来。
让我们通过一个简单的例子来演示窗口子类化的实现过程,程序实现的就是前面介绍的16进制与10进制转换的程序,源代码位于所附光盘的Chapter09\SubClass目录中。程序首先在资源脚本文件SubClass.rc中定义了一个对话框,对话框中包括两个编辑控件,IDC_DEC用来输入10进制数值,它包含ES_NUMBER风格,只能输入0~9的数值;而IDC_HEX用来输入16进制数值,代码如下:
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#include
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#define ICO_MAIN 1000
#define DLG_MAIN 1000
#define IDC_HEX 1001
#define IDC_DEC 1002
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN ICON "Main.ico"
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DLG_MAIN DIALOG 107, 102, 129, 42
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "Hex <> Dec"
FONT 9, "宋体"
{
LTEXT "Hex", -1, 7, 9, 15, 8
EDITTEXT IDC_HEX, 27, 7, 94, 12
LTEXT "Dec", -1, 7, 26, 15, 8
EDITTEXT IDC_DEC, 27, 24, 94, 12, ES_NUMBER
}
汇编源程序SubClass.asm的内容如下:
.386
.model flat, stdcall
option casemap :none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Equ 等值定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN equ 1000
DLG_MAIN equ 1000
IDC_HEX equ 1001
IDC_DEC equ 1002
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
hInstance dd ?
hWinMain dd ?
dwOption dd ?
lpOldProcEdit dd ?
.const
szFmtDecToHex db '%08X',0
szFmtHexToDec db '%u',0
szAllowedChar db '0123456789ABCDEFabcdef',08h
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; IDC_HEX编辑框的新窗口过程
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProcEdit proc uses ebx edi esi hWnd,uMsg,wParam,lParam
mov eax,uMsg
.if uMsg == WM_CHAR
mov eax,wParam
mov edi,offset szAllowedChar
mov ecx,sizeof szAllowedChar
repnz scasb
.if ZERO?
.if al > '9'
and al,not 20h
.endif
invoke CallWindowProc,lpOldProcEdit,\
hWnd,uMsg,eax,lParam
ret
.endif
.else
invoke CallWindowProc,lpOldProcEdit,\
hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
_ProcEdit endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 计算16进制到10进制
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_HexToDec proc
local @szBuffer[512]:byte
invoke GetDlgItemText,hWinMain,IDC_HEX,\
addr @szBuffer,sizeof @szBuffer
lea esi,@szBuffer
cld
xor eax,eax
mov ebx,16
.while TRUE
movzx ecx,byte ptr [esi]
inc esi
.break .if ! ecx
.if cl > '9'
sub cl,'A' - 0ah
.else
sub cl,'0'
.endif
mul ebx
add eax,ecx
.endw
invoke wsprintf,addr @szBuffer,addr szFmtHexToDec,eax
invoke SetDlgItemText,hWinMain,IDC_DEC,addr @szBuffer
ret
_HexToDec endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 计算10进制到16进制
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_DecToHex proc
local @szBuffer[512]:byte
invoke GetDlgItemInt,hWinMain,IDC_DEC,NULL,FALSE
invoke wsprintf,addr @szBuffer,addr szFmtDecToHex,eax
invoke SetDlgItemText,hWinMain,IDC_HEX,addr @szBuffer
ret
_DecToHex endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProcDlgMain proc uses ebx edi esi hWnd,wMsg,wParam,lParam
mov eax,wMsg
;********************************************************************
.if eax == WM_CLOSE
invoke EndDialog,hWnd,NULL
;********************************************************************
.elseif eax == WM_INITDIALOG
mov eax,hWnd
mov hWinMain,eax
invoke SendDlgItemMessage,hWnd,\
IDC_HEX,EM_LIMITTEXT,8,0
invoke SendDlgItemMessage,hWnd,\
IDC_DEC,EM_LIMITTEXT,10,0
invoke GetDlgItem,hWnd,IDC_HEX
invoke SetWindowLong,eax,GWL_WNDPROC,addr _ProcEdit
mov lpOldProcEdit,eax
;********************************************************************
.elseif eax == WM_COMMAND
mov eax,wParam
.if ! dwOption
mov dwOption,TRUE
.if ax == IDC_HEX
invoke _HexToDec
.elseif ax == IDC_DEC
invoke _DecToHex
.endif
mov dwOption,FALSE
.endif
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
_ProcDlgMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke DialogBoxParam,hInstance,DLG_MAIN,\
NULL,offset _ProcDlgMain,NULL
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
程序运行后将显示如图9.9所示的对话框,上面的编辑控件是经过子类化的,只能输入16进制字符,当输入字母a~f的时候,不管输入的是大写还是小写字母,都会被控件转换成大写字母。下面的编辑框是ES_NUMBER风格的,可以输入数字。不管在哪个编辑框中输入数值,程序会马上进行转换并将结果在另一个编辑框中显示出来。
图9.9 窗口子类化例子的运行界面
程序在对话框的初始化消息WM_INITDIALOG中对这两个编辑控件发送EM_LIMITTEXT消息,以此限制能够输入的最大字符长度,接下来程序通过GetDlgItem获取IDC_HEX编辑框的窗口句柄,并使用SetWindowLong函数将编辑框的新窗口过程设置到_ProcEdit子程序中,返回的原窗口过程地址被保存到lpOldProcEdit变量中:
invoke GetDlgItem,hWnd,IDC_HEX
invoke SetWindowLong,eax,GWL_WNDPROC,addr _ProcEdit
mov lpOldProcEdit,eax
这样,当Windows向IDC_HEX编辑框发送消息时,_ProcEdit子程序就会收到消息,在_ProcEdit中我们仅处理感兴趣的WM_CHAR消息,程序在数据段中定义了一个允许输入的字符表,表中包括0~9、大小写的A~F以及退格键(如果不允许输入退格键的话将无法修正输入错误)。然后在处理WM_CHAR消息时使用scasb指令进行查表,如果字符在表中,则将WM_CHAR消息通过CallWindowProc函数转发给原来的窗口过程,如果不在表中则直接返回,相当于丢弃了这个按键动作。代码如下:
.const
szAllowedChar db '0123456789ABCDEFabcdef',08h
.code
...
mov eax,wParam ;WM_CHAR消息中的一段代码
mov edi,offset szAllowedChar
mov ecx,sizeof szAllowedChar
repnz scasb
.if ZERO?
.if al > '9'
and al,not 20h
.endif
invoke CallWindowProc,lpOldProcEdit,hWnd,uMsg,eax,lParam
ret
.endif
在转发之前,程序还对字符进行判断,如果字符是小写的a~f的话(表中的字符中大于“9”的肯定是字母),则通过and al,not 20h语句将字母转换成大写,因为大写字母的ASCII码从41h开始,小写字母从61h开始,这样的计算方法对大写字母没有影响,对小写的则刚好能够转换成大写的。程序将其他所有的消息原封不动地转发给原来的窗口过程,这样才能让编辑控件的窗口过程为我们完成控件的其他功能。
转发消息使用了CallWindowProc函数,这个函数仅起到将参数入栈和调用指定地址的作用,对于下面的语句我们完全可以用自己调用lpOldProcEdit的方法来代替它:
invoke CallWindowProc,lpOldProcEdit,hWnd,uMsg,eax,lParam
下面的代码就可以完成同样的功能:
push lParam
push eax
push uMsg
push hWnd
call lpO ldProcEdit
程序中的其他代码应该算是相当简单的,_DecToHex子程序是10进制到16进制的转换子程序,子程序中用GetDlgItemInt函数读入编辑框中的10进制数值,并用wsprintf转换成16进制数值的字符串并显示到IDC_HEX编辑框中;_HexToDec是16进制到10进制的转换子程序,由于并没有现成的转换函数,所以在子程序中顺序读入字符并每次通过乘以16来进行计算。
程序中还有一个技巧。由于使用SetDlgItemText设置编辑框文本的时候,编辑框会发送WM_COMMAND消息,由于一收到某个WM_COMMAND消息就进行转换计算,并再次使用SetDlgItemText函数将计算结果显示在另一个编辑框中,这样就会进入发送WM_COMMAND消息的死循环中。为此程序中定义了一个dwOption变量,当正在处理某个WM_COMMAND消息的时候,将这个变量设置为1来防止重入,这样就能够防止死循环的发生,代码如下:
.elseif eax == WM_COMMAND
mov eax,wParam
.if ! dwOption
mov dwOption,TRUE
.if ax == IDC_HEX
invoke _HexToDec
.elseif ax == IDC_DEC
invoke _DecToHex
.endif
mov dwOption,FALSE
.endif
对控件窗口进行子类化,影响的仅是被操作的窗口,并不会影响基于这种控件的其他窗口,因为SetWindowLong函数操作的对象仅是单个窗口而不是窗口类。所以要对多个控件窗口进行同样的子类化就必须对每个窗口都进行子类化操作
WIN32汇编(控件超类化)
时间:2009-8-7 18:09:08 点击:63
核心提示:9.6.1 什么是控件的超类化 子类化是对窗口功能的调整和扩展,那么超类化是什么呢?超类化是对类的调整和扩展,在C++中,可以通过继承和扩展某个基类来形成一个派生的类,超类化可以完成的功能就和这相似。超类化主要用在什么地方呢?举例来说,如果需要一个只能输入16进制字符的编辑框,那么可以通过对编辑框窗...
9.6.1 什么是控件的超类化
子类化是对窗口功能的调整和扩展,那么超类化是什么呢?超类化是对类的调整和扩展,在C++中,可以通过继承和扩展某个基类来形成一个派生的类,超类化可以完成的功能就和这相似。
超类化主要用在什么地方呢?举例来说,如果需要一个只能输入16进制字符的编辑框,那么可以通过对编辑框窗口子类化来实现,9.5节的例子就是如此,但是当应用程序需要大量使用这种16进制编辑框时,该如何处理呢?方法有3种:
● 创建自己的类,自己书写所有的功能代码。
● 创建多个Edit控件,并把它们全部子类化。
● 超类化Edit控件,用Edit控件当做基类派生出一个新的类,并用这个类来建立多个“新Edit”控件窗口。
第一种方法在9.5节中就被“枪毙”了,几乎没有人去干这种吃力不讨好的事情;第二种方法要好一点,但子类化一大堆的控件也是一件令人头痛的事情;这时就应该使用超类化Edit类方法,当从Edit类派生出一个新的“16进制编辑类”后,接下来直接使用这个类就能够创建出一大堆的16进制编辑框。
9.6.2 控件超类化的实现
各种自定义的窗口和不同的控件窗口之所以看上去千姿百态,功能也各不相同是因为两个原因:首先用来表示类属性的WNDCLASSEX结构定义不同,造成窗口的风格与形状等各不相同;其次,不同窗口类使用的窗口过程不同,这些不同的窗口过程对各种消息的处理方法各不相同,造成窗口的功能不同。
设想这样一种情况:如果自定义一个类,这个类的WNDCLASSEX结构中定义的风格、形状、光标与图标等所有属性都和Edit类相同,然后在自己的类中将窗口过程地址指向Edit类的窗口过程(或者原样拷贝Edit类的窗口过程代码),这个类会实现什么样的功能呢?答案是:除了类的名称不同之外,用这个类创建的窗口的形状和所有功能将和Edit框一模一样!这时候,就相当于从Edit类派生出了一个相同的类来。
这样一来就不难设想从基类派生出新类的方法,我们可以获取基类的WNDCLASSEX结构,然后保持结构中的大部分字段不变,仅修改个别需要自定义的属性,那么新类的窗口风格就和基类类似,如果还需要扩充某些功能的话,可以将结构中的窗口过程地址指到自己的子程序中,这样就可以扩展基类的功能。最后,使用这个修改后的结构以自定义的名称注册一个类,一个新的类就派生出来了。
对基类进行超类化的时候,如果修改的仅是窗口风格,那么使用派生类建立的窗口和基类窗口会实现同样的功能,但外观上会有所不同;如果仿照子类化窗口的方法修改窗口过程,那么使用派生类建立的窗口和基类窗口的外形是相同的,但是功能上会有所不同。
使用GetClassInfoEx函数可以获取现存的类的属性,对基类进行超类化的第一步就是使用这个函数获取基类的WNDCLASSEX 结构。GetClassInfoEx函数的使用方法是:
invoke GetClassInfoEx,hinst,lpszClass,lpwcx
hinst参数是创建这个类的应用程序的实例句柄,如果要获取某个Windows预定义类的属性,那么这个参数使用NULL。
lpszClass参数指向一个字符串,用来定义类的名称。lpwcx参数指向一个WNDCLASSEX结构,用来返回指定类的属性。在调用函数之前,结构中的cbSize必须正确地设置为WNDCLASSEX结构的长度,否则函数的执行可能失败。
获取WNDCLASSEX结构以后,可以根据需要修改结构的内容。如果需要派生出一个功能不同的新类,可以将窗口过程地址设置到自己的程序中,当然原来的地址应该被保存下来,以便收到不感兴趣的消息时转发到原来的窗口过程中。除了修改需要自定义的属性外,还有两个字段是必须修改的:hInstance字段必须设置为应用程序的实例句柄;lpszClassName必须指向新的派生类的类名。完成了这些修改后,使用经过修改的WNDCLASSEX注册一个新的类就大功告成了。
让我们通过一个简单的例子来演示超类化的过程,例子中建立了一个对话框,并在对话框中定义了多个从Edit类派生出来的16进制编辑类,这些类实现的功能和9.5节中的例子是一样的,全部的源代码在所附光盘的Chapter09\SuperClass目录中,SuperClass.rc文件的定义如下:
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#include
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#define ICO_MAIN 1000
#define DLG_MAIN 1000
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN ICON "Main.ico"
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DLG_MAIN DIALOG 107, 102, 126, 82
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "SuperClass"
FONT 9, "宋体"
{
CONTROL "",-1,"HexEdit",ES_LEFT | WS_BORDER | WS_TABSTOP,5,5,115,12
CONTROL "",-1,"HexEdit",ES_LEFT | WS_BORDER | WS_TABSTOP,5,20,115,12
CONTROL "",-1,"HexEdit",ES_LEFT | WS_BORDER | WS_TABSTOP,5,35,115,12
CONTROL "",-1,"HexEdit",ES_LEFT | WS_BORDER | WS_TABSTOP,5,50,115,12
CONTROL "",-1,"HexEdit",ES_LEFT | WS_BORDER | WS_TABSTOP,5,65,115,12
}
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
读者可以看到,对话框中定义了多个HexEdit类,但是系统中并没有预定义这种名称的类,这就是将要从Edit类中派生的类。SuperClass.asm文件的内容如下:
.386
.model flat, stdcall
option casemap :none
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Include 文件定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
include windows.inc
include user32.inc
includelib user32.lib
include kernel32.inc
includelib kernel32.lib
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; Equ 等值定义
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN equ 1000
DLG_MAIN equ 1000
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
hInstance dd ?
hWinMain dd ?
lpOldProcEdit dd ?
.const
szAllowedChar db '0123456789ABCDEFabcdef',08h
szEditClass db 'Edit',0
szClass db 'HexEdit',0
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; HexEdit控件的新窗口过程
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProcEdit proc uses ebx edi esi hWnd,uMsg,wParam,lParam
mov eax,uMsg
.if uMsg == WM_CHAR
mov eax,wParam
mov edi,offset szAllowedChar
mov ecx,sizeof szAllowedChar
repnz scasb
.if ZERO?
.if al > '9'
and al,not 20h
.endif
invoke CallWindowProc,lpOldProcEdit,\
hWnd,uMsg,eax,lParam
ret
.endif
.else
invoke CallWindowProc,lpOldProcEdit,\
hWnd,uMsg,wParam,lParam
ret
.endif
xor eax,eax
ret
_ProcEdit endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 基于Edit类建立一个新的类:HexEdit
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_SuperClass proc
local @stWC:WNDCLASSEX
mov @stWC.cbSize,sizeof @stWC
invoke GetClassInfoEx,NULL,addr szEditClass,addr @stWC
push @stWC.lpfnWndProc
pop lpOldProcEdit
mov @stWC.lpfnWndProc,offset _ProcEdit
push hInstance
pop @stWC.hInstance
mov @stWC.lpszClassName,offset szClass
invoke RegisterClassEx,addr @stWC
ret
_SuperClass endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
_ProcDlgMain proc uses ebx edi esi hWnd,wMsg,wParam,lParam
mov eax,wMsg
;********************************************************************
.if eax == WM_CLOSE
invoke EndDialog,hWnd,NULL
;********************************************************************
.else
mov eax,FALSE
ret
.endif
mov eax,TRUE
ret
_ProcDlgMain endp
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
start:
invoke GetModuleHandle,NULL
mov hInstance,eax
invoke _SuperClass
invoke DialogBoxParam,hInstance,DLG_MAIN,\
NULL,offset _ProcDlgMain,NULL
invoke ExitProcess,NULL
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
end start
由于在对话框初始化的时候,对话框管理器就要根据对话框资源的内容创建每个子窗口控件,所以在调用DialogBoxParam函数显示对话框之前,“HexEdit”类就必须存在,否则初始化工作会失败。因此,程序在DialogBoxParam函数之前调用_SuperClass子程序进行超类化的工作。
在_SuperClass子程序中,程序通过GetClassInfoEx函数获取Edit控件的类结构,然后将结构中的窗口过程指到自己的_ProcEdit子程序中,接下来将hInstance字段设置为自己的实例句柄,并将类的名称改为“HexEdit”,最后用RegisterClassEx函数注册一个新的“HexEdit”类:
.const
szEditClass db 'Edit',0
szClass db 'HexEdit',0
.code
...
mov @stWC.cbSize,sizeof @stWC ;@stWC是一个WNDCLASSEX结构
invoke GetClassInfoEx,NULL,addr szEditClass,addr @stWC
push @stWC.lpfnWndProc
pop lpOldProcEdit
mov @stWC.lpfnWndProc,offset _ProcEdit
push hInstance
pop @stWC.hInstance
mov @stWC.lpszClassName,offset szClass
invoke RegisterClassEx,addr @stWC
新的窗口过程和子类化窗口中的例子是一样的,在这里就不进行分析了,程序的结果就是:用HexEdit类建立的所有窗口的功能和子类化窗口例子中IDC_HEX编辑框的功能是一样的。
本程序演示的是派生类在对话框中的使用情况,在这里基于派生类创建的窗口是由对话框管理器自动调用CreateWindowEx函数创建的,如果将派生类使用在普通窗口中的话,可以通过指定派生类的名称,自己使用CreateWindowEx函数来创建。