分类: LINUX
2009-01-13 13:35:38
()
自由撰稿人
对话框编程是一个快速构建用户界面的技术。通常,我们编写简单的图形用户界面时,可以通过调用 CreateWindow 函数直接创建所有需要的子窗口,即控件。但在图形用户界面比较复杂的情况下,每建立一个控件就调用一次 CreateWindow 函数,并传递许多复杂参数的方法很不可取。主要原因之一,就是程序代码和用来建立控件的数据混在一起,不利于维护。为此,一般的 GUI 系统都会提供一种机制,利用这种机制,通过指定一个模板,GUI 系统就可以根据此模板建立相应的主窗口和控件。MiniGUI 也提供这种方法,通过建立对话框模板,就可以建立模态或者非模态的对话框。
本文首先讲解组成对话框的基础,即控件的基本概念,然后讲解对话模板的定义,并说明模态和非模态对话框之间的区别以及编程技术。
许多人对控件(或者部件,widget)的概念已经相当熟悉了。控件可以理解为主窗口中的子窗口。这些子窗口的行为和主窗口一样,即能够接收键盘 和鼠标等外部输入,也可以在自己的区域内进行输出――只是它们的所有活动被限制在主窗口中。MiniGUI 也支持子窗口,并且可以在子窗口中嵌套建立子窗口。我们将 MiniGUI 中的所有子窗口均称为控件。
在 Windows 或 X Window 中,系统会预先定义一些控件类,当利用某个控件类创建控件之后,所有属于这个控件类的控件均会具有相同的行为和显示。利用这些技术,可以确保一致的人机操 作界面,而对程序员来讲,可以像搭积木一样地组建图形用户界面。MiniGUI 使用了控件类和控件的概念,并且可以方便地对已有控件进行重载,使得其有一些特殊效果。比如,需要建立一个只允许输入数字的编辑框时,就可以通过重载已有 编辑框而实现,而不需要重新编写一个新的控件类。
如果读者曾经编写过 Windows 应用程序的话,应该记得在建立一个窗口之前,必须确保系统中存在新窗口所对应的窗口类。在 Windows 中,程序所建立的每个窗口,都对应着某种窗口类。这一概念和面向对象编程中的类、对象的关系类似。借用面向对象的术语,Windows 中的每个窗口实际都是某个窗口类的一个实例。在 X Window 编程中,也有类似的概念,比如我们建立的每一个 Widget,实际都是某个 Widget 类的实例。
这样,如果程序需要建立一个窗口,就首先要确保选择正确的窗口类,因为每个窗口类决定了对应窗口实例的表象和行为。这里的表象指窗口的外观,比如窗 口边框宽度,是否有标题栏等等,行为指窗口对用户输入的响应。每一个 GUI 系统都会预定义一些窗口类,常见的有按钮、列表框、滚动条、编辑框等等。如果程序要建立的窗口很特殊,就需要首先注册一个窗口类,然后建立这个窗口类一个 实例。这样就大大提高了代码的可重用性。
在 MiniGUI 中,我们认为主窗口通常是一种比较特殊的窗口。因为主窗口代码的可重用性一般很低,如果按照通常的方式为每个主窗口注册一个窗口类的话,则会导致额外不必 要的存储空间,所以我们并没有在主窗口提供窗口类支持。但主窗口中的所有子窗口,即控件,均支持窗口类(控件类)的概念。MiniGUI 提供了常用的预定义控件类,包括按钮(包括单选钮、复选钮)、静态框、列表框、进度条、滑块、编辑框等等。程序也可以定制自己的控件类,注册后再创建对应 的实例。表 1 给出了 MiniGUI 预先定义的控件类和相应类名称定义。
表 1 MiniGUI 预定义的控件类和对应类名称
控件类 | 类名称 | 宏定义 | 备注 |
静态框 | "static" | CTRL_STATIC | |
按钮 | "button" | CTRL_BUTTON | |
列表框 | "listbox" | CTRL_LISTBOX | |
进度条 | "progressbar" | CTRL_PRORESSBAR | |
滑块 | "trackbar" | CTRL_TRACKBAR | |
单行编辑框 | "edit"、"sledit" | CTRL_EDIT、CTRL_SLEDIT | |
多行编辑框 | "medit"、"mledit" | CTRL_MEDIT、CTRL_MLEDIT | |
工具条 | "toolbar" | CTRL_TOOLBAR | |
菜单按钮 | "menubutton" | CTRL_MENUBUTTON | |
树型控件 | "treeview" | CTRL_TREEVIEW | 包含在 mgext 库,即MiniGUI 扩展库中。 |
月历控件 | "monthcalendar" | CTRL_MONTHCALENDAR | 同上 |
旋钮控件 | "spinbox" | CTRL_SPINBOX | 同上 |
在 MiniGUI 中,通过调用 CreateWindow 函数,可以建立某个控件类的一个实例。控件类既可以是表 1 中预定义 MiniGUI 控件类,也可以是用户自定义的控件类。与 CreateWindow 函数相关的几个函数的原型如下(include/window.h):
|
CreateWindow 函数建立一个子窗口,即控件。它指定了控件类、控件标题、控件风格,以及窗口的初始位置和大小。该函数同时指定子窗口的父窗口。 CreateWindowEx 函数的功能和 CreateWindow 函数一致,不过,可以通过 CreateWindowEx 函数指定控件的扩展风格。DestroyWindow 函数用来销毁用上述两个函数建立的控件或者子窗口。
清单 1 中的程序,利用预定义控件类创建控件。其中hStaticWnd1 是建立在主窗口 hWnd 中的静态框;hButton1、hButton2、hEdit1、hStaticWnd2则是建立在 hStaicWnd1 内部的几个控件,并作为 hStaticWnd1 的子控件而存在,建立了两个按钮、一个编辑框和一个静态按钮;而 hEdit2 是 hStaicWnd2 的子控件,是 hStaticWnd1 的子子控件。
清单1 利用预定义控件类创建控件
|
用户也可以通过 RegisterWindowClass 函数注册自己的控件类,并建立该控件类的控件实例。如果程序不再使用某个自定义的控件类,则应该使用 UnregisterWindowClass 函数注销自定义的控件类。上述两个函数以及和窗口类相关函数的原型如下(include/window.h):
|
RegisterWindowClass 通过 pWndClass 结构注册一个控件类;UnregisterWindowClass 函数则注销指定的控件类;GetClassName 活得窗口的对应窗口类名称,对主窗口而言,窗口类名称为"MAINWINDOW";GetWindowClassInfo 分别用来获取和指定特定窗口类的属性。
清单 2 中的程序,定义并注册了一个自己的控件类。该控件用来显示安装程序的步骤信息,MSG_SET_STEP_INFO 消息用来定义该控件中显示的所有步骤信息,包括所有步骤名称及其简单描述。MSG_SET_CURR_STEP 消息用来指定当前步骤,控件将高亮显示当前步骤。
清单2 定义并注册自定义控件类
|
采用控件类和控件实例的结构,不仅可以提高代码的可重用性,而且还可以方便地对已有控件类进行扩展。比如,在需要建立一个只允许输入数字的编辑框 时,就可以通过重载已有编辑框控件类而实现,而不需要重新编写一个新的控件类。在 MiniGUI 中,这种技术称为子类化或者窗口派生。子类化的方法有三种:
在 MiniGUI 中,控件的子类化实际是通过替换已有的窗口过程实现的。清单 3 中的代码就通过控件类创建了两个子类化的编辑框,一个只能输入数字,而另一个只能输入字母:
清单 3 控件的子类化
|
在 MiniGUI 中,对话框是一类特殊的主窗口,这种主窗口只关注与用户的交互――向用户提供输出信息,但更多的是用于用户输入。对话框可以理解为子类化之后的主窗口类。 它针对对话框的特殊性(即用户交互)进行了特殊设计。比如用户可以使用 TAB 键遍历控件、可以利用 ENTER 键表示默认输入等等。在 MiniGUI 当中,在建立对话框之前,首先需要定义一个对话框模板,该模板中定义了对话框本身的一些属性,比如位置和大小等等,同时定义了对话框中所有控件的初始信 息,包括位置、大小、风格等等。在 MiniGUI 中,用两个结构来表示对话框模板(src/window.h):
|
结构 CTRLDATA 用来定义控件,DLGTEMPLATE 用来定义对话框本身。在程序中,应该首先利用 CTRLDATA 定义对话框中所有的控件,并用数组表示。控件在该数组中的顺序,也就是对话框中用户按 TAB 键时的控件切换顺序。然后定义对话框,指定对话框中的控件数目,并指定 DLGTEMPLATE 结构中的 controls 指针指向定义控件的数组。如清单 4 所示。
清单 4 对话框模板的定义
|
在定义了对话框模板数据之后,需要定义对话框的回调函数,并调用DialogBoxIndirectParam 函数建立对话框,如清单 5 所示,所建立的对话框如图 1 所示。
清单 5 定义对话框回调函数,并建立对话框
|
DialogBoxIndirectParam 以及相关函数的原型如下:
|
在 DialogBoxIndirectParam 中,需要指定对话框模板(pDlgTemplate)、对话框的托管主窗口句柄(hOwner)、对话框回调函数地址(DlgProc),以及要传递到对 话框过程的参数值(lParam)。EndDialog 用来结束对话框过程。DestroyAllControls 用来销毁对话框(包括主窗口)中的所有子控件。
在清单 5 中,对话框回调函数并没有进行任何实质性的工作,当用户按下"确定"按钮时,调用 EndDialog 函数直接返回。
对话框回调函数是一类特殊的主窗口回调函数。用户在定义自己的对话框回调函数时,需要处理 MSG_INITDIALOG 消息。该消息是在 MiniGUI 建立根据对话框模板建立对话框以及控件之后,发送到对话框回调函数的。该消息的 lParam 参数包含了由 DialogBoxIndirectParam 函数的第四个参数传递到对话框回调函数的值。用户可以利用该值进行对话框的初始化,或者保存起来以备后用。例如,清单 7 中的程序将 MSG_INITDIALOG 消息的 lParam 参数保存到了对话框窗口句柄的附加数据中,这样可以确保在任何需要的时候,方便地从对话框窗口的附加数据中获取这一数据。
|
通常而言,传递到对话框回调函数中的参数是一个结构的指针,该结构包含一些初始化对话框的数据,同时也可以将对话框的输入数据保存下来并传递到对话框之外使用。
简单而言,模态对话框就是显示之后,用户不能再切换到其他主窗口进行工作的对话框,而只能在关闭之后,才能使用其他的主窗口。MiniGUI 中,使用 DialogBoxIndirectParam 函数建立的对话框就是模态对话框。实际上,该对话框首先根据模板建立对话框,然后禁止其托管主窗口,并在主窗口的 MSG_CREATE 消息中创建控件,并发送 MSG_INITDIALOG 消息给回调函数,最终建立一个新的消息循环,并进入该消息循环,直到程序调用 EndDialog 函数为止。
实际上,我们也可以在 MiniGUI 中利用对话框模板建立普通的主窗口,即非模态对话框。这时,我们使用 CreateMainWindowIndirect 函数。该函数以及相关函数的原型如下(src/window.h):
|
对话框编程是 MiniGUI 应用开发中使用最为常见的一种技术。通过定义一个对话框模板,就可以自动创建一个具有复杂输入输出界面的对话框。本文讲述了 MiniGUI 中的控件类和控件实例的关系,并举例说明控件子类化的概念及应用;然后讲解了 MiniGUI 对话框的编程技术,包括对话框模板的定义和对话框回调函数的编程;最后说明了模态对话框和非模态对话框之间的区别。