曲径通幽论坛

标题: [专题]通过 curses 管理基于文本的屏幕控制 [打印本页]

作者: beyes    时间: 2009-3-16 21:49
标题: [专题]通过 curses 管理基于文本的屏幕控制

http://www.groad.net/bbs/read.php?tid=620
http://www.groad.net/bbs/read.php?tid=624
http://www.groad.net/bbs/read.php?tid=628
中,介绍了如何更精细的控制字符的输入以及怎样以一个与具体终端无关的形式提供字符输出。
但是,这种通过使用“通用终端接口”(GTI或 termios)和用用来操控转义字符的 tarm() 及其相关函数需要许多低等级的代码。然在许多程序中,更多需要的是一个高等级的接口。我们希望能够简单的在屏幕上进行简单的绘制以及通过一个函数库来自动的处理终端的相关性问题。

curses 库在简单的文本行程序(line-based)和全图形化(一般更难以编程)X 视窗系统程序间的重要性犹如一道过渡的桥梁(halfway house),如 GTK+/GNOME 和 Qt/KDE.

Linux 确实也有个 svgalib 库(超级 VGA 库,一个底层的图形库),但这并不是一个 UNIX 标准库,所以在其他的类 UNIX 系统里并不通用。

尽管是是编写基于字符的全屏幕程序,curses 库同样显得相当的简单并且与终端无关。用 curses 总比直接使用转义序列要来得简单。curses 也可以管理键盘,并提供一个使用简单且无阻塞(nonblocking)的字符输入模式。
作者: beyes    时间: 2009-3-17 02:14
标题: 使用 curses 编译
curses 库的名字来自于它的能力---优化光标的移动以及使屏幕上所需要的更新最小化,也就是说(and hence),减少了需要发送到基于文本的终端上的字符数量。尽管字符输出量比起以前的被动式终端/基本终端(dumb terminal)和低速调制解调器来说已经显得不太重要,但是 curses 库还是为作为一个挺有用的附加工具包保留了下来。

curses 是一个函数库,我们必须从一个适当的系统库文件里把所有有关的头文件、函数和宏定义等添加到我们的程序中去,这样才能享受它给我们带来的便利。

对于 curses 库,其实已经存在几个不同的实现版本。其原始版本见 BSD UNIX;在标准化 X/Open 之前,这个版本然后被集成进 system V 系列的 UNIX 里面。 Linux 使用 ncurses("new curses"), 这是为 linux 开发的自由软件函数库。这个实现对于其他的 UNIX 版本具有极高的可移植性。甚至是也有 MS-DOS 和 Windows 的 curses 版本。如果你发现在你喜欢的 UNIX 版本中所捆绑的 curses 没有支持一些特性,那可以尝试用 ncurses 的一个拷贝来替换之。Linux 用户通常都能在他们的系统里找到已安装好的 ncurses 库,或者至少也会包含有一些基于curses(curses-based)的软件所需要的组件。如果你的发行版系统的开发库里并没有预装curses(没有 curses.h 头文件或者没有 curses 库链接),那么通常也可以在大多主要的发行版的标准软件包里找到它,在里面可能的命名为: libncurses5-dev .

X/Open 技术规格定义了 curses 的两个等级:基础的和扩展的。扩展的 curses 包含了一些附加的子程序,这里面包括了在一定范围内可以处理多栏字符和颜色操作的子程序

在编译 curses 程序时,你必须包含头文件 curses.h,并且要用 -lcurses 选项来来链接 curses 库。在许多 Linux 系统里,你可以简单的使用 curses,但你也将发现你实际上正在使用的是更优秀,更新的 ncurses 实现!

检查一下本机中 curses 的建立情况
[root@localhost ~]# ls -l /usr/include/*curses.h
lrwxrwxrwx 1 root root 16 12-02 21:39 /usr/include/curses.h -> ncurses/curses.h
lrwxrwxrwx 1 root root 17 12-02 21:39 /usr/include/ncurses.h -> ncurses/ncurses.h

看一下相关库文件
[root@localhost ~]# ls -l /usr/lib/lib*curses*
lrwxrwxrwx 1 root root     12 12-02 21:39 /usr/lib/libcurses.a -> libncurses.a
lrwxrwxrwx 1 root root     17 12-02 21:39 /usr/lib/libcurses.so -> libncurses.so.5.5
lrwxrwxrwx 1 root root     13 12-02 21:39 /usr/lib/libcursesw.a -> libncursesw.a
lrwxrwxrwx 1 root root     18 12-02 21:39 /usr/lib/libcursesw.so -> libncursesw.so.5.5
-rw-r--r-- 1 root root 469236 2007-01-06 /usr/lib/libncurses.a
-rw-r--r-- 1 root root 123506 2007-01-06 /usr/lib/libncurses++.a
-rw-r--r-- 1 root root 584508 2007-01-06 /usr/lib/libncurses_g.a
lrwxrwxrwx 1 root root     15 12-02 21:39 /usr/lib/libncurses.so -> libncurses.so.5
lrwxrwxrwx 1 root root     17 12-02 21:36 /usr/lib/libncurses.so.5 -> libncurses.so.5.5
-rwxr-xr-x 1 root root 297464 2007-01-06 /usr/lib/libncurses.so.5.5
-rw-r--r-- 1 root root 123506 2007-01-06 /usr/lib/libncurses++w.a
-rw-r--r-- 1 root root 522346 2007-01-06 /usr/lib/libncursesw.a
-rw-r--r-- 1 root root 663886 2007-01-06 /usr/lib/libncursesw_g.a
lrwxrwxrwx 1 root root     16 12-02 21:39 /usr/lib/libncursesw.so -> libncursesw.so.5
lrwxrwxrwx 1 root root     18 12-02 21:36 /usr/lib/libncursesw.so.5 -> libncursesw.so.5.5
-rwxr-xr-x 1 root root 330428 2007-01-06 /usr/lib/libncursesw.so.5.5

如上所示,若在系统里找到了 curses.h 和 ncurses.h 已经存在并有了相应链接,并且看到 ncurses 库文件也已存在,那么可以使用以下命令进行编译:
gcc program.c -o promgram.exe -lcurses

如果你的 curses 配置情况不能自动使用 ncurses,那么你必须通过包含 ncurses.h 而不是 curses.h 明确强制使用 ncurses 库,通过一下命令来实现:
gcc -I/usr/include/ncurses program.c -o program -lncurses
这里,-I 选项的作用是指定检索头文件时将要使用的子目录路径。可下载代码形式的制作文件(makefile)会假设用户缺省使用的是 ncrses 函数库,如果用户必须对此做出修改,说不定在你的系统上还不得不手工完成编译工作。
作者: beyes    时间: 2009-3-17 23:25
标题: curses 术语及概念
curses 例程工作在屏幕,视窗,以及子窗口上。“屏幕”就是我们这在写的设备,它占据了这个设备上所有可用的显示面积。当然,如果终端窗口在一个 X 窗口中,那么所谓的“屏幕”就是该终端窗口中所有可用的字符位置。

然而,至少总有一个 curses 视窗 stdscr ,它和物理屏幕一样大小。你可以额外创建一些小于屏幕的窗口。视窗可以互相重叠并且可以有很多个子窗口,但是每一个子窗口必须总是包含在父窗口中。

cruses 库维护了两个数据结构,此数据接够有点类似于一个终端屏幕的地图(用这两个数据结构来映射屏幕):
stdscr 与 curscr
这两个结构中,stdscr 相对来说比较重要一点,这个结构在 curses 函数输出时被更新。stdscr 数据结构是“标准屏幕”。它和 stdout 在很多方面都非常相似。在 curses 程序里,它是个默认的输出窗口。curscr 结构与之类似,但是它保持屏幕所显示的实际上看起来就像是发生在当前时刻。写到 stdscr 的输出不会立即出现在屏幕上,知道程序调用了 refresh() -- 这时候,curses 库会比较 stdscr 和 curscr (屏幕当前的样子)中的内容;然后,curses 会使用这两个结构的不同来更新屏幕。

一些 curses 程序需要了解 cruses 所维护的 stdscr 结构,所以几个函数需要它这样的一个参数。然而,实际上的 stdscr 结构与函数库的具体实现密切相关(implementation-dependent)并且永远不能直接去访问。curses 程序也永远无须用到 curscr 结构。

因此,在 curses 程序里处理输出字符的过程如下
[Plain Text] 纯文本查看 复制代码
使用 curses 函数去更新一个逻辑屏幕;
请求 curses 通过 refresh 来刷新物理屏幕。


这个两级跳的方法(tow-level approach)的好处,除了对程序设计方面更加容易外(in addition to 除了..之外),还有就是 curses 屏幕刷新得非常有效率。另外(in addtion),尽管这在一个控制台屏幕上看来不太重要,但如果你正在运行的程序是在一个低速网络链接上(低速串行口或调制解调器)时,这种差距看起来就很客观了。

一个 curses 程序会对逻辑屏幕输出函数做多次的调用,比如说可能会在整个屏幕上移动光标使之找到要写文本的位置以及划线与画框框。

在某个阶段(at some stage),用户需要查看全部的输出。这种情况一般发生在调用 refresh 期间,curses 将计算出物理屏幕对影逻辑屏幕的一个最佳方法。通过使用合适终端工具(性能指标)以及优化光标移动动作,curses 可以经常以更少的字符输出来更新屏幕,而不需要立即的去一次性的写一整屏的东西。

逻辑屏幕的布局是一个字符数组,数组下标由行号和列号组成,开始坐标(0,0)在屏幕的左上角。

所有的 curses 函数使用(y,x)为坐标表示,y表示行,x表示列。每一个位置不仅容纳这个屏幕位置上的字符而且还保存有他的属性。属性是否能被显示出来还依赖于物理终端的性能指标,但通常至少会有粗题和下划线这两种属性可用。

因为 curses 库需要创建和破坏同一个临时数据结构,所有 curses 程序在使用之前必须初始化库,以便用后还原。这里有两个函数可以做这件工作:initscrendwin

测试代码
#include <unistd.h>
#include <stdlib.h>
#include <curses.h>

int main()
{
        initscr();

        move(5, 15);
        printw("%s", "hello,wrold");
        refresh();
        sleep(3);  /*休息3秒以看到效果*/

        endwin();
        exit(EXIT_SUCCESS);
}

作者: beyes    时间: 2009-3-18 21:48
标题: 屏幕
所有 curses 程序必须以 initscr 开始 并且以 endwin 结束。
如果 initscr 函数执行成功,则返回一个指向 stdscr 结构的指针;假如失败,它就简单的打印出诊断信息并使程序退出。
endwin 函数成功时返回 OK, 错误时返回 ERR.你可以调用 endwin 函数离开 cruses, 然后可以通过调用 clearok(stdscr,1)与 refresh 来复原 curses 操作.这有效的使得 curses 忘记了自己的显示情况,而强制它执行了完整的一次原文重现操作.

测试代码:
#include <unistd.h>
#include <stdlib.h>
#include <curses.h>

int main()
{
        initscr();

        move(5, 15);
        printw("%s", "hello,wrold");
        refresh();
        sleep(3);

        endwin();
        sleep(3);

        clearok(stdscr, 1);
        refresh();
        sleep(3);
        endwin();
        exit(EXIT_SUCCESS);
}

作者: beyes    时间: 2009-3-19 01:44
标题: 向屏幕输出数据
提供了几个几本的函数来刷新屏幕
#include <curses.h>

int addch(const chtype char_to_add);
int addchstr(chtype *const string_to_add);
int printfw(char *format, ...);
int refresh(void);
int box(WINDOW *win_ptr, chtype vertical_char, chtype horizontal_char);
int insch(chtype char_to_insert);
int insertln(void);
int delch(void);
int beep(void);
int flash(void);

curses 有自己的字符类型--chtype,它比标准的 char 类型还有更多的二进制位。在标准 Linux 的 ncurses 版本里,chtype 实际上是一个 unsigned long 的 typedef 定义。

add... 函数增加字符或字符串到指定的当前位置。
printw 函数与 printf 一样对一个字符串进行格式化,并且把它打印到当前位置。
refresh 函数使物理屏幕被刷新,成功时返回OK,错误发生时返回ERR。
box 函数允许绕着窗口话一个框子。

在标准的 curses 里,你可能仅用到“正常”字符。而在扩展的 curses 中,你可以使用 ACS_VLINEACS_HLINE 这两个定义画出更好看的框子。基于此,你的终端需要支持画线字符(line-drawing characters)。通常的,这些在 xterm 窗口里会有更好的表现,而标准的控制台就差些。然而对这两者的支持差距却趋向日益变大,所以如果对移植性看得比较重要,那么还是要避免使用它们。

insch 函数的功能是插入一个字符,把现有的字符向右移,但在行尾进行这个操作造成的后果没有定义,具体的情况还是确定于你所使用的终端。
insertln 插入一个空行,把现有的行依次向下移动一行。
两个 delete 函数是类似(analogous)于两个 insert 函数。

如果要弄出声音,你可以调用 beep 函数。只有很少一部分的终端无法弄出声来,所以一些 curses 设置就让闪屏来代替 beep 调用。如果你工作在一个繁忙的办公室里,你就会非常乐见机器能够发出声来提醒你。正如大家猜到的那样,flash 函数能够引起屏幕闪屏,但如果无法产生闪屏效果的话,它就试图发出声来提醒你。
作者: beyes    时间: 2009-3-20 00:57
标题: 从屏幕中读取数据
可以从屏幕中读取数据,尽管这一功能并不经常用(因为对于写过的东西还是很容易跟踪的)。如果确实需要,可以使用以下函数:
#include <curses.h>
chtype inch(void);
int instr(char *string);
int innstr(char *string, int number_of_characters);
inch() 函数总是可用的,但 instr() 和 innstr() 却不是总能被支持。
inch() 函数返回一个当前屏幕光标位置处那个字符以及它的属性信息。注意,inch 并不是返回一个字符,而是一个 chtype 类型的数据。

作者: beyes    时间: 2009-3-20 02:06
标题: 清屏
有以下 4 种主要的方法来清除一个屏幕中的区域:
#include <curses.h>
int erase(void);
int clear(void);
int clrtobot(void);
int clrtoeol(void);

erase 函数在屏幕的每个位置都写上空格。clear 函数和 erase 函数类似,也是进行清屏动作,但还会通过调用函数 clearok 可以强制原文重现操作。clearok 强制进行清屏操作,但在下一次调用 refresh 时会进行原文重现操作。

clear 函数通常使用一个中断命令来擦除整个屏幕,而不是简单的试图擦除当前屏幕中的非空区域。这样,对于完整的屏幕擦除 clear 函数就成了一个相当可靠彻底的方法。在屏幕显示因为某些原因变得混乱或者被破坏了,那么在 clear 函数后面跟着 refresh 函数这样的组合操作,提供了一个非常有用的重新绘制屏幕手段。

clrtobot 函数从屏幕上光标所在的当前位置清除到屏幕末尾;
clrtoeol 从光标的当前位置清除到所在行的末尾。
作者: beyes    时间: 2009-3-20 10:20
标题: 移动光标
移动光标只需要一个函数,还有一个辅助函数用来控制刷新屏幕后 curses 应该把光标放在什么地方:
#include <curses.h>
int move(int new_y, int new_x);
int leaveok(WINDOW *window_ptr, bool leave_flag);

move 函数简单的把逻辑光标位置移动到指定地点。在绝大部分的 curses 版本里,有两个 extern 整形:LINESCOLUMS 包含着物理屏幕的尺寸,这可以确定 new_y 和 new_x 的最大值。
就其调用 move 本身而言,这并不会引起物理光标 位置的移动,它仅改变的是光标在屏幕上的逻辑位置,而紧接着的输出就会在这个位置上出现。如果希望调用 move 后马上就看到效果,那就在其后调用 refresh() 函数。

在一次屏幕刷新后,curses需要把物理光标放在某个位置上,控制这一位置的标志由 leaveok 函数设置。默认情况下,标志为 false,在刷新后,硬件光标将停留在和逻辑光标所在的地点。 如果flag 设置为 ture,则硬件光标随机的停留在屏幕可能的任何一个地方。一般来说,人们更喜欢用缺省选项。
作者: beyes    时间: 2009-3-20 11:54
标题: 字符属性
每一个 curses 字符都有一个确定的属性,属性控制字符在屏幕上的显示方式,当然前提是显示硬件要能支持所请求的显示属性。
预定义的属性有:
A_BLINK A_BOLDA_DIMA_REVERSE A_STANDOUTA_UNDERLINE
可以利用以下函数设置一个或多个这样的属性:
#include <curses.h>

int attron(chtype attribute);
int attroff(chtype attribute);
int attrset(chtype attribute);
int standout(void);
int standend(void);

attrset  函数设置 curses 的属性;
attron 和 attroff 在不影响打开或者关闭指定的属性;
standoutstandend 提供了一种更为通用的 “突出” 模式,在大多数终端上,它通常被映射为 ”反显“ 。
作者: beyes    时间: 2009-3-20 23:25
标题: 应用举例一
测试代码
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <curses.h>

int main()
{
        const char witch_one[] = "First Witch ";
        const char witch_two[] = "Second Witch ";
        const char *scan_ptr;

        initscr(); /*初始化*/

        move(5, 15);
        attron(A_BOLD);  /*打开字体加粗属性*/
        printw("%s", "Macbeth");
        attroff(A_BOLD); /*关闭字体加粗属性*/
        refresh();
        sleep(2);

        move(8, 15);
        attron(A_STANDOUT);  /*突出显示属性,打底色*/
        printw("%s", "Thunder and Lightning");
        attroff(A_STANDOUT);
        refresh();
        sleep(2);

        move(10, 10);
        printw("%s", "When shall we three meet again");
        move(11, 23);
        printw("%s", "In thunder, lightning, or in rain?");
        move(13, 10);
        printw("%s", "When the hurlyburly's done,");
        move(14,23);
        printw("%s", "When the battle's lost and won.");
        refresh();
        sleep(2);

        attron(A_DIM);
        scan_ptr = witch_one + strlen(witch_one) - 1;
        while(scan_ptr != witch_one) {
                move(10, 10);
                insch(*scan_ptr--);
        }
        insch(*scan_ptr);

        scan_ptr = witch_two + strlen(witch_two) - 1;
        while(scan_ptr != witch_two) {
                move(13, 10);
                insch(*scan_ptr--);
        }
        insch(*scan_ptr);
        attroff(A_DIM);
        refresh();
        sleep(2);

        move(LINES - 1, COLS - 1); /*把光标移动到窗口的右下角*/

        refresh();
        sleep(2);

        endwin();
        exit(EXIT_SUCCESS);
}
作者: beyes    时间: 2009-3-21 01:21
标题: 键盘.键盘模式
读键盘例程是由工作模式控制的。对工作模式进行设置的函数如下:
#include <curses.h>
int echo(void);
int noecho(void);
int cbreak(void);
int nocbreak(void);
int raw(void);
int noraw(void);

两个 echo 函数简单的打开互和关闭输入字符的回显。余下的 4 个函数控制着在终端中输入的字符是如何被 curses 程序所用的。
为了解释 cbreak,就必须理解默认的输入模式。当一个 curses 程序由调用 initscr 开始时,输入模式被设置为 ”预处理模式“( cooked mode )。这意味着所有的处理都以文本行为单位的。也就是说,输入仅在用户敲入回车键后才有效。键盘的特殊字符处于被激活状态,所以键入一个合适的字符序列或组合可以在程序中产生一个信号。如果正在运行的终端是来自另一个终端,流控制( flow control )也处于激活状态。

通过调用 cbreak, 一个程序可以设置输入模式为 “字符终止模式”( cbreak mode ),即在用户一键入字符后,就会立马为程序所用,而不是先缓冲起来并等到用户键入回车后才可用。与“预处理模式”一样,“字符终止模式”里键盘特殊键也处于激活状态;但对于一些简单的按键,如“退格键”(Backspace),会直接传递到程序中被处理,所以如果希望 Backspace 键有自己所期望的功能,那只有自己对其编程。

raw 调用关闭特殊字符处理功能,所以在键入特殊字符序列时并不会产生信号和流控制。
nocbreak 把输入模式设置回“预处理模式”,但特殊字符处理功能保持不变。
noraw 还原“预处理模式”和特殊字符处理功能。
作者: beyes    时间: 2009-3-21 10:59
标题: 键盘.输入
读键盘的操作较简单,有以下几个主要的函数:
#include <curses.h>
int getch(void);
int getstr(char *string);
int getnstr(char *string, int number_of_characters);
int scanw(char *format, ...);
[/table][table=100%,#f9f7ed]
这些函数与他们的 ”非curses“ 对等函数,如 getchar, gets, scanf 的行为非常相似。
注意:getstr() 函数并没有对返回的字符串的长度没做限制,所以必须非常小心的使用。
如果你的 curses 版本支持 getnstr,此函数可以限制读入字符的长度。建议使用此函数,而不是 getstr 。它们与 gets 和 fgets 的行为相类似。

作者: beyes    时间: 2009-3-21 14:13
标题: 测试 cbreak mode 示例
测试代码
#include <unistd.h>
#include <stdlib.h>
#include <curses.h>

void input();

int main()
{
        initscr();

        standout();
        move(9,10);
        printw("%s", "----Test nocbeak mode----");
        refresh();
        standend();
        input();
        sleep(3);

        standout();
        move(9,10);
        printw("----Test cbreak-mode----");
        refresh();
        standend();

        cbreak();
        input();
        sleep(3);

        nocbreak();
        endwin();
        exit(EXIT_SUCCESS);
}

void input()
{

        move(10,10);
        printw("%s", "Inpuut y/n: ");
        refresh();

        if(getch()== 'y' || getch()== 'n') {
                move(12, 10);
                printw("%s", "Input correct");
                refresh();
        } else {
                move(12, 10);
                printw("%s", "Input Wrong");
                refresh();
        }
}

程序说明
一开始在默认情况下,程序处于 non-cbreak-mode 模式,也就是 getch() 需要等待键入回车后才能收到输入的字符;
在输入回车后,程序判断了输入字符的对错,然后进入 cbreak-mode 模式,这时候就只要输入一个字符,无需再回车,程序马上做出判断。此后显示完后退出。
作者: beyes    时间: 2009-3-21 14:50
标题: 键盘输入(模式与检测)测试示例
键盘控制测试代码
#include <unistd.h>
#include <stdlib.h>
#include <curses.h>
#include <string.h>

#define PW_LEN 256
#define NAME_LEN 256

int main()
{
        char name[NAME_LEN];
        char password[PW_LEN];
        const char *real_password = "xyzzy";
        int i = 0;

        initscr();
        printw("%s", "Please login:");

        move(7, 10);
        printw("%s", "User name: ");
        getstr(name);

        move(8, 10);
        printw("%s", "Password: ");
        refresh();

        cbreak();
        noecho();
        /*内存初始化,password所指向的内存区域的sizeof()个字节空间都为'\\0'*/
        memset(password, '\\0', sizeof(password));
        while(i < 5) {
                password = getch();
                if(password == '\\n') break;
                move(8, 20 + i);
                addch('*');
                refresh();
                i++;
        }

        echo();
        nocbreak();

        move(11, 10);
        if(strncmp(real_password, password, strlen(real_password)) == 0)
                printw("%s", "Correct");
        else    printw("%s", "Wrong");
        printw("%s", " password");
        refresh();
        sleep(3);

        endwin();
        exit(EXIT_SUCCESS);
}

程序说明
使用 noecho 函数关闭了键盘输入的回显功能,用 cbreak 函数把输入情况设置为 ” 字符中止模式“ 之后,开辟了一小块内存做好接收口令的准备工作。构成口令的每一个字符一经敲入就立刻得到处理。随后,在屏幕的下一个位置显示一个 "*" 号,注意每次要对屏幕进行刷新。用户按下回车后,我们使用 strncmp 把刚输入的口令和保存在程序里的口令字进行比较。

作者: beyes    时间: 2009-3-21 19:09
标题: 窗口-窗口的结构
在物理屏幕上也可以同时显示多个不同尺寸的窗口。

stdscr 只是 WINDOW 结构的一个特例,就像 stdout 是一个文件流的特例一样。
WINDOW 结构一般在 curses.h 中有声明,对它的存取操作必须感规定的指令进行,程序永远不允许直接访问它,因为这个结构依赖于 curses 函数库的具体实现,它在不同版本的 curses 函数库里会有所变化。

你可以通过 newwindelwin 来创建和销毁窗口:
#include <curses.h>

WINDOW *newwin(int num_of_lines, int num_of_cols, int start_y, int start_x);
int delwin(WINDOW *window_to_delete);

newwin 函数创建一个新的窗口,窗口从屏幕位置(start_y, start_x)开始,尺寸由 num_of_lines 和 num_of_cols 来表示。此函数返回一个指向新窗口的指针,若创建失败则返回 null 。如果想让新窗口的右下角正好落在屏幕的右下角位置上,可以把它的行数和列数设置为 0 。窗口不论新旧大小,都不能超过当前屏幕。所以,若是新窗口的任何部分落在屏幕区域以外的地方 newwin 就会失败。由 newwin 创建的新窗口完全独立于任何现存的任何窗口。默认情况下,它会放在任何显存窗口的上面,遮盖(但不是改变)他们的内容。

delwin 函数删除由 newwin 创建的前一个窗口。因为调用 newwin 时可能已经分配过内存,所以当不再需要时应该将其删除掉。
注意:永远不要删除 curses 自己的窗口 -- stdscr 和 curscr 。
作者: beyes    时间: 2009-3-21 20:31
标题: 窗口--通用化函数(generalized functions)
在前面已经使用过 addch 和 printw 函数在屏幕上增添字符。与其它函数一起,它们都可以在前面添加个前缀。如:
前缀 w 表示对窗口进行操作;
前缀 mv 表示光标移动;
前缀 mvw 表示移动和对。

如果到 curses 头文件里查看更多的 curses 实现,就会发现在前面接触到的许多函数是一些简单的宏定义(#define 语句),宏定义的内容则是更通用化的函数。

如果添加了 w 前缀,就必须在它的参数表面前增加一个 WINDOW 指针;

如果添加了 mv 前缀,就需要添加两个参数 --屏幕纵坐标 x 和屏幕横坐标 y ,这两个坐标值设设定了执行这一操作的屏幕位置。y 和 x 是相对于窗口的坐标值而不是相对于屏幕的坐标值,(0, 0)代表的是窗口的左上角。

添加了 mvw 前缀,需要添加 3 个参数,一个是 WINDOW 指针,另外两个是 y 和 x 值。WINOW 指针永远出现在屏幕坐标值的前面,可有时候从函数前缀上看好像应该把 y 和 x 坐标放在最前面,遇到这种情况千万不要犯糊涂,否则容易出问题。

下面是 addch 和 printw 系列全体函数的框架定义:
#include <curses.h>

int addch(const chtype char);
int waddch(WINDOW *window_pointer, const chtype char);
int mvaddch(int y, int x, const chtype char);
int mvwaddch(WINDOW *window_pointer, int y, int x, const chtype char);
int printw(char *format, ...);
int wprintw(WINDOW *window_pointer, char *format, ...);
int mvprintw(int y, int x, char *format, ...);
int mvwprintw(WINDOW *window_pointer, int y, int x, char *format, ...);
其它一些函数,比如 inch, 也有加 “mv" 和 "mvw" 前缀的通用化变体。
作者: beyes    时间: 2009-3-21 20:59
标题: 窗口--移动和刷新窗口
以下函数可以移动和重新绘制窗口:
#include <curses.h>

int mvwin(WINDOW *window_to_move, int new_y, int new_x);
int wrefresh(WINDOW *window_ptr);
int wclear(WINDOW *window_ptr);
int werase(WINDOW *window_ptr);
int touchwin(WINDOW *window_ptr);
int scrollok(WINDOW *window_ptr, bool scroll_flag);
int scroll(WINDOW *window_ptr);

mvwin 函数在屏幕上移动窗口。因为不允许窗口任何部分超出屏幕区域。mvwin 如果试图移动窗口以致于它的任何部分落在了屏幕区域外头,那么忙这个函数就会失败。

wrefresh, wclear 和 werase 函数只是在前面所见过的函数的通用化版本。它们在加入了 WINDOW 指针后,它们就可以对指定的窗口进行操作,而不局限于 stdscr 。

touchwin 有些特殊。它通知 curses 库由参数指针所指向的窗口已经发生改变。这意味着,在下一次调用 wrefresh 时 curses 总将会重新绘制那个窗口。这个函数对于在屏幕上有几个函数堆叠的时需要安排哪一个窗口出来显示非常有用。

两个 scroll 函数控制窗口的卷屏情况。 scrollok 函数,当传递一个布尔值 true 时,其允许一个窗口卷屏。默认地,窗口是不会卷屏的。
scroll 函数就是简单的把窗口中的内容往上卷一行。一些 curses 实现也有 wsctl 函数,这可以一次卷几行内容,这个参数值还可能是负数。
作者: beyes    时间: 2009-3-22 01:35
标题: 窗口函数的应用示例(多窗口)
测试代码
#include <unistd.h>
#include <stdlib.h>
#include <curses.h>

int main()
{
        WINDOW *new_window_ptr;
        WINDOW *popup_window_ptr;
        int x_loop;
        int y_loop;
        char a_letter = 'a';

        initscr();

        move(5, 5);
        printw("%s", "Testing multiple windows");
        refresh();
        sleep(2);


        for(y_loop = 0; y_loop < LINES - 1; y_loop++) {
                for(x_loop = 0; x_loop < COLS - 1; x_loop++) {
                        mvwaddch(stdscr, y_loop, x_loop, a_letter);
                        a_letter++;
                        if(a_letter > 'z') a_letter = 'a';
                }
        }
        /*刷新屏幕*/
        refresh();
        sleep(3);

        new_window_ptr = newwin(10, 20, 5, 5);/*创建新窗口*/
        mvwprintw(new_window_ptr, 2, 2, "%s", "Hello World");
        mvwprintw(new_window_ptr, 5, 2, "%s",
                        "Notice how very long lines wrap inside the window");
        wrefresh(new_window_ptr);
        sleep(3);

        a_letter = '0';
        for(y_loop = 0; y_loop < LINES - 1; y_loop++) {
                for(x_loop = 0; x_loop < COLS - 1; x_loop++) {
                        mvwaddch(stdscr, y_loop, x_loop, a_letter);
                        a_letter++;
                        if(a_letter > '9')
                                a_letter = '0';
                }
        }

        refresh();
        sleep(3);

        /*这里刷新并不会改变什么,因为确实没对new_window做什么改变*/
        wrefresh(new_window_ptr);
        sleep(6);

        /*new_window会被刷新带到前面来,因为 touchwin 函数欺骗了 curses ,以致 curses 认为 new_window 已经发生改变*/
        touchwin(new_window_ptr);
        wrefresh(new_window_ptr);
        sleep(3);

        popup_window_ptr = newwin(10, 20, 8, 8);
        box(popup_window_ptr, '+', '~'); /*给窗口指定边框*/
        mvwprintw(popup_window_ptr, 5, 2, "%s", "Pop Up Window!");
        wrefresh(popup_window_ptr);
        sleep(3);

        touchwin(new_window_ptr); /*切换到 new_window */
        wrefresh(new_window_ptr);
        sleep(3);
        wclear(new_window_ptr);   /*对 new_window 进行清屏*/
        wrefresh(new_window_ptr);
        sleep(3);
        delwin(new_window_ptr);  /*不再需要 new_window,释放掉*/

        touchwin(popup_window_ptr); /*切换到 popup_window */
        wrefresh(popup_window_ptr);
        sleep(3);

        delwin(popup_window_ptr); /*释放掉 popup */
        touchwin(stdscr);
        refresh();
        sleep(3);
        endwin();
        exit(EXIT_SUCCESS);
}

说明
curses 在刷新窗口的时候并不考虑窗口在屏幕上出现的先后顺序,所谓窗口之间的上下继承关系也无从谈起。为了确保 curses 能够以正确的顺序绘制窗口,就必须以正确的顺序对他们进行刷新。解决这个问题的办法之一是把全体窗口的指针都保存到一个数组或列表里,通过这个数组来安排它们显示到屏幕上去的正确顺序。
作者: beyes    时间: 2009-3-22 01:49
标题: 窗口--优化窗口刷新
在上帖的例子中可见,刷新多窗口需要一些技巧,但并不至于无章可循。然而在一个慢速网络上,当刷新终端时,可能会出现较严重的潜在问题。幸运的是,这个问题在现在来说已经很少存在了。

我们的目标是,要尽可能的减少往屏幕上绘制的字符个数,因为在慢速的链路上对屏幕的绘制可能慢得令人无法忍受。curses 提供了一个非常手段 -- 用以下的两个函数:
#include <curses.h>

int wnoutrefresh(WINDOW *window_ptr);
int doupdate(void);

wnoutrefresh 函数决定需要往屏幕上送哪个字符,但实际上并没有发送它们。真正把字符发送到中断的工作由 doupdate 来完成。
如果你简单的调用 wnoutrefresh ,然后紧跟着调用 doupdate,其效果与调用 wrefresh 一样。
如果想重新绘制一叠窗口,可以对每个窗口都调用 wnoutrefresh (当然要有正确的顺序),然后在最后一个 wnoutrefresh 后统一调用 doupdate 。这样就可以让 curses 对轮流的对每一个窗口执行更新计算工作然后输出到屏幕上来。这种做法可以最大限度地减少需要 curses 发送的字符个数。
作者: beyes    时间: 2009-3-22 15:06
标题: 窗口 -- 子窗口
多窗口的一种特殊情况是 "子窗口"。可以使用以下函数创建与销毁子窗口:
#include <curses.h>

WINDOW *subwin(WINDOW *parent, int num_of_lines, int num_of_cols, int start_y, int start_x);
int delwin(WINDOW *window_to_delete);

subwin 函数有着和 newwin  几乎完全一样的参数,删除子窗口和删除其它窗口一样,也是用 delwin 函数。

和对待新的窗口一样,我们可以使用 delwin 来删除子窗口,而且还可以使用带有 mvw 前缀的相关函数去写子窗口。确实,在大部分时候,子窗口的行为特征和新窗口非常相像,但是有一个非常重要的区别:
子窗口没有独立的屏幕字符存储区,不保存自己屏幕字符的集合;它和父窗口共享着同一块字符存储区,这个区域及其大小是创建子窗口时由父窗口指定的。这就意味着,在子窗口中的任何改变都将映射到它的父窗口中去;当子窗口被删除时,屏幕不会改变什么。

这乍一看(at first sight),子窗口看起来似乎没什么用。那为什么不直接就修改父窗口里的内容呢?
子窗口的主要用途是卷动其它窗口中里的部分内容。在编写 curses 程序时,需要卷动屏幕中一小块子区域那太稀松平常不过了。通过创建一个子窗口然后对子窗口进行卷屏,你可以得到你想要的结果。

使用子窗口的一个限制是:应用程序必须在刷新屏幕之前必须在父窗口里调用 touchwin 函数。
作者: beyes    时间: 2009-3-23 12:25
标题: 子窗口应用示例
测试代码
#include <unistd.h>
#include <stdlib.h>
#include <curses.h>

int main()
{
        WINDOW *sub_window_ptr;
        int x_loop;
        int y_loop;
        int counter;
        char a_letter = '1';

        initscr();
        /*以 0-9 来填充屏幕*/
        for(y_loop = 0; y_loop < LINES - 1; y_loop++) {
                for( x_loop = 0; x_loop < COLS - 1; x_loop++) {
                        mvwaddch(stdscr, y_loop, x_loop, a_letter);
                        a_letter++;
                        if(a_letter > '9') a_letter = '1';
                }
        }
        /*创建子窗口,stdscr 是父窗口指针*/
        sub_window_ptr = subwin(stdscr, 10, 20, 10, 10);
        scrollok(sub_window_ptr, 1); /*滚屏设定*/

        touchwin(stdscr);
        refresh();
        sleep(3);

        werase(sub_window_ptr);
        mvwprintw(sub_window_ptr, 2, 0, "%s\\n", "window will scroll");
        wrefresh(sub_window_ptr);
        sleep(3);

        for(counter = 1; counter < 10; counter++) {
                wprintw(sub_window_ptr, "%s\\n", "text is scrolling");
                wrefresh(sub_window_ptr);
                sleep(5);
        }

        delwin(sub_window_ptr);

        touchwin(stdscr);
        refresh();
        sleep(3);

        endwin();
        exit(EXIT_SUCCESS);
}

说明
在子窗口里的内容在 for 循环的控制下会不断滚屏输出。
注意,在子窗口删除及 window(stdscr) 被刷新后,之前在子窗口中输出的内容仍保留在屏幕中,这是因为子窗口中刷新出来的数据正是来自与父窗口 stdscr 。
作者: beyes    时间: 2009-3-23 12:28
标题: 键盘上的数字小键盘(keypad)
在前面已经看到 curses 提供处理键盘的一些工具。许多按键至少都会有光标移动键和功能键。许多键盘还有数字小键盘以及其它按键,如 Insert Home 键。

在绝大部分的终端上,解码这些按键都算是一个难题。因为一般情况下按下这些按键后,它们会发送一个以转义字符打头的字符串。应用程序不仅要设法分辨出 ”单独按下 Escap 按键“ 和 ”按下功能按键爱你发送出来的一连串字符“ 之间的差别,还要对付因终端型号的不同而产生的 “同一逻辑按键使用不同的转义序列” 的情况,这两个问题交织在一起,错综复杂。

幸运的是,curses 为管理功能键提供了一个精巧的工具。对于每一个终端来说,它们的功能键发送出来的转义序列都被保存,通常是在一个 terminfo 结构里,并且所包含的 curses.h 头文件有一套前缀为  KEY_  的逻辑按键 #defines 定义。

当 curses 启动时,转义序列和逻辑按键之间的转换被禁止,这需要 keypad 函数重新开启。如果成功就返回 OK,如果失败就返回 ERR 。

#include <cuurses.h>

int keypad(WINDOW *window_ptr, bool keypad_on);

一旦通过调用 keypad ,并设置 keypad_on 参数为 ture 时, keypad mode 被使能。接着,curses 接管(take over)按键的转义序列的处理工作。这样,读取键盘不但返回被按下的按键,也返回与逻辑按键对应的 KEY_defines 。

在使用 keypad mode 时有 3 个小小的限制:
      第一个问题是识别 escape 转义序列需要一定的时间,而许多网络协议要么把字符打成数据包(这会导致 escpae 转义序列的识别不正确),要么会从某个地方开始分断它们(这将导致功能键的转义序列被识别成 escape 字符和其它彼此没有联系的字符)。在 WAN 网络或其它繁忙的链路上这一情况将更为剧烈。唯一解决的办法是尝试对终端进行编程使之对你所希望用的每一个功能键发送单独的,独一无二的字符,然而这会限制控制字符的数量。
      为了让 curses 能够区分 “单独按下 Escape按键” 和一个以 Escape 字符打头的键盘转义序列,它就必须等待很短的一小段时间。在 keypad 模式被激活后,处理 Escape 按键所造成的非常微小的延迟也能被注意到。
      curses 不能处理 “非独一无二” 的转义序列。如果你的终端有两个都发送同一个转义序列的不同按键,curses 将对此不做处理,因为它不能告诉该返回哪一个逻辑按键。

作者: beyes    时间: 2009-3-24 11:03
标题: keypad 模式测试
测试代码

#include <unistd.h>
#include <stdlib.h>
#include <curses.h>

#define LOCAL_ESCAPE_KEY 27

int main()
{
        int key;

        initscr();
        crmode();  /*切换到 CBREAK 模式,即字符一输入就被处理*/
        keypad(stdscr, TRUE); /*激活 keypad 模式*/

        noecho();  /*输入不回显*/
        clear();
        mvprintw(5, 5, "Key pad demostration. Press 'q' to quit");
        move(7,5);
        refresh();
        key = getch();  /*检测按键*/

        while(key != ERR && key != 'q') {
                move(7,5);
                clrtoeol();  /*如果没错误则清(7,5)起到其所在的行末*/

                if((key >= 'A' && key <= 'Z') ||
                   (key >= 'a' && key <= 'z')) {
                        printw("Key was %c", (char)key);
                }
                else {
                        switch(key) {
                                case LOCAL_ESCAPE_KEY: printw("%s", "Escape key"); break;
                                case KEY_END: printw("%s", "END key"); break;
                                case KEY_BEG: printw("%s", "BEGINNING key"); break;
                                case KEY_RIGHT: printw("%s", "RIGHT key"); break;
                                case KEY_LEFT: printw("%s", "LEFT key"); break;
                                case KEY_UP: printw("%s", "UP key"); break;
                                case KEY_DOWN: printw("%s", "DOWN key"); break;
                                default: printw("Unmatched - %d", key); break;
                        }/* switch */
                } /* else */

                refresh();
                key = getch();

        }/* while */

        endwin();
        exit(EXIT_SUCCESS);
}

程序说明
在上面程序中,必须关闭回显功能,否则光标在按键按下时会发生移动
作者: beyes    时间: 2009-3-25 01:21
标题: 使用颜色
刚开始时,非常少的 ”哑“ 终端是支持颜色显示功能的,所以早期版本的 curses 对这方面也不做支持。而在 ncurses 及现在大部分的 curses 实现中都支持颜色显示。不幸的是,curses 的“哑屏幕” 出身已经影响到了 API, curses 在使用颜色方面也有许多严格的限制,这反应了早期的终端在颜色显示方面相当差的兼容能力。

屏幕上的每一个字符都 可以用多种颜色中的一种,字符所在的背景色也是一样可以选择。例如,你可以用绿色来 写文本,而文本所在的背景色可选为红色。

curses 所支持的颜色显示有一点不寻常,在这里,一个字符的颜色并没有定义得与其背景色无关( independently of 与...无关;不取决与...);而是,你必须定义一个字符的前景色和背景色作为一个 “颜色对” (color pair)。

在确定你可以使用 curses 的颜色功能之前,你必须检查当前的中断是否支持颜色显示,然后对 curses 的颜色例程进行初始化。对于此,可以使用两个函数:has_colors 和 start_color :
#include <curses.h>

bool has_colors(void);
int start_color(void);

如果终端支持颜色显示,那么 has_colors 函数返回 ture 。接着,就可以调用 start_color 了,如果颜色功能初始化成功,函数返回 OK 。一旦 start_color  一经调用并且颜色显示功能被初始化成功,变量 COLOR_PAIRS 就被设置为该终端所能支持的 “颜色对” 的最大值。一般情况下限制为 64 个 “颜色对”。变量 COLORS 定义了可选择颜色种类的最大值,此值一般为 8 。在内部,0-63一般作为每种 "颜色对“ 的特定 ID 。

在你可以使用颜色作为属性之前,你必须对你所想用的 ”颜色对“ 进行初始化——用 init_pair 函数来完成此工作。访问颜色属性使用 COLOR_PAIR 函数:
#include <curses.h>

int init_pair(short pair_number, short foreground, short background);
int COLOR_PAIR(int pair_number);
int pair_content(short pair_number, short *foreground, short *background);

curses.h 通常都定义了一些基本的颜色,其命名以 COLOR_ 开始。
pair_content() 函数允许对之前定义的 ”颜色对“ 信息进行检索(retrieve)。

比如,要定义绿色背景上的红色内容为一号 ”颜色对“,那么如下定义:
init_pair(1, COLOR_RED, COLOR_GREEN);

经过上面的定义后,这个颜色组合可以用来作为颜色的属性了,注意一下 COLOR_PAIR 的用法:
wattron(window_ptr, COLOR_PAIR(1));

由于 COLOR_PAIR 已经被定义为一个属性,接下来就可以使用其他的属性与之组合。在 PC 上,通过组合 COLOR_PAIR 属性和附加属性 A_BOLD 属性以达到在屏幕上显示出更浓的颜色效果,组合两个属性的方法为使用 OR 位运算符,如下所示:
wattron(window_ptr, COLOR_PAIR(1)|A_BOLD);

作者: beyes    时间: 2009-3-26 16:55
标题: 终端颜色显示测试
测试代码

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <curses.h>
int main()
{
int i;
initscr();
if (!has_colors()) { /*终端是否支持颜色显示*/
endwin();
fprintf(stderr, "Error - no color support on this terminal\\n");
exit(1);
}
if (start_color() != OK) { /*初始化颜色功能是否成功*/
endwin();
fprintf(stderr, "Error - could not initialize colors\\n");
exit(2);
}
clear();
mvprintw(5, 5, "There are %d COLORS, and %d COLOR_PAIRS available",
COLORS, COLOR_PAIRS); /* 一般COLORS为8种; COLOR_PAIRS 为 64 */
refresh();

init_pair(1, COLOR_RED, COLOR_BLACK);
init_pair(2, COLOR_RED, COLOR_GREEN);
init_pair(3, COLOR_GREEN, COLOR_RED);
init_pair(4, COLOR_YELLOW, COLOR_BLUE);
init_pair(5, COLOR_BLACK, COLOR_WHITE);
init_pair(6, COLOR_MAGENTA, COLOR_BLUE);
init_pair(7, COLOR_CYAN, COLOR_WHITE);
for (i = 1; i <= 7; i++) {
attroff(A_BOLD);
attrset(COLOR_PAIR(i)); /* 也可以用 attron 来代替*/
mvprintw(5 + i, 5, "Color pair %d", i);
attrset(COLOR_PAIR(i) | A_BOLD);
mvprintw(5 + i, 25, "Bold color pair %d", i);
refresh();
sleep(1);
}
endwin();
exit(EXIT_SUCCESS);
}
程序说明
在检查了屏幕是否支持彩色显示后,程序对颜色处理进行了初始化以及定义了一些 ”颜色对“ 。
attrset() 函数和 attron() 函数在此可以互换使用。
在颜色属性定义中,COLOR_PAIR() 的返回值亦是其中一种。
在用 init_pair() 函数进行初始化后,在内部就存在了一个 "颜色对" 的特定 ID ,当使用 COLOR_PAIR() 函数时,就会查找到这个 ID ,于是便知道了之前初始化的颜色属性。那么 COLOR_PAIR() 函数的返回值就可以传递给 attron(), attroff(), attrset() 等函数所用。
作者: beyes    时间: 2009-3-26 21:59
标题: 重定义颜色
作为早期的 ”哑终端“ 的遗留问题---在任何一次只能显示非常少的颜色,但允许对颜色进行配置,curses 允许使用  init_color() 函数进行颜色重定义:
include <curses.h>

ini init_color(short color_number, short red, short green, short blue);
这个函数把一个可用色彩的深度(0 ~ COLORS 之间)细调为一个新的值,新值的范围从 0 - 1000 。
作者: beyes    时间: 2009-3-27 00:34
标题: 逻辑屏幕和显示平面
在编写比较高级的 curses 程序时,我们可以先创建一个逻辑屏幕,然后再把它的全部或部分内容输出到物理屏幕上,这个办法简便易行,效果也不错。如果还能有一个尺寸大于物理屏幕的逻辑屏幕,再根据需要一次只显示逻辑屏幕的某个部分,其效果往往会更好。

在前面讲过,所有的窗口不能大于物理屏幕。但实际上,curses 允许逻辑屏幕大于正常窗口,并且专门为尺寸大于正常窗口的逻辑屏幕上的信息处理准备了特殊的数据结构 -- 显示平面(pad)。

创建显示平面和创建正常窗口的原理非常相似:
include <curses.h>

WINDOW *newpad(int number_of_lines, int number_of_columns);

注意,和 newwin() 一样,newpad() 函数的返回值是一个指向 WINDOW 结构的指针。Pad 的删除同样使用 delwin() 函数。

对”显示平面“有其独立的函数。显示平面不受屏幕坐标位置的限制,所以在刷新时必须指定希望把显示平面的哪个区间输出到屏幕上,还必须设定它在屏幕上占据的坐标位置。以下是对显示平面进行刷新的 prefresh 函数的定义:
include <curses.h>

int prefresh(WINDOW *pad_ptr, int pad_row, int pad_column,
             int screen_row_min, int screen_col_min,
             int screen_row_max, int screen_col_max);

这个函数的作用是把显示平面从 (pad_row, pad_column) 开始的区间写到屏幕上的一块区域中,这块区域被定义为坐标 (screen_row_min, screen_col_min) 和 (screen_row_max, screen_col_max) 之间的那一部分。

一个附加的函数是 pnoutrefresh() ,它和 wnoutrefresh() 的作用类似,功能也是使得屏幕的更新变得更为有效。
作者: beyes    时间: 2009-3-27 02:18
标题: pad 的应用举例
测试代码

#include <unistd.h>
#include <stdlib.h>
#include <curses.h>

int main()
{
        WINDOW *pad_ptr;
        int x, y;
        int pad_lines;
        int pad_cols;
        char disp_char;

        initscr();
        /*显示平面比终端的长宽都大 50 个字符宽度*/
        pad_lines = LINES + 50;
        pad_cols = COLS + 50;
        pad_ptr = newpad(pad_lines, pad_cols);

        disp_char = 'a';

        for(x = 0; x < pad_lines; x++) {
                for(y = 0; y < pad_cols; y++) {
                        mvwaddch(pad_ptr, x, y, disp_char);
                        if(disp_char == 'z') disp_char = 'a';
                        else disp_char++;
                }
        }
        mvprintw(0,0, "The MAX LINES On This Terminal Is %d", LINES);
        mvprintw(1,0, "The MAX COLS On This Terminal Is %d", COLS);
        refresh();

        prefresh(pad_ptr, 0, 0, 2, 2, 9, 9);
        sleep(2);
        prefresh(pad_ptr, LINES + 5, COLS + 7, 5, 5, 21, 19);
        sleep(2);
        delwin(pad_ptr);
        endwin();
        exit(EXIT_SUCCESS);
}

说明
上面我的终端可以容纳24行80列的字符。
prefresh(pad_ptr, 0, 0, 2, 2, 9, 9)
可以看到,参数 (0,0) 表示要显示的内容从 (0,0) 坐标起,显示内容的多少限定在(2-9) 行,(2-9) 列这么一块区域里。
prefresh(pad_ptr, LINES + 5, COLS + 7, 5, 5, 21, 19);
上一条语句效果一样,由上可见,{ (LINES+5),(COLS+7) }   表示的字符为 j 。

程序运行如下图所示:





欢迎光临 曲径通幽论坛 (http://www.groad.net/bbs/) Powered by Discuz! X3.2