Win32汇编--使用资源--对话框--在对话框中使用子窗口控件(2)
2、子窗口控件的通用使用方法
由于子窗口控件实际上就是窗口,大部分窗口函数对它们都是适用的,如可以用EnableWindow在灰化和允许状态之间切换,可以用ShowWindow在显示和隐藏之间切换,可以用GetWindowText和SetWindowText来改变上面的文字,也可以用MoveWindow来改变大小和移动位置等。在Control.asm中用“显示图片”复选框切换图片框的隐藏和显示,用的就是ShowWindow函数,处理“允许更换图片”复选框时切换“更换图片”按钮的状态,用的是EnableWindow函数。
除了可以用对子窗口控件使用窗口的通用函数外,还可以使用针对它们的专用函数。下面介绍一些常用的函数。
在资源脚本文件中定义的是控件的ID,当这些子窗口控件被创建以后同样会有一个窗口句柄,但既然它们不是由我们由自己创建的,那么怎么知道它们的窗口句柄呢?有一个函数可以从ID中获取子窗口句柄:
invoke GetDlgItem, hDlg, dwIDDlgItem
mov hDlgItem, eax
函数的输入参数是对话框句柄和ID值,返回值是子窗口句柄;反过来,有两种方法可以从子窗口句柄获取ID:
(1)invoke GetDlgCtrolID, hWndCtrl ;输入子窗口句柄,返回值是控件ID
(2)invoke GetWindowLong, hWndCtrl, GWL_ID
当需要向控件发送消息的时候,当然可以先用GetDlgItem获取子窗口句柄再用SendMessage函数,但有一个函数更为简便:
invoke SendDlgItemMessage, hDlg, dwIDDlgItem, Msg, wParam, lParam
这个函数可以直接向控件发送消息,只需要在参数中指定对话框句柄和子窗口ID(注意:并没有PostDlgItemMessage这样的函数!)。
如果要想知道在一个控件上按下了Tab键或Shift+Tab键会跳到哪一个控件上去,也就是说下一个或上一个Tab停留位在哪里,可以使用GetNextDlgTabItem函数:
invoke GetNextDlgTabItem, hDlg, hCtl, bPrevious
.if eax
mov hWinNext, eax
.endif
其中的bPrevious参数指定了搜索的方向;与之相似,使用GetNextDlgGroupItem函数可以返回下一个分组的位置:
invoke GetNextDlgGroupItem, hDlg, hCtl, bPrevious
.if eax
mov hWinNext, eax
.endif
3、使用单选钮和复选框
单选钮是互斥的选择钮,同一组的多个单选钮只能有一个被选中,单选钮的外形是一个圆形的标记加上文本,圆形中有黑点表示被选中。复选框不是互斥的,多个复选框的状态不会互相影响,复选框的外形是一个方框加上文本,方框中可以用有无对钩来表示是否被选中。
单选钮和复选框控件都是基于Button类的,只不过它们的窗口风格分别是BS_RADIOBUTTON和BS_CHECKBOX。既然它们是特殊的“按钮”,所以和它们有关的函数都带有“Button”一词,查看一个单选钮或复选框是否被选中可以用下面的函数来检测:
invoke IsDlgButtonChecked, hDlg, nIDButton
函数的返回值可能是BST_CHECKED(选中状态),BST_INDETERMINATE(3态复选框的灰化状态)或BST_UNCHECKED(未选中状态)。也可以用向子窗口控件发送BM_GETCHECK消息的方法来检测,返回值和上面的函数是一样的。
如果想设置单选钮或复选框的状态,可以使用下面的语句:
invoke CheckDlgButton, hDlg, nIDButton, uCheck
参数uCheck用BST_CHECKED,BST_INDETERMINATE或BST_UNCHECKED来表示需要设置的状态,含义同上。向控件发送BM_SETCHECK消息也可以取得同样的效果,这时消息的wParam中放置需要设置的状态。
复选框是不互斥的,所以可以随意设置状态。而对于BS_RADIOBUTTON风格的单选钮来说,并不是把某个按钮设置为选中状态以后,同组的其他按钮就会自动变成非选中状态,所以用CheckDlgButton函数选中了一个单选钮以后,如果不是手动把同组的其他按钮全部改为非选中状态(逐个地调用CheckDlgButton),就会看到同时有两个单选钮是选中的。但把同组的所有单选钮逐个地设置显得有点麻烦,所以针对单选钮有一个专用函数:
invoke CheckRadioButton, hDlg, nIDFirstButton, nIDLastButton, nIDCheckButton
这个函数把ID在nIDFirstButton和nIDLastButton之间的单选钮全部设置为非选中状态,只有nIDCheckButton是选中状态,当然在使用中要注意将这一批ID定义为连续的数值。
如果还嫌CheckRadioButton有点麻烦,还有一种最简单的办法——使用自动单选钮,同组的AUTORADIOBUTTON会随着用户选中一个而自动清除其他单选钮的状态,所以在程序中只需要在初始化的时候预设一次,其他时间就可以不必关心设置问题了,以后唯一用到的就是调用IsDlgButtonChecked检查状态了。
4、使用静态控件
静态控件是基于Static类的子窗口控件,之所以叫“静态”控件,是因为它是“安静”的——它们不向对话框发送WM_COMMAND消息,所以静态控件的ID一般是没有用处的,定义时常常将它们定义为-1,如果需要在程序中改变属性,那么也可以为静态控件指定一个唯一的ID。
资源脚本文件中可以使用缩写的基于Static类的有LTEXT,CTEXT,RTEXT(文本框)和ICON(图标框),除了这些常用的类型之外,Static类还可以用CONTROL语句通过指定不同的窗口风格派生出不同用途的控件来。
下面说明静态控件的一些用法。
对于文本框,文本长度超过边界的时候默认是自动换行的,但如果同时指定SS_SIMPLE风格的话,就不会自动换行。读者可以在程序中用SetWindowText或发送WM_SETTEXT消息来动态改变显示的文本,同样,也可以用GetWindowText或发送WM_GETTEXT消息来获取其中的文本。
静态控件可以用来构筑简单的线条和图形,如果指定SS_BLACKFRAME,SS_GRAYFRAME或SS_WHITEFRAME风格,那么静态控件显示为填充的矩形,填充颜色分别是黑色、灰色或白色;而指定SS_BLACKRECT,SS_GRAYRECT或SS_WHITERECT风格的话,则显示为非填充的矩形框,边线颜色是黑色、灰色或白色。
静态控件也可以用来做立体感的线条或边框,指定SS_ETCHEDHORZ风格显示为横线,指定SS_ETCHEDVERT风格显示为竖线,指定SS_ETCHEDFRAME风格则显示为立体的矩形框,视觉上的效果似于没有文字的GROUPBOX。
静态控件还有一个用途是做图形显示,当图形是图标的时候,用ICON语句就可能定义了,其默认的风格是SS_ICON,如果想使用位图,那么可以指定SS_BITMAP风格,例子程序中的图片框就是这样定义的。
CONTROL IDB_1, IDC_BMP, “Static”, SS_BITMAP | WS_CHILD | WS_VISIBLE, 5, 5, 40, 95
在这里,“文字”部分指定位图资源的ID,前面已经把Picture1.bmp的资源ID定义为IDB_1,IDC_BMP是图片框自己的ID,如果不需要在程序中改变图片的话,那么这里可以定义为-1。
在程序中可以通过向控件发送STM_SETIMAGE消息来设置新的图片,消息的wParam指定图片的格式,取值可以是IMAGE_BITMAP,IMAGE_CURSOR和IMAGE_ICON,分别对应新图片的格式,lParam是图片的句柄,如果是位图,lParam就是用LoadBitmap装入的位图句柄,同样,图片类型是光标和图标的时候,这里就是用LoadCursor和LoadIcon装入的句柄。
在例子程序中,用来改变图片框图片的语句是:
invoke SendDlgItemMessage, hWnd, IDC_BMP, STM_SETIMAGE, IMAGE_BITMAP, eax
eax中是位图句柄,IDC_BMP是图片框的ID,wParam用IMAGE_BITMAP表示要设置的图片类型是位图。
5、使用文本编辑控件
文本编辑控件是基于Edit类的控件,可以用缩写EDITTEXT定义,读者可以在文本编辑控件中输入并编辑文本。每当用户在文本编辑控件中输入一个字符的时候,控件就会向对话框过程发送一个WM_COMMAND消息,所以在例子程序中,当在自定义文字的编辑框中每输入一个字,标题栏文字就会马上改变。
要获取编辑框中的文本有多种方法,可以用GetWindowText,也可以用发送WM_GETTEXT消息的办法,要设置文本,同样可以用SetWindowText或发送WM_SETTEXT,但最简便的办法还是使用下面的函数:
invoke GetDlgItemText, hDlg, nIDDlgItem, lpString, nMaxCount ;取文本
invoke SetDlgItemText, hDlg, nIDDlgItem, lpString ;设置文本
lpString是放置字符的缓冲区地址,用GetDlgItemText函数来获取文本的时候,要用nMaxCount参数指定缓冲区的最大长度,以免获取的文本长度超过缓冲区长度引起溢出,设置的时候若使用SetDlgItemText函数时就不需要这个参数。
在实际使用中,经常要在文本编辑控件中输入输出数值型参数,将文本转换为数值比较麻烦,把数值转换为文本也要经过一个wsprintf调用,为了简化操作,Windows提供了两个函数来处理这个问题:
invoke SetDlgItemInt, hDlg, nIDDlgItem, uValue, bSigned ;设置控件中的数值
invoke GetDlgItemInt, hDlg, nIDDlgItem, lpTranslated, bSigned ;取控件中的数值
SetDlgItemInt函数将uValue参数先转换成字符串格式,然后设置到文本编辑控件中,bSigned参数指定了uValue的格式,如果是TRUE的话,表示uValue是有符号数;是FALSE的话,表示uValue是无符号数。
GetDlgItemInt函数则将对话框中的文本转换成数值型返回,同样,用bSigned指定转换的方式,TRUE表示按照符号数格式转换,这时函数会检测文本的第一个字符是不是负号;FALSE则按照无符号数转换。参数lpTranslated是指向一个dword型变量的指针,GetDlgItemInt会在这个变量中返回BOOL类型值表示函数是否调用成功,成功则返回TRUE,有这样一个参数的原因是函数的返回值用来返回转换后的数值了,以至于没有地方可以表示函数是否执行成功。当然,lpTranslated参数也可以输入NULL,这样,当函数返回0的时候就无法知道是文本框是“ 0 ” 还是文本不符合格式造成转换失败。
SetDlgItemInt和GetDlgItemInt函数不仅适用于文本编辑控件,所有对其上面的文本可以修改的控件都可以使用它们。
使用文本编辑控件的时候,文本的长度也是个需要注意的问题。如果控件的宽度定义得过窄,当字符填充到最右边的时候,编辑框就不允许继续输入了,为了继续输入并让文本自动卷动,可以指定WS_HSCROLL风格;反之,定义WS_HSCROLL风格后输入文本的长度不受限制又不好,那么可以用向控件发送EM_LIMITTEXT消息的方式来设定最大长度。
下面的例子将IDC_EDIT的输入最大长度定为10个字符:
invoke SendDlgItemMessage, hDlg, IDC_EDIT, EM_LIMITTEXT, 10, NULL
另外,有时候可能需要把编辑框设置为只读的(和灰化不同,灰化的编辑框中文本无法进行任何操作,包括卷动操作,而只读的仅仅是不能修改),要把初始状态定义为只读的,只需在定义语句中加上ES_READONLY风格,在程序中需要动态改变只读状态可以发送EM_SETREADONLY消息,下面的第一句把编辑框设为只读,第二句把编辑框改回到可写状态:
invoke SendDlgItemMessage, hDlg, IDC_EDIT, EM_SETREADONLY, TRUE, NULL ;只读
invoke SendDlgItemMessage, hDlg, IDC_EDIT, EM_SETREADONLY, FALSE, NULL;可写
文本编辑框在默认状态下是单行的,也可以通过加上EM_MULTILINE风格变成多行的,这时可以同时加上WS_VSCROLL风格显示一个垂直方向的滚动条。
6、使用滚动条
滚动条有水平和垂直两种,默认的SCROLLBAR语句定义的是水平的滚动条,它的默认风格是SBS_HORZ,例子程序中用下面的语句定义了一个水平滚动条:
SCROLLBAR IDC_SCROLL, 6, 118, 125, 10
如果要定义垂直的滚动条,那么要指明SBS_VERT风格:
SCROLLBAR IDC_SCROLL, x, y, 宽度, 高度, SBS_VERT
和其他子窗口控件发送WM_COMMAND消息不同,水平滚动条向对话框窗口发送WM_HSCROLL消息,而垂直滚动条则发送WM_VSCROLL消息,所以针对两种方式的滚动条要分别处理不同的消息。
WM_xSCROLL消息的参数如下所示:
wParam的低16位 = nScrollCode ;动作码
wParam的高16位 = nPos ;滚动条当前位置
lParam = hwndScrollBar ;滚动条控件的窗口句柄
其中nScrollCode代表了滚动条的当前动作,定义值及其含义如下:
SB_BOTTOM 滚动条移到了最下/右边。
SB_ENDSCROLL 用户停止了滚动动作。
SB_THUMBPOSITION 滚动条被拖动到某处。
SB_THUMBTRACK 滚动条在拖动中。
SB_TOP 滚动条移到了最上/左边。
SB_LINELEFT 滚动条左移了一格(对于水平滚动条)。
SB_LINERIGHT 滚动条右移了一格(对于水平滚动条)。
SB_PAGELEFT 滚动条左移了一页(对于水平滚动条)。
SB_PAGERIGHT 滚动条右移了一页(对于水平滚动条)。
SB_LINEDOWN 滚动条下移了一格(对于垂直滚动条)。
SB_LINEUP 滚动条上移了一格(对于垂直滚动条)。
SB_PAGEDOWN 滚动条下移了一页(对于垂直滚动条)。
SB_PAGEUP 滚动条上移了一页(对于垂直滚动条)。
nPos的值只有当动作码是SB_THUMBPOSITION或SB_THUMBTRACK时才有效,其他的时候为0。
第一眼看到SB_xxx动作码的时候,读者可能会以为水平滚动条和垂直滚动条的动作码是不相同的——水平滚动条是SB_xxxLEFT、SB_xxxRIGHT,而垂直滚动条是SB_xxxUP、SB_xxxDOWN,但在Windows.inc中查看一下就可以发现,SB_xxxLEFT和SB_xxxUP在数值上是相同的,SB_xxxRIGHT和SB_xxxDOWN也是如此,所以不同定义方法只是为了直观起见而已。
以水平滚动条为例,处理滚动条消息的代码一般是如下结构:
.elseif eax == WM_HSCROLL ;窗口的消息处理分支,eax为wMsg
mov eax, lParam
.if eax == hWnd滚动条1
mov eax, wParam
.if ax == SB_LINELEFT
dec 位置变量
.elseif ax == SB_LINERIGHT
inc 位置变量
.elseif ax == SB_PAGELEFT
sub 位置变量,页长
.elseif ax == SB_PAGERIGHT
add 位置变量,页长
.elseif ax == SB_THUMBPOSITION || ax == SB_THUMBTRACK
mov eax, wParam
shr eax, 16
mov 位置变量,eax
.endif
.elseif eax == hWnd滚动条2
;处理滚动条2的代码,同上面的结构
…
.endif
在例子程序Control.asm中只定义了一个滚动条,所有的消息肯定都是它发出的,所以去掉了判断lParam是哪个滚动条的步骤直接处理wParam中的动作码。
在用户按动滚动条后,滚动条不会自己移动位置,它只是将用户的动作以WM_xSCROLL消息的形式反馈给程序,真正要移动它还是要靠程序来设置,所以代码中要根据不同的动作首先计算新的位置,并判断新的位置是否越界,例子程序中的这些代码判断新的位置是否超出0~100的范围,如果是,则校正到0~100之间:
cmp dwPos, 0
jge @F
mov dwPos, 0
@@:
cmp dwPos, 100
jle @F
mov dwPos,100
在介绍MASM语句的时候提到过,.if dwPos > 0语句只可以用来比较无符号数,所以在这里使用cmp指令自己测试分支而不是使用.if伪指令。
不计算好新位置的时候要将位置设置回去,用户才会看到滚动条移动了,方法是向滚动发送SBM_SETPOS消息:
invoke SendDlgItemMessage, hWnd, IDC_SCROLL, SBM_SETPOS, dwPos, TRUE
最后一个参数为TRUE表示设置后重新绘画滚动条。
在初始化的时候,要给滚动条发送SBM_SETRANGE消息来设定滚动范围:
invoke SendDlgItemMessage, hWnd, IDC_SCROLL, SBM_SETRANGE, 最小值, 最大值
如果需要获取滚动条的信息,可以尝试发送下面两个消息:SBM_GETPOS可以获取滚动条的当前位置,也就是上一次用SBM_SETPOS设置的值;SBM_GETRANGE可以获取滚动的范围,也就是用SBM_SETRANGE设置的值。
7、使用组合框
顾名思义,组合框是一个“组合”起来的东西,它由一个可供选择的列表和一个可供输入的edit类组合而成。组合框让用户既可以自己输入文本,也可以选择列表中的某一项当做输入。用不同的风格定义可以产生3种类型的组合框。
CBS_SIMPLE风格的组合框,它的上面可以输入文本,下面的列表可供选择预设文本;
CBS_DROPDOWN风格的组合框,上面同样可以输入文本,但下面的列表是下拉式的,平时处于收起状态,点击编辑框右边的三角形才会拉下来;
CBS_DROPDOWNLIST风格的组合框,它仅是一个下拉的选择框,上面的框中不允许输入文字。
组合框中还有几种常用的、可以附加的风格:
CBS_AUTOHSCROLL 输入过长的文本时输入框自动卷动。
CBS_LOWERCASE 自动将所有的文本转换成小写。
CBS_SORT 自动将插入的文本项排序。
CBS_UPPERCASE 自动将所有的文本转换成大写。
组合框中列表框部分的文字添加、项目的选择等操作都是通过发送消息来完成的,主要的消息如下表所示:
组合框的消息
消息 |
wParam |
lParam |
说明 |
CB_ADDSTRING |
0 |
字符串地址 |
把一个字符串添加到列表中 |
CB_INSERTSTRING |
位置索引 |
字符串地址 |
把一个字符串插入到列表中 |
CB_FINDSTRING |
开始查找的位置索引 |
查看的字符串 |
在列表中查找以lParam字符串开头的项,找到则返回位置索引,未找到则返回CB_ERP |
CB_FINDSTRINGEXACT |
位置索引 |
查找的字符串 |
精确查找字符串 |
CB_DELETESTRING |
位置索引 |
0 |
删除一个列表项 |
CB_RESETCONTENT |
0 |
0 |
删除所有的列表项 |
CB_GETLBTEXT |
位置索引 |
缓冲区地址 |
获取指定列表项的字符串,缓冲区必须足够大 |
CB_GETLBTEXTLEN |
位置索引 |
0 |
获取指定列表项的字符串长度 |
CB_GETCOUNT |
0 |
0 |
获取列表项的总项数 |
CB_SETCURSEL |
位置索引 |
0 |
选中一个列表项,并将列表项中的文字拷贝到编辑控件中 |
CB_SELECTSTRING |
开始查找的位置索引 |
字符串地址 |
查找以lParam指定的字符串开始的列表项,如果找到则选中它并将字符串拷贝到编辑控件中 |
CB_GETCURSEL |
0 |
0 |
获取当前选中的位置索引,没有选中的项目则返回CB_ERR |
CB_SHOWDROPDOWN |
状态 |
0 |
打开(状态为TRUE)或收起(状态为FALSE)下拉列表 |
CB_GETDROPPEDSTATE |
0 |
0 |
检测列表的当前下拉状态,返回TRUE表示拉下,FALSE表示收起 |
当用户在组合框中进行选择操作时,Windows向对话框过程发送WM_COMMAND消息,消息中wParam参数的低16位是组合框ID,高16位是通知码,用来表示用户的操作,通知码的定义如下表所示。
用户操作组合框后的通知码
通知码 |
说明 |
CBN_SELCHANGE |
用户将要选择一个项目(鼠标移动到了这个项目上) |
CBN_CLOSEUP |
下拉列表关闭(可能是选择完成也可以是取消选择) |
CBN_SELENDOK |
用户完成选择项目 |
CBN_SELENDCANCEL |
用户取消选择(鼠标移动到了某个项目上,但并没有按下而是点击了其他控件,或按动了Esc键) |
CBN_DBLCLK |
在CBS_SIMPLE的组合框中双击了一个列表项 |
CBN_DROPDOWN |
用户打开了下拉框(按动了编辑框的下拉按钮) |
如果想在用户选择了一个项目后做相应的动作,最好的办法就是处理CBN_SELENDOK通知码,因为这才意味着用户真正完成了一个选择动作,例子程序中就是这样处理的:
.elseif ax == IDC_TITLETEXT ;在WM_COMMAND消息中
shr eax,16
.if ax == CBN_SELENDOK
invoke SendDlgItemMessage, hWnd, IDC_TITLETEXT, CB_GETCURSEL, 0, 0
;根据返回的eax值做相应动作…
.endif
以上的操作都是针对下拉列表部分的,另外也有很多消息是针对组合框中的编辑控件的,对组合框的窗口句柄发送WM_GETTEXT和WM_SETTEXT,操作的对象就是组合框的编辑控件;如果要限制控件中文本的最大输入长度,可以发送CB_LIMITTEXT的消息,这时候wParam参数指定最大数量;当用户在编辑框中编辑文本的时候,Windows在用户输入之后、字符显示之前会发送CBN_EDITUPDATE通知码;当字符在编辑框中显示以后,又会发送CBN_EDITCHANGE通知码。所以在处理WM_COMMAND消息时通过处理这两个通知码可以检测到用户的输入操作。
组合框是子窗口控件中比较复杂的一种,这里仅介绍了常用的消息和通知码。