- 论坛徽章:
- 1
|
当我们建立了workwin,headwin,msgwin之后,在headwin上显示了一些抬头信息,在msgwin中显示了提示信息,而workwin留着为用户提供一个真正的输入/输出界面。
有时,我们希望在屏幕上看到象一些银行系统中使用的画面一样,一些项目、后面跟着要求你输入或随时依条件显示的内容,这些都是用户提供的,不可能在编程时确定。
利用curses例程主要是wgetch()可以编写出接受用户输入的程序,可以实现在屏幕上提供一块接收用户输入数据区域的功能,有时我们将这个区域叫域(field),而将含有一些域的屏幕叫表单(form)。利用wgetch()等例程可以实现表单和域,也可以使域只接收固定类型的值(比如只接收数值)、自动换行等,但要这么实现,你得做大量工作,并且要考虑周全,除非你只想实现一个简单的演示界面。
当然,界面的简单与否在于用户的看法,应该使之具有一定提示性,让用户明白他们应该在哪里输入,输入什么,以及具有一定的方便性,输入方式的一致性,以使用户使用便捷,比如各form的功能键定义应一致。
curses库中提供了一组有关form和field的例程,他们组成了libform.a库,他给程序员提供了一个操作form和field的高级接口,以使程序员不必自己考虑如何建立form和field,但是即使这样,用form库编程,也是一件麻烦事。
1、form介绍
一个表单(form)是包含很多域(field)的集合,每个域可以是提示(标签)也可以是数据输入项。
通常通过以下几个步骤控制一个form程序:
。初始化curses。
。使用new_field()建立form所用的域。
。建立form并张贴它。
。刷新屏幕。
。建立一个循环,处理用户请求。
。当得到用户退出请求或,unpost表单。
。释放form所占内存。
。释放fields所占内存。
。终止curses。
表单中的域可以有多行,这里只考虑单行域。
2、建立form
要建立form首先要建立他所包含的域,建立域的函数是如下格式:
FIELD *new_field(height, width, top, left, offscreen, nbuf)
int height, width;
int top, left;
int offscreen;
int nbuf;
头两个参数为域的高度和宽度,top和left指明域的左上角坐标,offscreen通常为0。
最重要的参数是nbuf,他指明你希望为这个域分配的缓冲区的个数,form库为每个field分配一个工作缓冲区,大小是:
(height + offscreen) * width + 1
域中的每个字符位置对应缓冲区的一个字符位置,最后缓冲区内的结尾加上一个null字符以标识结束,nbuf就是指明另外使用几个附加的这样的缓冲区,一般值在0--7之间,你可以使用附加的缓冲区存储你有用的数据,比如密码,因为0号缓冲区的内容默认是显示在屏幕上的。
创建表单的函数
FORM *new_form(fields)
FIELD **fields;
这个函数的作用是将前面建立的域与表单关联。
参数是一个FIELD指针数组,注意:最后一个应该是(FIELD *)NULL,这个指针数组中field的次序决定表单中域的访问次序。
域的属性
除了域缓冲区之外,每个域还有其他属性:
(1)、域选项
这是个位选择掩码(模),有以下几种定义:
O_ACTIVE O_VISIBLE O_PUBLIC O_WRAP O_BLANK O_AUTOSKIP O_PASSOK O_STATIC
默认时他们都是可用的,最有用的是O_ACTIVE、O_VISIBLE、O_AUTOSKIP。比如要建立一个标签域,它是不可以活动的,即不可以接收数据,光标也不在其上停留。比如下面的建立标签片段:
f[2] = new_field(1, 10, 4, 18, 0, 0);
field_opts_off(f[2], O_ACTIVE);
(2)、域对齐方式
int set_field_just(field, justification)
FIELD *field;
int justification;
对齐方式有:NO_JUSTICATION JUSTIFY_LEFT JUSTFY_RIGHT JUSTFY_CENTER
(3)、域的显示属性
对于单个域,可以设置前景、背景,前景对应与输入的数据,背景对应与整个域。这里的显示属性就是由attrset()设置的curses的显示属性,类型为chtype。
(4)、域数据类型
TYPE_ALPHA —— 可接收[a-zA-z]
set_field_type(field, TYPE_ALPHA, width)
int width;
width为可接收的长度。
TYPE_ALNUM —— 可接收数字和字母,不包括特殊字符。
set_field_type(field, TYPE_ALNUM, width)
TYPE_ENUM —— 可接收给定集合中包含的数据。
set_field_type(field, TYPE_ENUM, keyword_list, checkcase, checkunique)
char **keyword_list; /* 以NULL结尾的字符串数组 */
int checkcase; /* 是否区分大小写 */
int checkunique; /* 自动完成匹配工作 */
TYPE_INTEGER —— 可以接收数据限定为整数和'-'号。
set_field_type(field, TYPE_INTEGER, precision, vmin, vmax)
int precision;
long vmix, vmax;
precision : 精度;
vmin vmax :范围。
此精度表示右对齐或应有的数据宽度,如果为4,输入‘18’后将显示‘0018’。
TYPE_NUMERIC —— 十进制数。
set_field_type(field, TYPE_NUMREC, precision, vmin, vmax)
这里的精度是指小数位,与前面的整数精度意义不同。
TYPE_REGEXP —— 接受正则表达式。
set_field_type(field, TYPE_REGEXP, expression)
char *expression;
expression : 表示正则式的字符串。
用这个类型可以做出接受行如"xxx.xxx.xxx.xxx"IP地址的域,还有电话号码域、日期时间域等,可惜它不支持扩展的正则表达式。
3、张贴表单
前面我们建立了一些域f[n],并将之与表单关联了,但是现在还不能在屏幕上看到这些,表单必须张贴才能看到。
下面是一个显示表单的函数,它调用一个复杂的建立表单及其关联域的函数cl_createform(),以后再讲它,然后用域钩函数来设定当前域的显示属性,设置表单窗口和表单子窗口,最后张贴表单,刷新窗口。
注意:每个表单都有一个表单窗口和一个子窗口,一般来讲,你要在哪个窗口显示表单,就将表单窗口和子窗口设成那个窗口。
chtype inp_attr;
FORM *cl_dispform(win, hm, fhm, attr)
WINDOW *win;
char *hm, *fhm;
chtype attr;
{
static FORM *form;
inp_attr = attr;
form = cl_createform(hm, fhm);
if(!form) return((FORM *)0);
set_field_init(form, cl_onbold);
set_field_term(form, cl_offbold);
set_form_win(form, win);
set_form_sub(form, win);
post_form(form);
cl_drawline(win, 0, 0, 0, 80);
cl_drawline(win, FORM_ELINE, 0, 0, 80);
pos_form_cursor(form);
wrefresh(win);
return(form);
}
void cl_offbold(form)
FORM *form;
{
set_field_back(current_field(form), A_NORMAL);
}
void cl_onbold(form)
FORM *form;
{
set_field_back(current_field(form), inp_attr);
}
需要解释的是表单光标定位函数pos_form_cursor()。一些用户可能在表单处理过程中将表单的光标从特定位置移开,此函数就是为连续处理用户请求而设计,它使表单光标重新归位。
4、建立循环以处理用户请求
表单开发和核心部分就是建立一个驱动处理用户的请求,以达到与用户交互的目的。
表单驱动响应如下请求:
。页面浏览请求
REQ_NEXT_PAGE REQ_PREV_PAGE
REQ_LAST_PAGE REQ_FIRST_PAGE
主要用在多页表单浏览的需要。
。域间浏览请求
REQ_NEXT_FIELD REQ_UP_FIELD
......
共12个
这些请求是指明在表单中各个域之间的光标移动,域间浏览时光标的移动有许多方式。
。域内浏览请求
REQ_PREV_CHAR REQ_END_FIELD
......
共14个
这些请求是在一个域的内部的光标移动形式。
。滚动请求
REQ_SCR_FLINE等
主要用在多行域。
。域内编辑请求
域内编辑请求指示在一个域内对域中的内容的增加、删除的编辑的功能,它有两种模式:REQ_INS_MODE和REQ_OVL_MODE,完整的域内编辑请求是
REQ_DEL_CHAR REQ_DEL_PREV
REQ_CLR_EOL REQ_CLR_EOF
REC_CLR_FIELD
其中主要用到的是REQ_DEL_CHAR,REQ_CLR_FIELD。
。域校验请求
REQ_VALIDATION
这是最重要的一个请求,用来对域中的数据类型和格式进行校验,有时在我们不离开域的时候就想校验的情况下,这个请求很有用,它也负责刷新数据缓冲区。这里所说的不离开/离开是指光标仍在本域之内,或者表单就一个活动域的情况下,域填满且光标没有回到域的开头。
下面我们来研究以下,怎样做出一个表单驱动来处理用户请求:
首先,我们需要做一个用户域校验函数:cl_virtualize(),它的作用是等待用户从域窗口输入一个字符,这里有一个结构数组,它的作用是使字符和我们要用的请求对应。此函数接收到字符后查找这个结构数组,如果字符数组中找到这个字符,则返回它对应的请求,否则,直接返回字符。
int cl_virtualize(f, win)
FORM *f;
WINDOW *win;
{
int mode = REQ_OVL_MODE;
unsigned n;
int c;
FIELD *me;
struct {
int code;
int result;
} lookup[] = {
{ CTRL('A'), REQ_NEXT_CHOICE },
{ CTRL('C'), REQ_CLR_FIELD },
{ CTRL('S'), REQ_RIGHT_FIELD },
{ CTRL('D'), REQ_DOWN_FIELD },
{ CTRL('F'), REQ_LEFT_FIELD },
{ CTRL('E'), REQ_UP_FIELD },
{ CTRL('X'), REQ_DEL_CHAR },
{ CTRL('H'), REQ_DEL_PREV },
{ KEY_CR, REQ_NEXT_FIELD },
{ KEY_DOWN, REQ_NEXT_FIELD },
{ KEY_UP, REQ_PREV_FIELD },
{ KEY_LEFT, REQ_LEFT_CHAR },
{ KEY_RIGHT, REQ_RIGHT_CHAR },
{ KEY_BACKSPACE, REQ_DEL_PREV },
{ KEY_HOME, REQ_FIRST_FIELD },
{ KEY_END, REQ_LAST_FIELD },
{ KEY_NPAGE, REQ_NEXT_PAGE },
{ KEY_PPAGE, REQ_PREV_FIELD },
{ KEY_F(1), MAX_FORM_COMMAND + 1 },
{ KEY_F(2), MAX_FORM_COMMAND + 2 },
{ KEY_F(3), MAX_FORM_COMMAND + 3 },
{ KEY_F(4), MAX_FORM_COMMAND + 4 },
{ KEY_F(5), MAX_FORM_COMMAND + 5 },
{ KEY_F(6), MAX_FORM_COMMAND + 6 },
{ KEY_F(7), MAX_FORM_COMMAND + 7 },
{ KEY_F(8), MAX_FORM_COMMAND + 8 },
{ KEY_QUIT, MAX_FORM_COMMAND + 9 },
{ KEY_ESCAPE, MAX_FORM_COMMAND + 9 },
{ KEY_AUTH, MAX_FORM_COMMAND + 2 },
{ KEY_COMMIT, MAX_FORM_COMMAND + 4 }
};
c = wgetch(win);
me = current_field(f);
for(n = 0; n 0) {
strcat(idata, field_buffer(flist[fldidx], 1));
strcat(idata, "|");
} else {
strcat(idata, field_buffer(flist[fldidx], 0));
strcat(idata, "|");
}
}
}
break;
default :
finish = 0;
break;
} /* switch */
break;
default :
/*
------------------------if using this then form input no loop ---------------
finish = -1;
*/
break;
} /* switch */
} /* while */
return(rc);
}
此函数里面的idata是存放全部域数据的一块缓冲区,imask是一个掩码,指明那些域的数据有用,需要返回,cl_outputdata()是输出idata中原始数据的,这里可以不用管它。这里最主要的函数是form_driver(),这是表单驱动主函数,我们建立个循环,让它在循环中一直接收用户的输入,除非用户输入[F4] [ESC]键,form_dirver()接收从cl_virtualize()返回的字符,进行比较、校验,他的返回值有两个:E_OK,E_UNKNOWN_COMMAND,E_OK表示它接受的按键在KEY_MAX和MAX_COMMAND之间,而E_UNKNOWN_COMMAND则表示它不认识cl_virtualize()传回的键值,在这种情况下我们再用cl_getrequest()固定以下这个键值的数值代码,虽然只有4,9两个值但我们还使用了switch,便于以后扩充。在这里,当我们按下[F4]时,调用请求form_dirver(form, REQ_VALIDATION)使未结束的域输入也有效地进行检查,写到0号缓冲区内,然后置finish = 4, 结束循环;当按下[ESC]键时,直接结束循环。
5、我们按下[F4]键接收了数据或按下[ESC]键放弃数据之后,如果不再使用该表单及其关联域,我们就可以释放它们:
void cl_clearform(form)
FORM *form;
{
WINDOW *w;
FIELD **f = (FIELD **)0;
if(!form) return;
w = form_win(form);
f = form_fields(form);
unpost_form(form);
free_form(form);
wrefresh(w);
while(*f)
free_field(*f++);
return;
}
此函数首先unpost_form,再释放form空间,然后找到其关联域,一个个释放它们。
6、如果此时退出curses,则可用前面提到的cl_endwindow()处理。
(未完待续)
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u/31/showart_508064.html |
|