使用资源—对话框
简介
顾名思义,对话框完成的是“对话”功能,应用程序一般建立一个主窗口用做工作界面,大部分的工作分在主窗口的客户区完成,但程序往往需要和用户交互,如输入参数和输入文本等,这些界面不必要全部放在主窗口中,习惯的做法是通过选择菜单项弹出一个窗口,然后在这个窗口中完成对话,这个窗口就是“对话框”,对话框中的按钮、文本框和图标等称为“子窗口控件”。
为了提示用户,习惯于在会引出对话框的菜单项后面加上省略号。如“文件”菜单中的“另存为…”表示会引出一个选择文件名的对话框,所以“另存为”3个字后面加了个省略号。对话框最典型的例子就是写字板“查找”菜单弹出的窗口以及IE浏览器中选择“Internet 选项”菜单项弹出的设置窗口。
1、对话框的类型
对话框分两类:modal对话框和modeless对话框,翻译成中文就是“模态的”和“非模态的”(也有的地方翻译成“模式的”和“非模式的”,Visual FoxPro中文版式就是这样),它们之间的区别在于是否允许用户在不同窗口间进行切换:当显示非模态对话框时,用户可以随意在这个对话框和其它窗口之间切换;而显示一个模态对话框时,用户在关闭对话框之前不允许切换到同一程序的其它窗口中,但可以切换到其他程序的窗口中;如果显示的是操作系统所属的模态对话框(即“系统模态的”),则切换到其他任何程序的窗口都是不允许的。
Windows在资源文件中定义对话框,然后在程序中利用这个模板创建对话框,模态对话框和非模态对话框的资源定义是相同的,只是创建时调用的函数不同而已。
2、对话框的工作原理
很明显,对话框和普通窗口之间有很多相似之处,实际上对话框就是基于窗口的,对话框的窗口风格使用的就是普通窗口的风格定义,对话框也有一个类似于窗口过程的对话框过程,但对话框和普通窗口在实现上又有很多不同之处,模态对话框和非模态对话框的实现也是不同的。普通的窗口在建立之前需要用RegisterClass注册一个窗口类,然后用CreateWindow建立窗口,建立窗口所需的参数如窗口风格、大小位置和窗口过程地址等由窗口类以及CreateWindow中的参数共同提供。
建立对话框的时候并不使用CreateWindow函数,取而代之,建立模态对话框的函数是DialogBoxParam,建立非模态对话框的函数是CreateDialogParam,调用这两个函数创建对话框窗口之前不需要注册对话框的窗口类。
Windows在这两个函数的内部调用CreateWindowEx来建立对话框,使用的风格、大小和位置参数取自资源中定义的对话框模板,使用的窗口类则是Windows内部定义的类,如果读者用一些工具去查看,会发现类名是“#32770”之类的字符串,在这个名字奇特的窗口类中,窗口过程被定义到了Windows内部的“对话框管理器”代码中,Windows在这里处理对话框的大部分消息,如维护客户区的刷新,键盘接口(按Tab键在不同子窗口之间切换、按回车调用默认按钮等),对话框管理器在初始化对话框时会根据对话框模板中定义的子窗口控件建立对话框中所有的子窗口。
用户程序中的对话框过程是由对话框管理器调用的,在处理消息前,对话框管理器会先调用用户指定的对话框过程,再根据对话框过程的返回值决定是否处理它们。
Windows对模态对话框,和非模态对话框的处理有些不同。在创建并显示模态对话框后,Windows会为它在内部建立一个消息循环,在这个消息循环中把消息发送给对话框管理器,对话框管理器在处理消息的过程中会调用用户定义的对话框过程,当对话框关闭的时候,Windows退出内建的消息循环,并从DialogBoxParam函数返回。而对于非模态对话框,CreateDialogParam函数在创建对话框后直接返回,对话框窗口的消息是通过用户程序中的消息循环派送的。
由于模态对话框的特征,使得用户它来做小程序的主窗口非常方便,因为用一句DialogBoxParam函数就可以搞定了,既不用注册窗口类,也不用写消息循环,这对看到创建窗口的几十句代码就烦的读者来说可真是个福音,这种方法的缺点就是无法使用依赖消息循环来完成的功能,很明显,加速键就不能用了。
对话框的资源定义
对话框资源定义的语法
在资源脚本中定义对话框的语法是:
对话框ID DIALOG [DISCARDABLE] x坐标, y坐标, 宽度, 高度
[可选属性]
BEGIN
子窗口控件
…
END
对话框中的子窗口控件语句定义在BEGIN/END(当然也可以以用花括号)之中,在这之前,可以定义对话框的一些可选属性,每种属性单独用一行定义,常用的可选属性如下表所示:
对话框的可选属性
属性
|
定义语法
|
说明
|
标题文字
|
CAPTION “文字”
|
定义显示在窗口标题栏上的文字
|
窗口类
|
CLASS “类名”
|
定义对话框窗口使用的窗口类,如果不定义,则使用Windows内建的类
|
窗口风格
|
STYLE 风格组合
|
定义对话框的窗口风格,同CreateWindowEx中的dwStyle参数
|
扩展风格
|
EXSTYLE风格组合
|
定义对话框的扩展窗口风格,同CreateWindowEx中的dwExStyle参数
|
字体
|
FONT 大小, “字体名”
|
定义对话框包括子窗口控件使用的字体
|
菜单
|
MENU 菜单 ID
|
对话框中使用的菜单,菜单ID在同一个资源脚本文件中定义
|
本节例子程序的资源脚本文件Dialog.rc定义如下:
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#include <resource.h>
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
#define ICO_MAIN 0x1000 //图标
#define DLG_MAIN 1
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
ICO_MAIN ICON "Main.ico"
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
DLG_MAIN DIALOG 50, 50, 113, 64
STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU
CAPTION "对话框模板"
FONT 9, "宋体"
{
ICON ICO_MAIN, -1, 10, 11, 18, 21
CTEXT "简单的对话框例子\n 用Win32ASM编写", -1, 36, 14, 70, 19
DEFPUSHBUTTON "退出(&X)", IDOK, 58, 46, 50, 14
CONTROL "", -1, "Static", SS_ETCHEDHORZ | WS_CHILD | WS_VISIBLE, 6, 39, 103, 1
}
脚本文件中除了定义图标之外,另外还定义了一个ID为1的对话框,对话框中有4个子窗口控件,分别是图标、文本、按钮和一个横线,按钮的ID为IDOK,其他的子窗口控件由于是静态控件,不会向对话框过程发送命令,所以ID就设置为-1,这些控件的具体用法将在后面的内容中详细介绍。
定义中还指定了一些可选属性,STYLE语句定义了对话框窗口的风格,CAPTION语句把标题栏定义为“对话框模板”,FONT语句指定了对话框使用的字体是大小为9的宋体。
对话框的位置为(50,50),大小为宽113单位,高64单位,读者可能已经注意到:这个对话框的大小好像比宽113像素、高64像素的窗口要大,事实上的确如此,这也正是大小是“单位”而不是“像素”的原因。对话框的位置、大小以及所有子窗口控件的度量单位是根据系统字体的大小来决定的,横向(x坐标和宽度)每单位为系统字符平均宽度的1/4,纵向(y坐标和高度)每单位为字符平均高度的1/8,由于系统字体的字符高度大致为宽度的两倍,所以虽然这种计算方法有些费解,但横向和纵向的数值在视觉上还是成比例的,但和以“像素”为单位在数值上肯定是不同的。如果读者一定要知道这个值换算成像素后是多少,那么可以用GetDialogBaseUnits函数来获取系统字体的高度和宽度再进行计算。
当一些英文版的软件在中文Windows上运行的时候,对话框中有些文本往往被砍掉了尾巴,原因就是这些程序是在英文Windows上调试的,文本框的尺寸是以英文Windows系统字符的大小来度量的,到了其他语言的Windows上后,系统字符的大小可能改变,对话框的大小也随着改变,结果就是原来刚好的宽度可能会变得不够,这也算是对话框尺寸度量方法的缺点吧!
使用文本编辑器直接书写对话框脚本定义不是很直观,所以在创建对话框资源时最好使用可视化的资源编辑器,如VC++或ResourceWorkshop等。
在子窗口控件的ID定义中有两个特殊的ID值——IDOK和IDCANCEL,在Resource.h中它们的值定义为1和2,IDOK是默认的“确定”ID,IDCANCEL是默认的“取消”ID。如果一个按钮的ID是IDOK,当焦点没有停留在其他按钮上的时候,在任何地方按下回车键就相当于按下了这个按钮,而按下Esc键的时候,就相当于按下了ID为IDCANCEL的按钮。
2、Tab停留位和组
对话框中可以定义多个子窗口控件,有的了窗口控件可以拥有输入焦点(如按钮、文本框与组合框等),有些则不能(如图标与文本等),当对话框中有多个允许拥有输入焦点的子窗口控件时(有WS_TABSTOP风格),用户可以用Tab键将输入焦点切换到下一个有WS_TABSTOP风格的子窗口控件上,也可以用Shift+Tab键切换到上一个,Tab键切换的顺序就叫做Tab停留位。
Tab停留位并不是系统根据子窗口控件的坐标位置自动排列的,而是按照子窗口控件在资源脚本文件中的定义顺序来排列的,所以读者在定义的时候最好根据子窗口控件的位置适当排列语句的先后,以免按动Tab键切换的时候焦点上下左右无规则的地跳来跳去。如果使用可视化的资源编辑器,那么菜单中一般会有“Tab停留位”菜单项,在编辑完成后也要进到这个菜单项中设置一下,资源编辑器会根据设置调整rc文件中定义语句的先后顺序。
对话框中往往有一些排列在一起的同类子窗口控件,如几个单选钮,几个单选钮之间的选中标记是互斥的,在对话框的其他地方可能又有一组互斥的单选钮用来代表其他功能,在对话框中规定所有的单选钮都是互斥的显然不现实,解决的方法就是将不同的子窗口控件“分组”,这就是“组”的含义。使用中可以选择一些子窗口控件定义WS_GROUP属性,两个有WS_GROUP属性的子窗口控件之间的所有子窗口控件同属同一组。
使用对话框
使用对话框的代码分为创建部分和对话框过程两个部分,完整代码如下所示:
.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 1000h ;图标
DLG_MAIN equ 1 ;对话框
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 数据段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.data?
hInstance dd ?
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
; 代码段
;>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
.code
_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
invoke LoadIcon, hInstance, ICO_MAIN
invoke SendMessage, hWnd, WM_SETICON, ICON_BIG, eax
.elseif eax == WM_COMMAND
mov eax, wParam
.if eax == IDOK
invoke EndDialog, hWnd, NULL
.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
读者可以发现,相对于普通窗口的使用,对话框的使用显得特别简单,最明显的区别在于主程序中的一大堆代码不见了,换成了一个DialogBoxParam语句。
1、创建模态对话框
创建模态对话框的函数是DialogBoxParam,它的使用方法是:
invoke DialogBoxParam, hInstance, lpTemplateName, hWndParent, \
lpDialogFunc, dwInitParam
函数的各参数说明如下:
·hInstance和lpTemplateName——函数从hInstance参数指定的模块中装入lpTemplateName参数指定的对话框资源,然后显示对话框窗口。例子程序中的lpTemplateName参数用的就是我们定义的DLG_MAIN。
·hWndParent——对话框的父窗口,对话框关闭之前将无法切换到父窗口所属的其它窗口中,例子中用对话框做主窗口,所以父窗口句柄是NULL,在其他程序中使用时,这个参数设置为主窗口的句柄。
·lpDialogFunc——指定了对话框过程的地址,例子程序中是_ProcDlgMain。
·dwInitParam——当做WM_INITDIALOG消息的lParam传给对话框过程,读者可以用它来做自定义的用途。
要结束模态对话框,必须在对话框过程的WM_CLOSE消息中使用EndDialog函数:
invoke EndDialog, hDlg, dwResult
不能使用通常的DestroyWindow函数,参数中的hDlg就是对话框窗口的句柄,dwResult参数是退出时的返回值,这个值最后由DialogBoxParam函数返回到主程序中。
2、创建非模态对话框
创建非模态对话框的函数是CreateDialogParam,它的参数定义和DialogBoxParam一模一样:
invoke CreateDialogParam, hInstance, lpTemplateName, hWndParent, \
lpDialogFunc, dwInitParam
mov hDlg, eax
CreateDialogParam和DialogBoxParam在使用中有几个不同点:
·CreateDialogParam在创建对话框后,会根据对话框模板的风格是否定义了WS_VISIBLE来决定是否显示对话框窗口。如果定义了则显示,没有的话,则程序需要在以后自行调用ShowWindow来显示它;而DialogBoxParam函数不管是否定义了WS_VISIBLE风格都会显示对话框。
·CreateDialogParam在建立对话框窗口后直接返回,返回值是对话框窗口的句柄;而DialogBoxParam要在对话框关闭后才返回,返回值是EndDialog中的dwResult参数。
·在CreateDialogParam返回后,应用程序在自己的消息循环中获取对话框消息,所以如果要用非模态对话框做程序的主窗口,消息循环的代码还是要写的;而DialogBoxParam是使用Windows为它内建的消息循环。
·关闭非模态对话框使用DestroyWindow函数,注意在这里不要用EndDialog函数。
3、对话框过程
Windows在“对话框管理器”——也就是为对话框内建的窗口过程中处理对话框消息,在处理前会首先调用用户定义的对话框过程,程序可以在这里选择是否自行处理某些消息。读者在理解时可以把“对话框管理器”看成是对话框的“DefWindowProc”,凡是自己不想处理的消息都由它来处理。
和窗口过程一样,对话框过程是一个“回调”子程序,它由程序定义,Windows来调用,模态对话框和非模态对话框的对话框过程是一样的。
对话框过程和窗口过程的输入参数是一样的,也是:
DialogProc proc hwndDlg, uMsg, wParam, lParam
在程序里面一般编写对话框过程的分支结构如下:
_ProcDlgMain proc uses ebx edi esi hWnd, wMsg, wParam, lParam
mov eax, wMsg
.if eax == WM_CLOSE
;模态对话框用EndDialog关闭
;非模态对话框用DestroyWindow关闭
.elseif eax == WM_INITDIALOG
;初始化代码
.elseif eax == WM_COMMAND
;子窗口控件发送的消息
;wParam的低16位为子窗口控件ID
.elseif eax == WM_XXXX
;处理其他需要处理的消息
.else
mov eax, FALSE
ret
.endif
mov eax, TRUE
ret
_ProcDlgMain endp
注意对话框过程和普通的窗口过程在使用上有以下区别:
·窗口过程对应于不同的消息有各种不同含义的返回值,而对话框过程返回BOOL类型的值,返回TRUE表示已经处理了某条消息,返回FALSE表示没有处理。“对话框管理器”代码会根据返回值决定是否继续处理某一条消息(唯一的例外是WM_INITDIALOG消息)。
·对于不处理的消息,不需要调用DefWindowProc来处理,这事情由“对话框管理器”来做。
“对话框管理器”不会把WM_CREATE消息转发给对话框过程,取而代之,它会以WM_INITDIALOG消息来调用对话框过程,程序可以在这里进行一些初始化的操作,WM_INITDIALOG消息的返回值有点特殊,如果程序想自行设置输入焦点,那么可以用SetFocus函数把输入焦点设置到需要的子窗口控件上,然后返回FALSE;如果返回TRUE的话,那么Windows会自动将输入焦点设置到第一个有WS_TABSTOP的子窗口控件上。
对话框过程在WM_COMMAND消息中处理子窗口控件发送的命令,当用户在对话框中按下了按钮,输入文字或选择复选框等操作时,子窗口控件会向对话框过程发送WM_COMMAND消息,wParam是子窗口控件的ID,如例子程序中处理“退出”按钮的消息,在里面用EndDialog函数关闭对话框。
对话框窗口的标题栏上默认没有定义图标,如果要像普通窗口一样显示一个图标,那么可以像例子程序中那样,在WM_INIDIALOG中用WM_SETICON消息来设置。