Chinaunix首页 | 论坛 | 博客
  • 博客访问: 101156731
  • 博文数量: 19283
  • 博客积分: 9968
  • 博客等级: 上将
  • 技术积分: 196062
  • 用 户 组: 普通用户
  • 注册时间: 2007-02-07 14:28
文章分类

全部博文(19283)

文章存档

2011年(1)

2009年(125)

2008年(19094)

2007年(63)

分类:

2008-03-25 20:59:32


 

当我们建立了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()处理。

(未完待续)

阅读(363) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~