- 论坛徽章:
- 0
|
- 在Sco下利用Curses/Form建立简单的用户界面
- 当我们建立了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()处理。
- (未完待续)
复制代码 |
|