16 KiB
控件编程基础
较为复杂的 GUI 系统中,都带有预定义的控件集合,它们是人机交互的主要元素。本章将说明什么是控件、控件类,并简单介绍 MiniGUI 中的预定义控件类。
1 控件和控件类
许多人对控件(或者部件)的概念已经相当熟悉了。控件可以理解为主窗口中的子窗口。这些子窗口的行为和主窗口一样,既能够接收键盘和鼠标等外部输入,也可以在自己的区域内进行输出――只是它们的所有活动被限制在主窗口中。MiniGUI 也支持子窗口,并且可以在子窗口中嵌套建立子窗口。我们将 MiniGUI 中的所有子窗口均称为控件。
在 Windows 或 X Window 中,系统会预先定义一些控件类,当利用某个控件类创建控件之后,所有属于这个控件类的控件均会具有相同的行为和外观。利用这些技术,可以确保一致的人机操作界面,而对程序员来讲,可以像搭积木一样地组建图形用户界面。MiniGUI 使用了控件类和控件的概念,并且可以方便地对已有控件进行重载,使得它有一些特殊效果。比如,需要建立一个只允许输入数字的编辑框时,就可以通过重载已有编辑框而实现,而不需要重新编写一个新的控件类。
如果读者曾经编写过 Windows 应用程序的话,应该记得在建立一个窗口之前,必须确保系统中存在新窗口所对应的窗口类。在 Windows 中,程序所建立的每个窗口,都对应着某种窗口类。这一概念和面向对象编程中的类、对象的关系类似。借用面向对象的术语,Windows 中的每个窗口实际都是某个窗口类的一个实例。在 X Window 编程中,也有类似的概念,比如我们建立的每一个 Widget
,实际都是某个 Widget
类的实例。
这样,如果程序需要建立一个窗口,就首先要确保选择正确的窗口类,因为每个窗口类决定了对应窗口实例的表象和行为。这里的表象指窗口的外观,比如窗口边框宽度,是否有标题栏等等,行为指窗口对用户输入的响应。每一个 GUI 系统都会预定义一些窗口类,常见的有按钮、列表框、滚动条、编辑框等等。如果程序要建立的窗口很特殊,就需要首先注册一个窗口类,然后建立这个窗口类的一个实例。这样就大大提高了代码的可重用性。
在 MiniGUI 中,我们认为主窗口通常是一种比较特殊的窗口。因为主窗口代码的可重用性一般很低,如果按照通常的方式为每个主窗口注册一个窗口类的话,则会导致额外不必要的存储空间,所以我们并没有在主窗口提供窗口类支持。但主窗口中的所有子窗口,即控件,均支持窗口类(控件类)的概念。MiniGUI 提供了常用的预定义控件类,包括按钮(包括单选钮、复选钮)、静态框、列表框、进度条、滑块、编辑框等等。程序也可以定制自己的控件类,注册后再创建对应的实例。
表 1 给出了 MiniGUI 预先定义的控件类和相应类名称定义。
表 1 MiniGUI 预定义的控件类和对应类名称
控件类 | 类名称 | 宏定义 | 备注 |
---|---|---|---|
静态框 | "static" | CTRL_STATIC |
见第19章 |
按钮 | "button" | CTRL_BUTTON |
见第20章 |
列表框 | "listbox" | CTRL_LISTBOX |
见第21章 |
单行编辑框 | "sledit" | CTRL_SLEDIT |
见第22章 |
多行编辑框 | "mledit" | CTRL_MLEDIT |
(同 CTRL_TEXTEDIT 控件) |
文本编辑框 | “textedit” | CTRL_TEXTEDIT |
见第22章 |
组合框 | “combobox” | CTRL_COMBOBOX |
见第23章 |
菜单按钮 | "menubutton" | CTRL_MENUBUTTON |
见第24章 |
进度条 | "progressbar" | CTRL_PRORESSBAR |
见第25章 |
滑块 | "trackbar" | CTRL_TRACKBAR |
见第26章 |
工具栏 | "newtoolbar" | CTRL_NEWTOOLBAR |
见第27章 |
属性表 | "propsheet" | CTRL_PROPSHEET |
见第28章 |
滚动窗口控件 | "ScrollWnd" | CTRL_SCROLLWND |
见第29章 |
滚动型控件 | "ScrollView" | CTRL_SCROLLVIEW |
见第30章 |
树型控件 | "treeview" | CTRL_TREEVIEW |
见第31章 |
列表型控件 | "listview" | CTRL_LISTVIEW |
见第32章 |
月历 | "MonthCalendar" | CTRL_MONTHCALENDAR |
见第33章 |
旋钮控件 | "SpinBox" | CTRL_SPINBOX |
见第34章 |
酷工具栏 | "CoolBar" | CTRL_COOLBAR |
见第35章 |
动画控件 | "Animation" | CTRL_ANIMATION |
见第36章 |
网格控件 | “gridview” | CTRL_GRidVIEW |
见第37章 |
图标型控件 | “IconView” | CTRL_ICONVIEW |
见第38章 |
滚动条控件 | "ScrollBar" | CTRL_SCROLLBAR |
见第39章 |
2 利用预定义控件类创建控件实例
在 MiniGUI 中,通过调用 CreateWindow
函数(CreateWindow
其实是 CreateWindowEx
函数的宏),可以建立某个控件类的一个实例。控件类既可以是__表 1.1__ 中预定义 MiniGUI 控件类,也可以是用户自定义的控件类。下面是与 CreateWindow
函数相关的几个函数的原型(minigui/window.h
):
HWND GUIAPI CreateWindowEx2 (const char* spClassName,
const char* spCaption, DWORD dwStyle, DWORD dwExStyle,
int id, int x, int y, int w, int h, HWND hParentWnd,
const char* werdr_name, const WINDOW_ELEMENT_ATTR* we_attrs,
DWORD dwAddData);
static inline HWND GUIAPI CreateWindowEx (const char* spClassName,
const char* spCaption, DWORD dwStyle, DWORD dwExStyle,
int id, int x, int y, int w, int h, HWND hParentWnd,
DWORD dwAddData)
{
return CreateWindowEx2 (spClassName, spCaption, dwStyle, dwExStyle,
id, x, y, w, h, hParentWnd, NULL, NULL, dwAddData);
}
#define CreateWindow(class_name, caption, style, id, x, y, w, h, parent, add_data) \
CreateWindowEx(class_name, caption, style, 0, id, x, y, w, h, parent, add_data)
BOOL GUIAPI DestroyWindow (HWND hWnd);
CreateWindow
函数建立一个子窗口,即控件。它指定了控件类 class_name
、控件标题 caption
、控件风格 style
、控件的标识符 id
、以及窗口的初始位置和大小 x, y, w, h
。该函数同时指定子窗口的父窗口 parent
。参数 add_data
用来向控件传递其特有数据的指针,该指针所指向的数据结构随控件类的不同而不同。
CreateWindowEx
函数的功能和 CreateWindow
函数一致,不过,可以通过 CreateWindowEx
函数指定控件的扩展风格 dwExstyle
。
CreateWindowEx2
函数是 MiniGUI 3.0 新增的函数,该函数在 CreateWindowEx
基础之上增加了两个新的参数:werdr_name
和 we_attrs
,分别用来指定控件所使用的渲染器名称以及渲染器属性。
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
/* 创建一个静态框 */
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);
/* 在 hStaticWnd1 中创建两个按钮控件 */
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);
/* 在 hStaticWnd1 中创建一个编辑框控件 */
hEdit1 = CreateWindow (CTRL_EDIT,
"Edit Box 1",
WS_CHILD | WS_VISIBLE | WS_BORDER,
IDC_EDIT1,
20, 80, 100, 24, hStaticWnd1, 0);
/* 在 hStaticWnd1 中创建一个静态框 hStaticWnd2 */
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);
/* 在 hStaticWnd2 中创建一个编辑框 hEdit2,这时,hEdit2 是 hStaticWnd1 的孙窗口 */
hEdit2 = CreateWindow (CTRL_EDIT,
"Edit Box 2",
WS_CHILD | WS_VISIBLE | WS_BORDER,
IDC_EDIT2,
0, 20, 100, 24, hStaticWnd2, 0);
3 控件编程涉及的内容
在控件编程中,所涉及到的内容除了控件的创建和销毁之外,一般还涉及到如下主题:
- 控件具有自己的窗口风格定义,需要在创建控件时指定需要的风格,不同的风格将使得控件具有不同的表象和行为。
- 获取或设置控件的状态、内容等。一般可通过向控件发送一些通用或者特有的消息来完成。另外,针对窗口的通用函数一般都适用于控件,例如:
ShowWindow
、MoveWindow
、EnableWindow
、SetWindowFont
等等。 - 控件内部发生某种事件时,会通过通知消息通知其父窗口。通知消息一般通过
MSG_COMMAND
消息发送,该消息的wParam
参数由子窗口标识符和通知码组成,lParam
参数含有发出通知消息的控件句柄。例如,当用户修改编辑框中的内容时,编辑框会向父窗口发出EN_CHANGE
通知消息。如果父窗口的窗口过程需要了解这一变化,则应该在父窗口的窗口过程中如下处理该通知消息:
switch (message) {
case MSG_COMMAND:
{
int id = LOWORD(wParam);
int nc = HIWORD(wParam);
if (id == ID_MYEDIT && nc == EN_CHANGE) {
/* 用户修改了子窗口 ID_MYEDIT 编辑框的内容,现在做进一步处理... */
}
}
break;
}
- MiniGUI v1.2.6 及其后版本中针对控件的通知消息处理引入了
SetNotificationCallback
函数,该函数可以为控件设置一个通知消息的回调函数。当控件有通知消息时,将调用该函数,而不是发送通知消息到父窗口。新的应用程序应尽量使用这个函数来处理控件的通知消息,以便获得良好的程序结构。本指南示例程序全部使用这一接口来处理控件的通知消息。
清单 2 中的函数使用预定义控件类建立了一个简单的对话框。当用户在编辑框中输入以毫米为单位的数据时,系统将在编辑框之下的静态框中显示对应的以英寸为单位的数据,并在用户选择“确定”按钮时将用户输入的数据返回到调用该对话框的程序。
清单 2 使用预定义控件实现简单输入对话框
#include <stdio.h>
#include <stdlib.h>
#include <minigui/common.h>
#include <minigui/minigui.h>
#include <minigui/gdi.h>
#include <minigui/window.h>
#include <minigui/control.h>
/* 定义对话框模板 */
static DLGTEMPLATE DlgBoxInputLen =
{
WS_BORDER | WS_CAPTION,
WS_EX_NONE,
120, 150, 400, 160,
"请输入长度",
0, 0,
4, NULL,
0
};
#define IDC_SIZE_MM 100
#define IDC_SIZE_INCH 110
/*
* 该对话框一共含有 4 个控件,分别用于显示提示信息、
* 用户输入框、显示转换后的长度值以及关闭程序用的“确定”按钮。
*/
static CTRLDATA CtrlInputLen [] =
{
{
CTRL_STATIC,
WS_VISIBLE | SS_SIMPLE,
10, 10, 380, 18,
IDC_STATIC,
"请输入长度(单位:毫米)",
0
},
{
CTRL_EDIT,
WS_VISIBLE | WS_TABSTOP | WS_BORDER,
10, 40, 380, 24,
IDC_SIZE_MM,
NULL,
0
},
{
CTRL_STATIC,
WS_VISIBLE | SS_SIMPLE,
10, 70, 380, 18,
IDC_SIZE_INCH,
"相当于 0.00 英寸",
0
},
{
CTRL_BUTTON,
WS_TABSTOP | WS_VISIBLE | BS_DEFPUSHBUTTON,
170, 100, 60, 25,
IDOK,
"确定",
0
}
};
/* 这是输入框的通知回调函数。*/
static void my_notif_proc (HWND hwnd, int id, int nc, DWORD add_data)
{
/* 当输入框中的值发生变化时,取出值并转换成英寸显示在英寸框中。
*/
if (id == IDC_SIZE_MM && nc == EN_CHANGE) {
char buff [60];
double len;
GetWindowText (hwnd, buff, 32);
len = atof (buff);
len = len / 25.4;
sprintf (buff, "相当于 %.5f 英寸", len);
SetDlgItemText (GetParent (hwnd), IDC_SIZE_INCH, buff);
}
}
/* 该对话框的窗口过程 */
static int InputLenDialogBoxProc (HWND hDlg, int message, WPARAM wParam, LPARAM lParam)
{
switch (message) {
case MSG_INITDIALOG:
/* 将通过 DialogBoxIndirectParam 的最后一个参数传递进入的指针
* 以窗口附加数据的形式保存下来,以便在以后使用。
*/
SetWindowAdditionalData (hDlg, lParam);
/* 设置控件的通知回调函数。
*/
SetNotificationCallback (GetDlgItem (hDlg, IDC_SIZE_MM), my_notif_proc);
return 1;
case MSG_COMMAND:
switch (wParam) {
case IDOK:
{
char buff [40];
/* 从输入框中获得数据,并保存在传入的指针中。
*/
double* length = (double*) GetWindowAdditionalData (hDlg);
GetWindowText (GetDlgItem (hDlg, IDC_SIZE_MM), buff, 32);
*length = atof (buff);
}
case IDCANCEL:
EndDialog (hDlg, wParam);
break;
}
break;
}
return DefaultDialogProc (hDlg, message, wParam, lParam);
}
static void InputLenDialogBox (HWND hWnd, double* length)
{
DlgBoxInputLen.controls = CtrlInputLen;
DialogBoxIndirectParam (&DlgBoxInputLen, hWnd, InputLenDialogBoxProc, (LPARAM)length);
}
int MiniGUIMain (int argc, const char* argv[])
{
double length;
#ifdef _MGRM_PROCESSES
JoinLayer(NAME_DEF_LAYER , "input" , 0 , 0);
#endif
InputLenDialogBox (HWND_DESKTOP, &length);
/* 把用户在对话框中输入的值打印在终端上。
*/
printf ("The length is %.5f mm.\n", length);
return 0;
}
#ifndef _MGRM_PROCESSES
#include <minigui/dti.c>
#endif
清单 2 程序的运行效果见__图 1__。该程序的完整源代码请见本指南示例程序包 mg-samples
中的 input.c
文件。
图 1 简单输入对话框
在本指南第 4 篇中,我们将详细介绍 MiniGUI 的各个预定义控件,我们将主要从三个方面介绍所有预定义控件:控件的用途、控件风格、控件消息以及控件的通知消息,并给出控件的编程实例。
4 控件专用的操作函数
MiniGUI 提供了一些控件专用的操作函数,见__表 2__。
表 2 通用控件操作函数
函数名称 | 用途 | 备注 |
---|---|---|
GetNotificationCallback |
获取控件的通知消息回调函数 | 在 MiniGUI 1.2.6 版本中出现 |
SetNotificationCallback |
设置控件的通知消息回调函数 | (同上) |
NotifyparentEx |
发送控件通知消息 |