作者:魏永明
对话框和控件编程
本文讲述 MiniGUI 中的对话框和控件编程。首先讲解 MiniGUI 中的控件类和控件实例的关系,并举例说明控件子类化的概念及应用;其次讲解 MiniGUI 对话框的编程技术,包括对话框模板的定义和对话框回调函数的编程;最后解释模态对话框和非模态对话框之间的区别。
1 引言
对话框编程是一个快速构建用户界面的技术。通常,我们编写简单的图形用户界面时,可以通过调用 CreateWindow 函数直接创建所有需要的子窗口,即控件。但在图形用户界面比较复杂的情况下,每建立一个控件就调用一次 CreateWindow 函数,并传递许多复杂参数的方法很不可取。主要原因之一,就是程序代码和用来建立控件的数据混在一起,不利于维护。为此,一般的 GUI 系统都会提供一种机制,利用这种机制,通过指定一个模板,GUI 系统就可以根据此模板建立相应的主窗口和控件。MiniGUI 也提供这种方法,通过建立对话框模板,就可以建立模态或者非模态的对话框。
本文首先讲解组成对话框的基础,即控件的基本概念,然后讲解对话模板的定义,并说明模态和非模态对话框之间的区别以及编程技术。
2 控件和控件类
许多人对控件(或者部件,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):
904 HWND GUIAPI CreateWindowEx (const char* spClassName, const char* spCaption,
905 DWORD dwStyle, DWORD dwExStyle, int id,
906 int x, int y, int w, int h, HWND hParentWnd, DWORD dwAddData);
907 BOOL GUIAPI DestroyWindow (HWND hWnd);
908
909 #define CreateWindow(class_name, caption, style, id, x, y, w, h, parent, add_data) 910 CreateWindowEx(class_name, caption, style, 0, id, x, y, w, h, parent, add_data)
CreateWindow 函数建立一个子窗口,即控件。它指定了控件类、控件标题、控件风格,以及窗口的初始位置和大小。该函数同时指定子窗口的父窗口。CreateWindowEx 函数的功能和 CreateWindow 函数一致,不过,可以通过 CreateWindowEx 函数指定控件的扩展风格。DestroyWindow 函数用来销毁用上述两个函数建立的控件或者子窗口。
清单 1 中的程序,利用预定义控件类创建控件。其中hStaticWnd1 是建立在主窗口 hWnd 中的静态框;hButton1、hButton2、hEdit1、hStaticWnd2则是建立在 hStaicWnd1 内部的几个控件,并作为 hStaticWnd1 的子控件而存在,建立了两个按钮、一个编辑框和一个静态按钮;而 hEdit2 是 hStaicWnd2 的子控件,是 hStaticWnd1 的子子控件。
清单1 利用预定义控件类创建控件
#define IDC_STATIC1 100
#define IDC_STATIC2 150
#define IDC_BUTTON1 110
#define IDC_BUTTON2 120
#define IDC_EDIT1 130
#define IDC_EDIT2 140
int ControlTestWinProc (HWND hWnd, int message, WPARAM wParam, LPARAM lParam)
{
static HWND hStaticWnd1, hStaticWnd2, hButton1, hButton2, hEdit1, hEdit2;
switch (message) {
case MSG_CREATE:
{
hStaticWnd1 = CreateWindow (CTRL_STATIC,
"This is a static control",
WS_CHILD | SS_NOTIFY | SS_SIMPLE | WS_VISIBLE | WS_BORDER,
IDC_STATIC1,
10, 10, 180, 300, hWnd, 0);
hButton1 = CreateWindow (CTRL_BUTTON,
"Button1",
WS_CHILD | BS_PUSHBUTTON | WS_VISIBLE,
IDC_BUTTON1,
20, 20, 80, 20, hStaticWnd1, 0);
hButton2 = CreateWindow (CTRL_BUTTON,
"Button2",
WS_CHILD | BS_PUSHBUTTON | WS_VISIBLE,
IDC_BUTTON2,
20, 50, 80, 20, hStaticWnd1, 0);
hEdit1 = CreateWindow (CTRL_EDIT,
"Edit Box 1",
WS_CHILD | WS_VISIBLE | WS_BORDER,
IDC_EDIT1,
20, 80, 100, 24, hStaticWnd1, 0);
hStaticWnd2 = CreateWindow (CTRL_STATIC,
"This is child static control",
WS_CHILD | SS_NOTIFY | SS_SIMPLE | WS_VISIBLE | WS_BORDER,
IDC_STATIC1,
20, 110, 100, 50, hStaticWnd1, 0);
hEdit2 = CreateWindow (CTRL_EDIT,
"Edit Box 2",
WS_CHILD | WS_VISIBLE | WS_BORDER,
IDC_EDIT2,
0, 20, 100, 24, hStaticWnd2, 0);
break;
}
.......
}
return DefaultMainWinProc (hWnd, message, wPara