当我们建立了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 < sizeof(lookup) / sizeof(lookup[0]); n++) { if(lookup[n].code == c) { c = lookup[n].result; break; } } if(c <= KEY_MAX) { c = cl_editpassword(me, c); } else if(c <= MAX_FORM_COMMAND) { c = cl_editpassword(me, c); } return(c); }
这个函数的主要作用是将输入的字符与请求值对应。 注意:cl_editpassword()为密码域处理函数,取自linux-HOWTO-curses的示例程序的form部分。 第二部就是再定义一个函数将请求值数值化,看起来多此一举,确实如此,但这样使程序结构更清晰。
int cl_getrequest(c) int c; {
int rc = 0; switch(c) { case MAX_FORM_COMMAND + 1 : rc = 1; break; case MAX_FORM_COMMAND + 2 : rc = 2; break; case MAX_FORM_COMMAND + 3 : rc = 3; break; case MAX_FORM_COMMAND + 4 : rc = 4; break; case MAX_FORM_COMMAND + 5 : rc = 5; break; case MAX_FORM_COMMAND + 6 : rc = 6; break; case MAX_FORM_COMMAND + 7 : rc = 7; break; case MAX_FORM_COMMAND + 8 : rc = 8; break; case MAX_FORM_COMMAND + 9 : rc = 9; break; default : rc = 0; beep(); } return(rc); } 此函数就是将用户自定义的请求转换成数值,以便在程序中唯一确定一个请求。
接下来就要处理用户表单输入了: 假使有一个输入函数,它在参数给出的form中输入数据,同时响应用户各种各样的请求,那么我们可以以以下结构编写这个输入处理函数:
int cl_inputdata(win, form, imask, idata) WINDOW *win; FORM *form; unsigned long imask; char *idata; { int rc; int finish = 0; int fldidx; int fldcnt; int rows, cols, frow, fcol, nrow, nbuf;
FIELD **flist = (FIELD **)0;
fldcnt = field_count(form); cl_setcurrent(win);
cl_outputdata(win, form, imask, idata, 1); memset(idata, 0, DATA_DEFAU_LEN);
while(!finish) { switch(form_driver(form, rc = cl_virtualize(form, workwin))) { case E_OK: break; case E_UNKNOWN_COMMAND : switch(cl_getrequest(rc)) { case 9: finish = 9; break; case 4: finish = 4; form_driver(form, REQ_VALIDATION); flist = form_fields(form); for(fldidx = 0; fldidx < fldcnt; fldidx++) { if(ut_getbits(imask, fldidx)) { if(field_info(flist[fldidx], &rows, &cols, &frow, &fcol, &nrow, &nbuf) == E_OK && nbuf > 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()处理。
(未完待续)
| |