1、- UNIX 图形编程函数库(c语言) - curses(一)- 前言 相信您在网路上一定用过如tin,elm 等工具, 这些软体有项共同的特色, 即他们能利用上下左右等方向键来控制游标的位置.除此之外, 这些程式 的画面也较为美观. 对 Programming 有兴趣的朋友一定对此感到好奇, 也 许他能在 PC 上用 Turbo C 轻易地写出类似的程式, 然而, 但当他将相同 的程式一字不变地移到工作站上来编译时, 却出现一堆抓也抓不完的错误. 其实, 原因很简单, 他使用的函式库可能在 UNIX 上是没有定义的.有些 在 Turbo-C 上被广泛使用的一些函式, 可能在 UNIX 上是不
2、被定义的. 为了因应网路上各式各样的终端机形态(terminal), UNIX 上特别发展出 一套函式库, 专门用来处理 UNIX 上游标移动及萤幕的显示.这就是本篇 文章要为您介绍的 - curses.h 函式库.利用这个函式库, 您也可以写出 像 elm 般利用方向键来移动光棒位置的程式. (CCCA 近来所提供的线上选 课程式, 及程式服务界面, 即是笔者利用 curses 发展而成的 ) curses 的历史与版本 cureses 最早是由柏克莱大学的 Bill Joy 及 Ken Arnold 所发展出来的. 当时发展此一函式库主要原因是为了提高程式对不同终端机的相容性而设 计的.因
3、此, 利用 curses发展出来的程式将和您所使用的终端机无关. 也就是说, 您不必担心您的程式因为换了一部终端机而无法使用.这对程 式设计师而言, 尤其是网路上程式的撰写, 是件相当重要的一件事. curses之所以能对上百种以上的终端机工作,是因为它将所有终端机的资 料, 存放在一个叫 termcap 的资料库, ( 而在第二版的 System V 系统中 , 新版的 curses 以 terminfo 取代原来的 termcap). 有了这些记录, 程 式就能够知道遇到哪一种终端机时, 须送什麽字元才能移动游标的位置, 送什麽字元才能清除整个萤幕清除. (* 注一) 另外, 本文的介绍
4、以 System V 的 curses 版本为主. 如何在您的程式使用 curses ? 在您的 C 程式的档头将; include 进来.当您引进curses.h 这个函式库後, 系统会自动将 ; 和 ;一并include 进 来.另外,在SystemV版本中, ;这个函式库也将一并 include进来. #include ; main() : : 当然, 您的系统内必须放有 curses.h 这个函式库. 如何编译(compile) 当您编辑好您的程式, 在 UNIX 提示符号下键入: % /usr/5bin/cc file.c -lcurses 引进 curses.h 这个 librar
5、y 或 % /usr/5bin/cc file.c -lcurses -ltermlib (*注二) 如何开始我的第一个 curses 程式? 在开始使用 curses 的一切命令之前, 您必须先利用initscr()这个函式 来开启 curses 模式. 相对的, 在结束curses模式前( 通常在您结束程式前)也必须以 endwin()来关闭 curses 模式. #include ; main() initscr(); : : : endwin(); 这是一般 curses 程式标准的模式. 此外, 您可以就您程式所须, 而做不同的设定. 当然, 您可以不做设定,而 只是呼叫 inits
6、cr(). 您可以自己写一个函式来存放所有您所须要的设定.平常使用时, 只要呼 叫这个函式即可启动 curses 并完成一切设定. 下面的例子, 即是笔者将平常较常用的一些设定放在一个叫 initial()的函 式内. void initial() initscr(); cbreak(); nonl(); noecho(); intrflush(stdscr,FALSE); keypad(stdscr,TRUE); refresh(); 各函式分别介绍如下: initscr() initscr()是一般 curses 程式必须先呼叫的函数, 一但这个函数 被呼叫之後, 系统将根据终端机的形态并
7、启动 curses 模式. endwin() curses 通常以呼叫 endwin() 来结束程式.endwin() 可用来关闭 curses 模式, 或是暂时的跳离curses 模式.如果您在程式中须要 call shell ( 如呼叫 system() 函式 ) 或是需要做 system call, 就必须先以 endwin() 暂时跳离 curses模式. 最後再以 wrefresh() doupdate() 来重返 curses 模式. cbreak() nocbreak() 当 cbreak模式被开启後, 除了 DELETE 或 CTRL 等仍被视为特殊 控制字元外一切输入的字元将
8、立刻被一一读取.当处於 nocbreak 模 式时, 从键盘输入的字元将被储存在buffer 里直到输入RETURN 或 NEWLINE.在较旧版的 curses 须呼叫 crmode(),nocrmode() 来 取代 cbreak(),nocbreak() nl() nonl() 用来决定当输入资料时, 按下 RETURN 键是否被对应为 NEWLINE 字 元 ( 如 n ). 而输出资料时, NEWLINE字元是否被对应为RETURN 和 LINDFEED 系统预设是开启的. echo() noecho() 此函式用来控制从键盘输入字元时是否将字元显示在终端机上.系统 预设是开启的.
9、intrflush(win,bf) 呼叫 intrflush 时须传入两个值: win 为一 WINDOW 型态指标, 通常传入标准输出入萤幕 stdscr bf 为 TRUE 或 FALSE 当 bf 为 true 时, 当输入中断字元 ( 如 break) 时, 中断的反应 将较为快速.但可能会造成萤幕的错乱. keypad(win,bf) 呼叫 keypad 时须传入两个值: win 为一 WINDOW 型态指标, 通常传入标准输出入萤幕 stdscr bf 为 TRUE 或 FALSE 当开启keypad 後, 可以使用键盘上的一些特殊字元, 如上下左右 等方向键, curses 会将
10、这些特殊字元转换成 curses.h 内定义的一 些特殊键. 这些定义的特殊键通常以 KEY_ 开头. refresh() refresh() 为 curses 最常呼叫的一个函式. curses 为了使萤幕输出入达最佳化, 当您呼叫萤幕输出函式企图改 变萤幕上的画面时, curses并不会立刻对萤幕做改变,而是等到 refresh() 呼叫後, 才将刚才所做的变动一次完成.其馀的资料将 维持不变. 以尽可能送最少的字元至萤幕上. 减少萤幕重绘的时间. 如果是 initscr() 後第一次呼叫refresh(), curses 将做清除萤 幕的工作. 游标的控制 move(y,x) 将游标移动
11、至 x,y 的位置 getyx(win,y,x)得到目前游标的位置 (请注意! 是 y,x 而不是 &y,&x ) 有关清除萤幕的函式 clear() erase() 将整个萤幕清除 (请注意配合refresh() 使用) 如何在萤幕上显示字元 echochar(ch) 显示某个字元 addch(ch) 显示某个字元 mvaddch(y,x,ch) 在(x,y) 上显示某个字元 相当於呼叫 move(y,x);addch(ch); addstr(str) 显示一串字串 mvaddstr(y,x,str) 在(x,y) 上显示一串字串 相当於呼叫 move(y,x);addstr(str); p
12、rintw(format,str) 类似 printf() , 以一定的格式输出至萤幕 mvprintw(y,x,format,str)在(x,y) 位置上做 printw 的工作. 相当於呼叫 move(y,x);printw(format,str); 如何从键盘上读取字元 getch() 从键盘读取一个字元 (注意! 传回的是整数值) getstr() 从键盘读取一串字元 scanw(format,&arg1,&arg2.) 如同 scanf, 从键盘读取一串字元 例: int ch; char string180; /* 请注意! 不可宣告为 char *string1; */ char
13、 string280; echo(); /* 开启 echo 模式, 使输入立刻显示在萤幕上 */ ch=getch(); string1=getstr(); scanw(%s,string2); mvprintw(10,10,String1=%s,string1); mvprintw(11,10,String2=%s,string2); 如何利用方向键 curses 将一些如方向键等特殊控制字元, 以 KEY_ 为开头定义在 curses.h 这个档案里头, 如 KEY_UP即代表方向键的 .但, 如果您想使用 curses.h所为您定义的这些特殊键的话,您就必须将 keypad设定为 TR
14、UE. 否则, 您就必须自己为所有的特殊键定义了. curses.h 为一些特殊键的定义如下: KEY_UP 0403 KEY_DOWN 0402 KEY_LEFT 0404 KEY_RIGHT 0405 KEY_HOME 0406 Home key (upward+left arrow) KEY_BACKSPACE 0407 backspace (unreliable) KEY_F0 0410 Function keys. KEY_F(n) (KEY_F0+(n) Formula for f . KEY_NPAGE 0522 Next page KEY_PPAGE 0523 Previous
15、 page 以上仅列出笔者较常使用的一些控制键, 至於其他控制键的定义, 请自行参 阅 man curses (* 注三) 一并为您列出其他常用的一些特殊字元 TAB /t ENTER /r ESC 27 BACKSPACE 127 如何改变萤幕显示字元的属性 为了使输出的萤幕画面更为生动美丽,我们常须要在萤幕上做一些如反白, 闪烁等变化.curses 定义了一些特殊的属性, 透过这些定义, 我们也可以 在 curses 程式控制萤幕的输出变化. attron(mod) 开启属性 attroff(mod) 关闭属性 curses.h 里头定义了一些属性, 如: A_UNDERLINE 加底线
16、A_REVERSE 反白 A_BLINK 闪烁 A_BOLD 高亮度 A_NORMAL 标准模式 (只能配合 attrset() 使用) 当使用 attron() 开启某一种特殊属性模式後, 接下来在萤幕的输出都会以 该种属性出现. 直到您呼叫 attroff() 将此模式关闭. 请注意, 当您欲 attron() 开启另一种属性时, 请记得利用 attroff()先关 闭原来的属性, 或直接以 attrset(A_NORMAL)将所有特殊属性关闭.否则, curses 会将两种属性做重叠处理. 例: attrset(A_NORMAL); /* 先将属性设定为正常模式 */ attron(A_
17、UNDERLINE); /* 加底线 */ mvaddstr(9,10,加底线); /* 加底线输出一串字元 */ attroff(A_UNDERLINE); /* 关闭加底线模式, 恢复正常模式 */ attron(A_REVERSE); /* 开启反白模式 */ mvaddstr(10,10,反白); /* 输出一串反白字元 */ attroff(A_REVERSE); /* 关闭反白模式, 恢复正常模式 */ attron(A_BLINK); /* 开启闪烁模式 */ mvaddstr(11,10,闪烁); /* 输出一串闪烁字元 */ attroff(A_BLINK); /* 关闭闪烁
18、模式, 恢复正常模式 */ attron(A_BOLD); /* 开启高亮度模式 */ mvaddstr(12,10,高亮度); /* 输出一串高亮度字元 */ attroff(A_BOLD); /* 关闭高亮度模式, 恢复正常模式 */ 其他常用的一些函式 beep() 发出一声哔声 box(win,ch1,ch2) 自动画方框ch1: 画方框时垂直方向所用字元 ch2: 画方框时水平方向所用字元 example: box(stdscr,|,-); 将以 | 及 - 围成一个方框 应用完整例 下面所举的例子,即完全利用刚刚所介绍的含式来完成.这个程式可将从键 盘上读取的字元显示在萤幕上, 并
19、且可以上下左右方向键来控制游标的位置 , 当按下 ESC 後, 程式即结束. 您有没有发现, 这不就是一个简单全萤幕编辑器的雏形吗? #include ; /* 引进 curses.h , 并自动引进 stdio.h */ #define StartX1 /* 决定游标初始位置 */ #define StartY1 void initial(); main() int x=StartX; /* 宣告 x,y 并设定其初值 */ int y=StartY; int ch; /* 宣告 ch 为整数,配合 getch() 使用 */ initial(); /* 呼叫 initial(), 启动 c
20、urses 模式,*/ /* 并完成其它设定 */ box(stdscr,|,-); /* 画方框 */ attron(A_REVERSE); /* 开启反白模式 */ mvaddstr(0,20,Curses Program);/* 在 (20,0) 处输出反白字元 */ attroff(A_REVERSE); /* 关闭反白模式 */ move(x,y); /* 将游标移至初始位置 */ do /* 以无限回圈不断等待输入 */ ch=getch(); /* 等待自键盘输入字元 switch(ch) /* 判断输入字元为何 */ case KEY_UP: -y; /* 判断是否键被按下 *
21、/ break; case KEY_DOWN: +y; /* 判断是否键被按下 */ break; case KEY_RIGHT: +x; /* 判断是否键被按下 */ break; case KEY_LEFT: -x; /* 判断是否键被按下 */ break; case r: /* 判断是否 ENTER 键被按下 */ +y; x=0; break; case t: /* 判断是否 TAB 键被按下 */ x+=7; break; case 127: /* 判断是否 BACKSPACE 键被按下 */ mvaddch(y,-x, );/* delete 一个字元 */ break; cas
22、e 27: endwin(); /* 判断是否ESC键被按下 */ exit(1); /* 结束 curses 模式 */ /* 结束此程式 */ default: addch(ch); /* 如果不是特殊字元, 将此字元印出 */ x+; break; move(y,x); /* 移动游标至现在位置 */ while (1); void initial() /* 自定开启 curses 函式 */ initscr(); cbreak(); nonl(); noecho(); intrflush(stdscr,FALSE); keypad(stdscr,TRUE); refresh(); 後记
23、 学完了上述的一些命令,相不相信您已经可以写出一个漂亮的全萤幕编辑 器了? 事实上, curses 提供的函式不下200 个, 可是笔者认为, 一切再 复杂的函式都可以用本文提到的一些组合变化而成,学了太多的函式, 只 是徒增自己困扰罢了.当然,如果您对其它函式有兴趣,可以自行参阅 curses 说明档.( 方法: % man curses ) 本文不过行抛砖引玉之效, 也 希望未来能陆续出现更多同学自行创作的程式. * 任何疑问及建议, 欢迎 e-mail 至 ljhCCCA.NCTU.edu.tw. 谢谢 ! *注一: 请参考 /usr/share/lib/termcup /usr/sha
24、re/lib/terminfo/s/sun注二: 1.如果是 BSD 的版本, 需使用 cc file.c -lcurses -ltermcap 来完成 compile. 2.计中工作站不知何故将原来的 /usr/5bin/cc 更改为 /usr/5bin/cc.org 因此, 您若想在计中工作站 compile curses 程式.需以 /usr/5bin/cc.org 取代 /usr/5bin/cc , 否则 compile 可能发生错误. 3.较旧版的 curses 需同时引进 curses 和 termlib 这两个 library, 因此, 您必须使用 /usr/5bin/cc fi
25、le.c -lcurses -ltermlib 来 compile.注三: 根据笔者的经验, 上下左右方向键应可正常使用而不会发生问题, 但其它 如 PgUp,PgDn,功能键,Home,End 等特殊键, 很容易因机器, 键盘不同而无 法使用, 因此, 若您的程式须要在不同的机器上使用, 建议您只用方向键来 控制, 其它的特殊键少用为妙. 至於 PgUp,PgDn 一些特殊键的控制方法, 由於较为复杂, 有兴趣的同学可参 考 tin 原始程式 curses.c 内所使用的一些方法.- UNIX 图形编程函数库(c语言) - curses (二)- 在上期为您介绍完了 curses.h 函式库
26、的一些基本函式呼叫後在, 在本期里 , 我们将继续为您介绍 curses 有关多视窗处理的函式. 有了这些函式, 我们 可以在程式里同时处理多个不同的视窗.如 joe 编辑器内我们可将萤幕切割 成好几个小萤幕, 并且可以在这些不同的萤幕间做切换并编辑不同的档案, 这 就是多视处理的应用. 另外, 有关 POP-UP 视窗的制作, 以及视窗的卷动, 在 本文里, 我们将以简单的例子, 告诉您这些功能是如何做到的. 关於一些较基 本函式的用法, 我们将不再特别介绍. 如果您尚未熟悉 curses 基本函式使用 方法, 请参阅上一期 (80 期 ) 通讯. 视窗的建立 视窗的建立, 以 newwin
27、() 这个函式来完成.同时, 需宣告此视窗为 WINDOW 结构变数. WINDOW *newwin(lines,colums,start_y,start_x); WINDOW *win; win=newwin(10,20,0,0); 如此, 将以 (0,0) 为原点, 取一个 10 列 20 行的矩形为一新的视窗.今後 我们只要呼叫 win 这个变数, 就可以对这新视窗做处理. 如: wmove(win,3,2); 多视窗处理函式的格式 这一类函式和一般的基本函式极为类似, 几乎每一个基本函式都有一个对应的 视窗处理函式.一般将 w 加在函式的里头作为区别, w 乃 window 之 意.
28、另外, 因为可同时处理多个视窗, 在呼叫使用时, 需特别指定欲处理的视 窗. 当然, 如果您指定对 stdscr 做处理, 由於是对标准输出入萤幕处理, 其 作用将相当於一般基本的函式. 如: wmove(win,y,x) 即对 win 这个视窗做 move() 动作. wmove(stdscr,y,x)相当於 move(y,x) 介绍一些较重要的函式 wmove(win,y,x) touchwin(win) wrefresh(win) mvwaddstr(win,y,x,str) wattron(attr) delwin(win) subwin(win,ny,nx,y,x) 其他函式多和基本
29、函式互为对应, 故不全部列出, 详细名称可参考 curses 的 online manual. 视窗内的座标系 视窗内的座标系, 将以此视窗的起始点为新原点, 并以其相对位置作为新的 座标. 举例来说 win=newwin(10,20,5,5); wmove(win,2,3); 将以 (5,5) 为新原点,y 方向移动 2 单位, x 方向移动 3 单位. 因此实际 上, 游标将移动至 y=7 x=8 的位置上. POP-UP 视窗的建立 利用 curses 所提供的视窗处理函式, 我们可以做出像ONLINE HELP 的 POP -UP 画面. 当按下某键後, 一个新的视窗将像 跳 出来一般
30、覆盖原来的画 面. 当关掉此视窗後, 又不会影响到原来被覆盖的画面. 下面的例子, 我们及模拟 ONLINE HELP 的形式, 当按下 h 键时, 视窗即出现 #include ; main() int ch,x,y; WINDOW *win; initscr(); cbreak; 启动 curses 模式 noecho(); nonl(); win=newwin(4,30,LINES/2-3, COLS/2-15);/* 建立一个新视窗, 其中LINES,COLS*/ box(win,|,-); /* 为 curses 内定值,即萤幕行/列数*/ mvwaddstr(win,1,4,This is another screen); mvwaddstr(win,2,2,Press anykey to continue.); for (y=0;yLINES;+y) /* 以填满萤幕 */ for (x=0;xCOLS;+x) mvprintw(y,x,); for(;) refresh(); ch=getch(); switch(ch) case q: /* 按 q 键离开 */ endwin(); exit(0);