Chinaunix
标题:
在Sco下利用Curses及Form建立简单的用户界面(一)
[打印本页]
作者:
unixsystem
时间:
2007-01-03 12:01
标题:
在Sco下利用Curses及Form建立简单的用户界面(一)
今年在sco 5.0.5/informix7.31下做了一个住房公积金程序,因此与esql/c和curses打了一些交道,在利用curses做界面时,总结了一些小经验:
在Sco下利用Curses/Form建立简单的用户界面
1.什么是Curses
Curses -- CRT屏幕处理和优化软件包,它包含许多例程.
Curses库例程给程序员一个不依赖特定终端的方法来合理优化更新字符屏幕.幕后功臣为terminfo/termcap.
2.Curses包的功能
.全部的屏幕窗口基垫的操作
.向窗口和基垫的输出
.读取终端的输入
.控制终端和curses的输入输出选项
.环境查询例程
.颜色操纵
.软标签的使用
.terminfo访问
.访问低层curses例程
3.开始使用curses
要想使用curses,必须将其初始化,就是在使用curses其它例程之前,必须先调用initscr()或new_term()例程.initscr()初始化curses,设定必要的变量,返回一个WINDOW结构指针.下面是一个典型的初始化流程.
int slk_you = 0;
WINDOW *cl_initwindow(slk_flag)
int slk_flag;
{
int i;
if(slk_flag) {
slk_init(SLK_CENT);
for(i = 1; i <= 8; i++)
slk_set(i, " ", 0);
slk_attron(A_DIM | A_REVERSE);
slk_refresh();
slk_you = 1;
}
initscr();
noecho();
nonl();
cbreak();
meta(stdscr, TRUE);
intrflush(stdscr, TRUE);
keypad(stdscr, TRUE);
cl_setcolor();
refresh();
return(stdscr);
}
slk_flag是要你指明是否使用软标签,软标签就是在屏幕最底端的一行提示区,一般8个,每个8个字符.你可以在里面写上诸如功能键用法等提示信息.如果你想使用软标签功能,你必须在initscr()之前就调用slk_init(),它使终端屏幕减少一行.程序往下设置了8个标签.
接下来调用initscr()填充一个WINDOW结构,初始化标准屏幕stdscr,决定终端的行数和列数(ROWS,COLS).
注意:WINDOW结构贯穿curses始终,它有几个重要的成员可能对你很有用:
short _cury, _curx; 当前光标在窗口上的坐标
short _maxy, _maxx; 此窗口的最大坐标(右下角)
short _begy, _begx; 左上角
chtype _attr; 当前窗口字符属性
该结构定义在/usr/include/tinfo.h中,有兴趣的可以查看这个文件.
noecho()要求输入字符不马上回显.
nonl()要求不换行.
keypad()读取终端键值表,如果激活,用户可以使用功能键.
cbreak()将终端设成CBREAK模式,在此模式下用户键入的字符立即被读取,且erase/kill字符也被处理,不用等到新行或回车键入.
intrflush()快速更新屏幕,调用之后,当键盘上键入一个中断键(break, ^c)等,tty驱动程序队列中的所有输入都被刷新.它容易产生花屏.
开始时,无论终端是返回7位还是8位字符,都有tty驱动程序的控制模式决定,调用meta()将强制返回8位(以TRUE为参数),它是本地化(L10N)的一部分.
注意:curses为了使屏幕输出达到最优化,它控制一个虚拟屏幕和一个物理屏幕.当调用函数企图改变屏幕上的画面时,curses并不会将物理屏幕立即改变,而只写到虚屏,直到调用wrefresh()/refresh()之后,才将刚才所做的改动一次性写到物理屏幕,其余资料保持不变,使传送到物理屏幕的字符数最少.也就是说,你希望改变画面时,必须调用refresh()/wrefresh(),才能看到你希望的结果,refresh()针对stdscr,wrefresh()针对特定窗口.wrefresh()/refresh()在curses编程中非常重要,它的调用时机要好好考虑,依我看,这主要靠编程经验.
以上这个函数就可以初始化curses了,基本上不用改动就可以用于任何curses程序.
4.规划屏幕
一般的终端屏幕是24X80的尺寸,在curses中屏幕/窗口的坐标是纵前横后的,也就是一个点的坐标为(y, x),如果使用软标签,则屏幕行数y减少一行.
屏幕的规划有使用全屏幕的,也有上下的也有左右的,我喜欢上下的堆叠窗口式的.一般将,就是将屏幕分为上下几个窗口,做为各种功能区,如抬头区,工作区,消息区等.我们来建立三个窗口做为这些区域,我们来设计一个建立各种窗口的函数:
WINDOW *cl_createwindow(nlines, ncols, begin_y, begin_x)
int nlines, ncols;
int begin_y, begin_x;
{
WINDOW *win;
win = newwin(nlines, ncols, begin_y, begin_x);
keypad(win, TRUE);
meta(win, TRUE);
return(win);
}
此函数用newwin()建立了一个有nlines行ncols列的新窗口,窗口的左上角在(beng_y, begin_x);同时也为此窗口设置了输入/输出选项.
前面提到建立三个区域,因为要在程序的各处使用他们,所以将这三个窗口定义为全局变量:
WINDOW *headwin, *workwin, *msgwin;
int cl_beginwork(slk_flag)
int slk_flag;
{
cl_initwindow(slk_flag);
workwin = NULL; headwin = NULL; msgwin = NULL;
if((workwin = cl_createwindow(WORK_ROWS, WORK_COLS, \
WORK_S_Y, WORK_S_X)) == NULL)
return(-123);
if((msgwin = cl_createwindow(MSG_ROWS, MSG_COLS, MSG_S_Y, 0)) == NULL)
return(-124);
if((headwin = cl_createwindow(2, 80, 0, 0)) == NULL)
return(-125);
wrefresh(headwin);
wrefresh(workwin);
wrefresh(msgwin);
cl_setcurrent(workwin);
return(0);
}
这个headwin主要用于显示一些日期 系统 机构 部门等信息,占用两行,msgwin是操作结果提示区,workwin是主要工作区,用于以后的form(表单)窗口.此函数建立三个窗口后,用cl_setcurrent()将workwin设为当前窗口.
void cl_setcurrent(win)
WINDOW *win;
{
touchwin(win);
curwin = win;
}
touchwin()的主要作用是使窗口成为输入焦点,以便接收用户输入.
利用touchwin()和wrefresh()可以产生pop窗口:
swin = newwin(...); /* 建立窗口 */
box(swin, 0, 0); /* 画框 */
mvwprintw(swin, 1, 1, "This is a popup window!");
/* 以上只是在子窗口操作,但不写到物理屏幕 */
mvprintw(10, 20, "This is standard screen!");
refresh();
/* 对stdscr操作, 画到物理屏幕 */
mvmgetch(stdscr, 20, 0);
touchwin(swin);
wrefresh(swin);
/* 上两个调用映射swin, 将覆盖stdscr的内容 */
mvwgetch(stdscr, 20, 0);
touchwin(stdscr);
refresh();
/* 上两个调用也必要,暴露stdscr,其原内容不变 */
这个小技巧给那些喜欢花里胡哨的人,因为大多数时候用不到popup窗口.
5.提示信息的显示
我们要将信息显示在msgwin中,信息包括操作提示,出错提示,确认提示等,这里有几个函数来显示信息:
void cl_dispmsg(w, begin_y, begin_x, msg, waitfor)
WINDOW *w;
int begin_y, begin_x;
char *msg;
BOOL waitfor;
{
if(msg == NULL) return;
mvwprintw(w, begin_y, begin_x, "%s", msg);
if(waitfor) wgetch(w);
}
这个函数将信息msg显示到指定的窗口w的begin_y,begin_x处,还可以决定是否等待.
我们可能不希望显示信息总在那里,有时继续操作时要抹掉它:
void cl_clearline(w, begin, lines)
WINDOW *w;
int begin, lines;
{
while(lines-- > 0) {
wmove(w, begin++, 0);
wclrtoeol(w);
}
}
它从一行的开头清除到行尾,一共可以清除lines行,这样可以如下调用来清除提示信息:
cl_clearline(msgwin, 0, 1);
当我们希望给用户一个操作提示之后,等待他确认操作有效性时,可以利用下面的函数:
int cl_yesno(win, y, x, prmp, defau)
WINDOW *win;
int y, x;
char *prmp;
char defau;
{
int ch;
ch = defau == 'Y' || defau == 'y'? 'Y' : 'N';
mvwprintw(win, y, x, "%s(Y/N): [%c]", prmp, ch);
wmove(win, y, x = win->_curx - 2);
wrefresh(win);
while(1) {
switch(wgetch(win)) {
case 'Y' :
case 'y' :
case '1' :
ch = 'Y';
break;
case 'N' :
case 'n' :
case '0' :
case 'q' :
case 'Q' :
ch = 'N';
break;
case '\r' :
break;
default :
beep();
continue;
}
waddch(win, ch);
wrefresh(win);
return ch == 'Y';
}
}
这个函数有一些技巧,他在提示时提供个默认值'Y'或'N',然后利用WINDOW结构成员_curx使光标移动回默认值之下,等待用户按键,除指定的那些键之外都不反应,最后返回ch是否等于'Y'.
有时在程序中硬编码提示信息很不方便,因为提示变更时你得重新编辑编译程序,所以我们将提示信息加到一个文本文件中,由一个字符串标识,这样在程序中可以使用该标识字符串来引用提示信息,而信息的实际内容在文本文件中,可以随时更改,文件内容可以着样:
E001|无法打开指定表单!
E002|此域不能为空!
.
.
.
前面为提示信息的标识符,后面是要在终端显示的具体内容,现在关键是我们怎样在程序中利用标识符来引用提示信息.
因为在文件中使用了一个分隔符'|',所以在程序中要用grep来提取,然后要用到一个按分隔符分解字符串的函数:
CLERROR *ut_geterror(errseri)
char *errseri;
{
int i;
int pos = 0;
char *errorstr;
char *pipecmd;
CLERROR *errlist;
FILE *pfp;
errlist = (CLERROR *)calloc(ERR_NUM + 1, sizeof(CLERROR));
pipecmd = (char *)malloc(100);
memset(pipecmd, 0, 100);
errorstr = (char *)malloc(100);
memset(errorstr, 0, 100);
sprintf(pipecmd, "grep %s ../../etc/Clerror.ch | tail -1", errseri);
if((pfp = popen(pipecmd, "r")) == NULL) {
perror("Cannot open file");
return((CLERROR *)NULL);
}
while(fgets(errorstr, 80, pfp) != NULL)
for(i = 0; i < (ERR_NUM + 1); i++) {
strncpy(errlist[i].errseri, errseri, 4);
strncpy(errlist[i].errstr, \
ut_readtext(errorstr, DELIM_STR, &pos), 70);
}
pclose(pfp);
return(errlist);
}
char *ut_readtext(str, delimit, pos)
char *str;
char *delimit;
int *pos;
{
char *p;
char *item;
int item_len;
if( *pos == strlen((char *)str) )
return((char *)NULL);
p = (char *)strstr((char *)&str[*pos], delimit);
if(p != NULL) {
item_len = p -(char *)&str[*pos];
item = (char *)malloc(item_len + 1);
memset(item, 0, item_len +1);
memcpy(item, &str[*pos], item_len);
*pos = *pos + item_len + strlen(delimit);
}
else if(strlen((char *)&str[*pos]) > 0) {
item_len = strlen((char *)&str[*pos]);
item = (char *)malloc(item_len + 1);
memset(item, 0, item_len + 1);
memcpy(item, &str[*pos], item_len);
}
else return((char *)NULL);
return(item);
}
注意:上面的ut_readtext()函数取自《Unix/linux下curses库开发指南》一书,如需使用,请尊重愿作者。
6.显示抬头信息
抬头信息显示在headwin中,至于显示那些信息由个人而定.
7.另外一些用得上的函数
在curses环境中,希望调用一个shell,然后返回,这需要一些特殊处理,有两个例程来处理这个情况 reset_shell_mode()和reset_prog_mode() 它们两个重新设置终端到shell模式和curses模式.
int ut_runcommand(s)
char *s;
{
int status, pid, w;
void (*istat)(), (*qstat)();
if ( (pid = fork()) == 0 ) {
ut_closeall();
reset_shell_mode();
if ( !s ) signal(SIGINT, SIG_DFL);
execl("/bin/sh", "sh", s? "-c": 0, s, 0);
_exit(127);
}
istat = (void (*)())signal(SIGINT, SIG_IGN);
qstat = (void (*)())signal(SIGQUIT, SIG_IGN);
while( (w = wait(&status)) != pid && w != -1 );
if ( w == -1 ) status = -1;
signal(SIGINT, istat); signal(SIGQUIT, qstat);
reset_prog_mode();
return(status);
}
此函数在子进程中引用一个shell来执行指定的命令s,并且处理信号SIGINT和SIGQUIT.
其中的ut_closeall()关闭0 1 2文件.
读出并显示文本文件:
int cl_dispfile(win, y, x, txtname)
WINDOW *win;
int y, x;
char *txtname;
{
int i, j;
long int ch;
int ret;
FILE *txtfp;
if((txtfp = fopen(txtname, "rb")) == NULL) {
cl_disperror(msgwin, ut_geterror("E001"), A_REVERSE);
return(0);
}
cl_dispprompt(ut_getprompt("P003"));
i = x;
j = y;
while((ch = fgetc(txtfp)) != EOF) {
if(i++ > COLS) {
i = x; ++j;
}
switch(ch) {
case 0x0a :
++j;
i = x;
mvwaddch(win, j, i, ch);
break;
default :
mvwaddch(win, j, i, ch);
break;
}
if(j == FORM_EROW - 1) {
loop1:
wmove(win, FORM_EROW - 1, 0);
ret = wgetch(win);
switch(ret) {
case 0x0e :
case '\r' :
cl_fullregion(win, y, x,
FORM_EROW - FORM_SROW - 2, COLS, ' ');
wrefresh(win);
j = y;
i = x;
break;
case 'q' :
case 0x1b :
cl_fullregion(win, y, x,
FORM_EROW - FORM_SROW - 2, COLS, 0);
wrefresh(win);
return(1);
default :
beep();
goto loop1;
}
}
}
wrefresh(win);
return(1);
}
这是个简单版本,你还可以扩充,如指定显示宽度 高度等,还可以控制上翻下翻,显示文件行数当前行等等信息.这个函数中注意两点:一是ch要定义成long int 或 int,不要定义成char,这样才可以显示中文字符,还有就是TAB字符的处理.
8.结束工作
在curses结束时,要退出curses模式,使终端回到shell模式,并且将输入/输出还原.
void cl_endwindow()
{
if(workwin) delwin(workwin);
if(headwin) delwin(headwin);
if(msgwin) delwin(msgwin);
if(slk_you)
slk_clear();
clear();
endwin();
}
其实直接用endwin()也可以正常退出,但是我们已经建立了三个窗口,并且有可能使用了软标签,所以还做了一些清理工作.
(未完待续)
复制代码
作者:
ivanleung
时间:
2007-01-03 18:37
好,楼主真是无私
作者:
ChinaOK
时间:
2007-01-04 13:06
期待(二)
作者:
johnsilver
时间:
2007-02-04 20:24
提示:
作者被禁止或删除 内容自动屏蔽
欢迎光临 Chinaunix (http://bbs.chinaunix.net/)
Powered by Discuz! X3.2