对话框
前边讲过资源的大家可以当做一朵朵小浮云,但是对话框这个资源需要引起足够的重视,因为他在日常编程中运用是无时不刻的!
顾名思义,对话框完成的是”对话”的功能,应用程序一般建立一个主窗口用做工作界面。
大部分的工作会在主窗口的客户区完成,但程序往往需要和用户交互,如输入参数和输入文本等,这些界面不必要全部放在主窗口中,习惯的做法是通过选择菜单项弹出一个”对话框”。
对话框中的按钮、文本框和图标等称为”子窗口控件”。
对话框最典型的例子就是写字板”查找”单弹出的窗口以及IE浏览器中选择”Internet选项”菜单项弹出的设置窗口。
对话框的类型
地球人不一定都知道对话框有分两类:modal对话框和 modeless对话框,翻译成中文就是”模态的”和”非模态的”。(也有的地方翻译成”模式的”和”非模式的”)
它们之间的区别在于是否允许用户在不同窗口间进行切换:当显示非模态对话框时,用户可以随意在这个对话框和其他窗口之间切换;而显示一个模态对话框时,用户在关闭对话框之前不允许切换到同一程序的其他窗口中,但可以切换到其他程序的窗口中。
另外,如果显示的是操作系统所属的模态对话框(即”系统模态的”),则切换到其他任何程序的窗口都是不允许的。
Windows 在资源文件中定义对话框,然后在程序中利用这个模板创建对话框,模态对话框和非模态对话框的资源定义是相同的,只是创建时调用的函数不同而已。
很明显,我们发觉对话框和普通窗口之间有很多相似之处。。。。。。
实际上对话框就是基于窗口的,对话框的窗口风格使用的就是普通窗口的风格定义,对话框也有一个类似于窗口过程的对话框过程,但对话框和普通窗口在实现上又有很多不同之处,模态对话框和非模态对话框的实现也是不同的。
不说废话,美图奉上:
对话框的工作原理
普通的窗口在建立之前需要用 RegisterClass 注册一个窗口类,然后用 CreateWindow 建立窗口对象。
建立窗口所需的参数如窗口风格、大小位置和窗口过程地址等由窗口类以及 CreateWindow 中的参数共同提供。
建立对话框的时候并不使用 CreateWindow 函数,取而代之,建立模态对话框的函数是 DialogBoxParam,建立非模态对话框的函数是 CreateDialogParam。
调用这两个函数创建对话框窗口之前不需要注册对话框的窗口类。
那是因为 Windows 在这两个函数的内部自动调用 CreateWindowEx 来建立对话框,使用的风格、大小和位置等参数取自资源中定义的对话框模板,使用的窗口类则是 Windows 内部定义的类。
Windows在这里处理对话框的大部分消息,如维护客户区的刷新,键盘接口(按Tab键在不同子窗口之间切换、按回车调用默认按钮等),对话框管理器在初始化对话框时会根据对话框模板中定义的子窗口控件建立对话框中所有的子窗口。
模态对话框例子
在接下来的内容中,以一个最简单的例子来讲解如何实现模态对话框:Dialog.exe
对话框的资源定义
对话框ID DIALOG [DISCARDABLE] x坐标, y坐标, 宽度, 高度
[可选属性]
{
...
}
看栗子:Dialog.rc
脚本文件中除了定义图标以外,另外还定义了一个 ID 为 1 的对话框,对话框中有 4 个子窗口控件,分别是图标、文本、按钮和一个横线。
按钮的 ID 为 IDOK,其他的子窗口控件由于是静态控件,不会向对话框过程发送命令,所以 ID 就设置为 −1,这些控件的具体用法将在后面的内容中详细介绍。
经常作图的朋友可能会有这样的疑问:
DLG_MAIN DIALOG 50, 50, 113, 64
对话框的位置为(50,50),大小为宽 113 单位、高 64 单位,可爱的鱼油可能已经注意到:这个对话框的大小好像比宽 113 像素、高 64 像素的窗口要大,事实上的确如此,这也正是大小是”单位”而不是”像素”的原因。
如果一定要知道这个值换算成像素后是多少,那么可以用 etDialogBaseUnits 函数来获取系统字体的高度和宽度再进行计算。
当一些英文版的软件在中文 Windows 上运行的时候,对话框中有些文本往往被砍掉了尾巴,原因就是这些程序是在英文 Windows 上调试的。
文本框的尺寸是以英文 Windows 系统字符的大小来度量的,到了其他语言的 Windows 上后,系统字符的大小可能改变,对话框的大小也随着改变。
结果就是原来刚好的宽度可能会变得不够,这也算是对话框尺寸度量方法的缺点吧!
使用文本编辑器直接书写对话框脚本定义不是很直观,所以在创建对话框资源时最好使用可视化的资源编辑器,如 VC++ 或 ResourceWorkshop或者小甲鱼之前推荐给大家的 ResEdit(在三十七讲下载)。
IDOK 和 IDCANCEL,在 Resource.h 中它们的值定义为 1 和 2。
如果一个按钮的 ID 是 IDOK,当焦点没有停留在其他按钮上的时候,在任何地方按下回车键就相当于按下了这个按钮,而按下 Esc 键的时候,就相当于按下IDCANCEL 的按钮。
Tab停留位
对话框中可以定义多个子窗口控件,有的子窗口控件可以拥有输入焦点(如按钮、文本框与组合框等),有些则不能(如图标与文本等)。
当对话框中有多个允许拥有输入焦点的子窗口控件时(有 WS_TABSTOP 风格),用户可以用 Tab 键将输入焦点切换到下一个有 WS_TABSTOP 风格的子窗口控件上,也可以用 Shift+Tab 键切换到上一个,Tab 键切换的顺序就叫做 Tab 停留位。
Tab 停留位并不是系统根据子窗口控件的坐标位置自动排列的,而是按照子窗口控件在资源脚本文件中的定义顺序来排列的。
所以我们在定义的时候最好根据子窗口控件的位置适当排列语句的先后,以免按动 Tab 键切换的时候焦点上下左右无规则地跳来跳去。
使用对话框
使用对话框的代码跟窗口一样分为创建部分和对话框过程两个部分:Dialog.asm
创建部分:
我们可以发现,相对于普通窗口的使用,对话框的使用显得特别简单,最明显的区别是主程序中的一大堆代码不见了,换成一个 DialogBoxParam 语句。
创建模态对话框
函数是 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 函数返回到主程序中。
创建非模态对话框
创建非模态对话框的函数是 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 参数。
不同点三号:
关闭非模态对话框使用 DestroyWindow 函数,注意在这里不要用 EndDialog 函数。
不同点四号:
在 CreateDialogParam 返回后,应用程序在自己的消息循环中获取对话框消息,所以如果要用非模态对话框做程序的主窗口,消息循环的代码还是要写的。
而 DialogBoxParam 是使用 Windows 为它内建的消息循环。
对话框过程
Windows 在”对话框管理器”(也就是为对话框内建的窗口过程)中处理对话框消息,在处理前会首先调用用户定义的对话框过程,程序可以在这里选择是否自行处理某些消息。
鱼油们在理解时可以把”对话框管理器”看成是对话框的 DefWindowsProc,就是我们不负责任地把所有不想自己处理的消息都扔给它来处理。
和窗口过程一样,对话框过程是一个”回调”子程序,它由程序定义,Windows 来调用,模态对话框和非模态对话框的对话框过程是一样的。
对话框过程和窗口过程的输入参数是一样的,也是:
DialogProc proc hwndDlg, uMsg, wParam, lParam
在程序里面一般编写对话框过程的分支结构如下
对话框过程.txt
注意对话框过程和普通的窗口过程在使用上有以下区别:
窗口过程对应于不同的消息有各种不同含义的返回值,而对话框过程简单地返回 BOOL 类型的值,返回 TRUE 表示已经处理了某条消息,返回 FALSE 表示没有处理。”对话框管理器”代码会根据返回值决定是否继续处理某一条消息。
对于我们懒得处理的消息(FALSE),不需要调用 DefWindowProc 来处理,这事情由”对话框管理器”来做(我们可以认为它们做一样的事儿)。
“对话框管理器”不会把 WM_CREATE 消息转发给对话框过程,取而代之,以 WM_INITDIALOG 消息来调用对话框过程。
WM_INITDIALOG 消息的返回值有点特殊:
如果程序想自行设置输入焦点,那么可以用SetFocus函数把输入焦点设置到需要的子窗口控件上,然后返回FALSE;
如果返回TRUE的话,那么Windows会自动将输入焦点设置到第一个有 WS_TABSTOP 的子窗口控件上。
对话框过程在 WM_COMMAND 消息中处理子窗口控件发送的命令,当用户在对话框中按下了按钮,输入文字或选择复选框等操作时,子窗口控件会向对话框过程发送 WM_COMMAND 消息,wParam 是子窗口控件的 ID,如例子程序中处理”退出”按钮的消息,在里面用 EndDialog 函数关闭对话框。
对话框窗口的标题栏上默认没有定义图标,如果要像普通窗口一样显示一个图标,那么可以像例子程序中那样,在 WM_INITDIALOG 中用 WM_SETICON 消息来设置。