第3 章 curses 库窗口

3 .1 curses 窗口简介

3.1.1窗口概念

窗口是 curses 库中最重要的一个组件,它实际上是屏幕上的一块矩形区域,在上面我们可以进行各种输出以及操作。 curses 库中提供了大量的函数以允许我们创建和操作自己的窗口,而不仅仅是只能使用标准窗口 stdscr 。对自定义窗口进行操作的函数一般与对标准窗口进行操作的函数是相同的,除非它们需要特别的参数,这个参数通常为一个指向自定义窗口的指针。 curses 也允许创建一种称之为基垫的窗口,英文名称为 pad 。基垫的各个方面与窗口并无二异,只是它的大小和位置不再局限于终端屏幕的可视部分。它可以比标准屏幕大,位置也可以位于标准屏幕之外而我们看不到。因为窗口函数只是 curses 库函数的一部分,因此我们可以用同样的方法来编译使用窗口的程序。我们在这一章只介绍最常用的一些函数,必要时我们会给出几个综合的例子,从而加深对窗口的理解和应用能力。

窗口可以位于标准屏幕的任何位置,它们之间可以相互重迭包括与标准屏幕。窗口同时可以包含与它们相关联的子窗口。任何在父窗口和子窗口重迭区域的变化都会同时影响到它们中的任何一个。与子窗口类似,基垫也同样有子基垫,英文名称为 subpad 。子基垫是与父基垫相关联并且位于父基垫之内的窗口。刷新基垫和子基垫与刷新窗口和子窗口有些细微的差异。这种具体的差异我们在后面会慢慢介绍。

图3.1 具体的演示了窗口、子窗口、基垫、子基垫之间的相互关系。

curses c curses窗口_curses c

图3. 1

3.1.2窗口数据结构

在使用窗口之前我们先看一下窗口的数据结构定义,通常它在头文件curses.h 中:

typedef struct _win_st  WINDOW;
struct _win_st
{
short       _cury , ;       /*  光标当前位置  */
short              _maxy , ;   /*  最大的位置值  */
short             _begy , ;   /* (0 , 0)  窗口相对于标准窗口的位置  */
char   ;
short   ;          /*  偏移量  */
bool        _clear ,        /* clearok()  信息  */
        _leave ,        /* leaveok()  信息  */
        _immed ,        /* window in immediate mode */
;        /* auto syncup of parent */
WINDOW   ;   /*  当前窗口的 ”pad” 区域 */
#ifdef   _VR3_COMPAT_CODE
_ochtype   **         _y16;              /* MUST stay at this offset in WINDOW */
#endif
short   ;            /* first change in line */
short   ;             /* last change in line */
short           _tmarg , ;   /* scrolling region bounds */
             /* MUST stay at this offset in WINDOW */
unsigned   _scroll   : 1;           /* scrollok() info */
unsigned   _use_idl   ;
unsigned   _use_keypad   : 1;
unsigned   _notimeout   : 1;
unsigned   _use_idc   ;
chtype   ;   /* current window attributes */
chtype   ;   /* background ,  normally blank */
int   ;   /* delay period on wgetch */
                     /* 0:  for nodelay */
                /* <0: for infinite delay */
                /* >0: delay time in millisec */
short   ;             /* number of descendants */
short          _parx , ;        /* coords relative to parent (0 , 0) */
WINDOW       *_parent;             /* 如果窗口为子窗口则这指向父窗口  */
chtype   ;             /* lines of data */
short   ;      /* number of bytes to come */
short   ;       /* index to hold coming char */
char   ;  /* array to hold partial m-width char */
bool   ;      /* TRUE for inserting ,  */
  /* FALSE for adding */
};

上面的结构基本包含了一个窗口所需要的各种资料,在以后的创建和使用窗口的过程中我们返回以及需要的都是这个的结构。下面是几个最经常用到的成员:

_cury ,  _curx :当前窗口中光标的位置

_begy ,  _begx :当前窗口的左上角相对于标准屏幕的位置

_parent :当前窗口的父窗口

_parx ,  _pary :子窗口的左上角相对于父窗口的位置

_attrs :当前窗口的属性

_bkgd :当前窗口的背景字符

 

3 .2 窗口操作

自定义窗口函数跟与标准屏幕交互的函数基本是相同的,除非它需要特殊的参数来指明函数作用的窗口不是标准屏幕。比如标准屏幕刷新用 refresh()  函数,而窗口刷新却是通过 wrefresh() 进行,wrefresh() 有一个指针用来指向需要刷新的窗口。当它调用的时候,屏幕上只有代表窗口的部分才会进行刷新,标准屏幕的其余部分并不会刷新。下面我们对窗口的函数进行详细的探讨,同时在一些函数说明后面会附上完整的使用代码,这些代码都是在 SCO UNIX 上测试过的,在别的平台上只要稍做改动甚至不需要改动就可以直接编译。

 

3 .2.1 创建和删除窗口

函数 newwin() 用来创建窗口并且返回 WINDOW 指针。这个指针在各个窗口处理函数中作为参数传递过去。newwin() 语法如表 3.1 所示。

表3 .1 newwin ()函数概述

头文件

curses.h

概述

WINDOW *newwin(lines , cols , begin_y , begin_x) ;

int lines , cols , begin_y , begin_x ;

返回

成功

失败

*WINDOW

(*WINDOW)NULL

newwin() 的参数含义如下:

■  line 和 cols 给出了需要创建的窗口所占的总行数和总列数,它们都是整数值。

■  begin_y  和 begin_x 给出了窗口的左上角所在的行数和列数,它们也是整数值。需要注意的是begin_y 和 begin_x 的顺序,它们与通常的 (x , y) 顺序正好相反。

 

newwin() 返回的是一个 WINDOW 类型的指针。如果创建失败,函数返回 (WINDOW*)NULL 。一旦获取了 WINDOW 结构,我们就可以访问这个结构中的所有的成员。例如,下面的程序片断中我们在标准屏幕上创建了一个窗口 midscreen :

WINDOW* midscreen;

midscreen=newwin(5 , 10 , 9 , 53);

创建的窗口有五行十列,在标准屏幕上它的左上角的位置是 (9 , 35) 。

 

如果 lines 或者 cols 为 0 的话,函数将自动创建一个行数和列数分别为 LINES 和  COLS 的窗口,这个窗口的范围包括整个终端屏幕。例如下面的程序片断创建一个行数为 LINES ,列数为 COLS 的窗口,它的左上角为 (0 , 0) 。

newwin(0 , 0 , 0 , 0)

必须注意的是创建窗口的大小不能超出实际屏幕的大小,窗口的行和列数的最大值也就是 LINES 和 COLS ,如果超过了,则返回失败。

对于那些不再使用的窗口我们有必要及时清除它。窗口清除包括两方面的含义:清除窗口画面和清除窗口内存。对于清除窗口画面,我们可以使用函数werase() 和 wclear() 实现,它们的作用和第二章的 erase() 、 clear() 非常类似。如果窗口局部清除,则可以使用 wclrtobot() 和 wclrtoeol() ,它们的用法和 clrtobot() 、 clrtoeol() 类似。

事实上,不管werase() 还是 wclear() ,它们并不能清除菜单所占的内存空间,因此有可能无意中会造成内存泄漏,因此我们还必须调用函数 delwin() 释放内存。函数语法如表 3.2 所示:

表3 . 2   delwin()函数概述

头文件

curses.h

概述

int delwin(win);

WINDOW* win;

有必要指出的是,在主窗口删除之前我们必须先删除与它关联的所有子窗口。

3 .2.2 创建子窗口

一个窗口通常都是有一些子窗口与之关联的。 subwin() 函数创建一个子窗口,同时返回一个指向子窗口的指针。一个子窗口实际上是与父窗口共享所有的或者部分的字符空间的窗口。从 WINDOW 结构中可以看出,结构中的 _parent 就是指向父窗口的指针,通过户取这个 _parent 指针,我们可以得到子窗口的父窗口。

表3 . 3  subwin ()函数概述

头文件

curses.h

概述

WINDOW *subwin(win , lines , cols , begin_y , beign_x)

WINDOW *win;

int lines , cols , begin_y , begin_x;

subwin 的参数的意义如下:

■  win 是指向当前创建的子窗口的父窗口的指针。一旦函数调用成功,子窗口结构中的 _parent 指针将指向父窗口 win 。

■  lines 和 cols 给出了创建的子窗口的总行数和总列数。

■  beign_y 和 begin_x 是子窗口的左上角在标准屏幕中的相对位置。注意不是在父窗口中的相对位置。

subwin() 同样返回一个 WINDOW 类型的指针,如果执行失败,它将同样返回 (WINDOW*)NULL 。由于子窗口位于复窗口之中,因此对其中任何一个的改变都会同时影响到它们。它们共享部分字符空间。

subwin() 函数的最通常的用法是将已存在的窗口切分成不同的区域。例如在下面的程序片断中, subwin 在标准屏幕的下面部分创建了一个称之为 cmdmenu 的窗口。

WINDOW* cmdmenu;

cmdmenu = subwin(stdscr  ,  5  ,  80  , 19  , ;

在这个例子中, cmdmenu 与标准屏幕共享部分字符空间,因此改变 cmdmenu 同样影响标准屏幕。

相对于subwin() ,我们在程序中更经常使用的则是 derwin() 函数。它的语法如表 3.4 所示。

表3 . 4 der win ()函数概述

头文件

curses.h

概述

WINDOW * der win(win , lines , cols , begin_y , beign_x)

WINDOW *win;

int lines , cols , begin_y , begin_x;

derwin()与 subwin() 唯一的不同则是参数 begin_y 和 begin_x 。在 derwin() 中, begin_y 和 begin_x 都是相对于父窗口而言。因此从这个意义上讲,它可能比 subwin() 更方便使用。

3 .2.3 在窗口中进行输入和输出

从窗口中接受输入和进行输出的函数与在标准屏幕中进行输入和输出的函数是很相似的,它们的命名都遵循curses 命名规范。两个之间唯一的不同之处在于我们必须增加窗口的指针作为额外的参数。这些函数的名称通常由标准屏幕的操作函数在头部加上字符“ w ”组合而成,同时将窗口的指针作为第一个参数传递。例如,标准屏幕上的字符输出函数  addch( ‘ c ’ ) 在自定义窗口函数中就变成 waddch(win ,‘ c ’ ) ,完成的功能就是在窗口 win 的当前位置上输出字符‘ c ’。下面就是一些常用的函数列表,我们不再重复描述它们的作用。

■ , y , x) 将窗口移动到 (y, x) 处

■ , ch) 在窗口的当前位置增加字符‘ ch ’

■ , y , x , ch) 在窗口中将光标移动到 (y, x) ,同时输出‘ ch ’

■ , str) 在窗口中增加字符串

■ , y , x , str) 将窗口光标移动到 (y, x) 同时输出 str 字符串

■ , fmt [  , arg…]) 在窗口中格式化输出

■ , fmt [  , arg…]) 在窗口中将光标移动后格式化输出

■  wgetch(win) 在窗口中获取输入字符

■ , y , x) 在窗口中移动到指定位置后获取输入字符

■ , str) 在窗口中获取输入字符串

■ , y , x , str) 在窗口中移动后获取输入字符串

■ , fmt [  , arg…]) 窗口中格式化输入

■ , y , x , fmt [ , arg…]) 窗口中光标移动后格式化输入

■ , c) 窗口中插入字符

■ , y , x , c) 窗口中光标移动后插入字符

■ , y , x) 窗口中插入一行

■  wdelch(win) 窗口中删除一个字符

■ , y , x) 窗口中移动后删除一个字符

■  wdeleteln(win) 窗口中删除一行

■  wclear(win) 和 werase(win) 清除窗口

■  wclrtoeol(win) 和 wclrtobot(win) 将当前位置到窗口底端的所有字符清除

■  wstandout(win) 和 wstandend(win) 将给定窗口设置为高亮显示和正常显示

■ , attr) , wattroff(win , attr) 和 wattron(win, attr) 设置窗口显示属性

 

由于基垫也是窗口,因此上面的函数同样可以在基垫中使用,但是 wrefresh() 和 wnoutrefresh() 两个函数除外,因为基垫由于包含标准屏幕的不可见部分,因此它的刷新与窗口有些差异, wnoutrefresh() 在后面的部分进行讲解。在基垫中我们使用 prefresh() 和 pnoutrefresh() 实现同样的功能。

 

3 .2.4 窗口坐标

一旦窗口创建之后,窗口的相关属性将被赋到 WINDOW 结构中。因此我们只要能够访问 WINDOW 中的结构就可以获取窗口的有关坐标属性。表3.5 所示的几个函数可以让我们获取创建的窗口的一些有用的属性。

表3 . 5   窗口坐标函数概述

头文件

curses.h

概述

void getbegyx(WINDOW *win, int y , int x) ;

void getmaxyx(WINDOW *win, int y , int x) ;

void getparyx(WINDOW *win, int y , int x) ;

void getyx(WINDOW *win, int y , int x) ;

getbegyx()函数用来获取指定窗口的起始坐标。坐标值保存在变量 y , x 中。

getmaxyx()函数用来获取窗口的最大坐标。

getparyx()函数用来获取子窗口相对于父窗口的起始坐标。

getyx()函数获取当前窗口中的光标位置。

需要注意的是坐标y , x 的前面并没有‘ & ’地址符号。

3 .2.5 窗口拷贝

curses中有的时候需要将某个窗口的内容拷贝到另外的一个窗口中,我们称之为窗口拷贝。为此 curses 中提供了三个函数: overlay() 、 overwrite() 、 copywin() 。它们的用法如表 3.6 所示。

表3 . 6   窗口拷贝函数概述

头文件

curses.h

概述

int overlay( WINDOW *srcwin , WINDOW  * d st win) ;

 

int overwrite(WINDOW *srcwin, WINDOW *dstwin) ;

 

int copywin(scrwin, dstwin , sminrowl , smincol , dminrow , dmincol , dmaxrow , dmaxcol , overlay) ;

WINDOW *scrwin, *dstwin ;

int sminrow, smincol , dminrow , dmincol , dmaxrow , dmaxcol ;

int overlay;

返回

成功

失败

OK

ERR

overlay() 函数将一个窗口中的内容拷贝到另外的一个窗口中,窗口所占用的内存空间并不进行拷贝。拷贝过程中字符与窗口的相对位置保持不变,即如果一个字符在原始窗口中的位置为( 10, 10 ),它在目标窗口中的位置也是 (10, 10) 。

overlay ()的参数的含义如下:

■   src win 是指向被拷贝的窗口的指针,即源窗口。

■ dst win 是指向接受拷贝的窗口的指针,即目标窗口。

src win 和 dstwin 的尺寸不需要完全相同。如果srcwin 大于 dstwin 窗口,函数仅仅拷贝srcwin 中适合的 dstwin 的部分。

overlay()是一种非破坏性的拷贝 (non-destructive) ,它不会拷贝源窗口上的空字符,因此如果源窗口的某个位置为空字符,而目标窗口对应位置不为空字符,那么 overlay() 拷贝后目标窗口的原有字符将继续保留;源窗口中的非空字符将覆盖目标窗口对应位置上的字符。这一点我们可以从下面的示例程序看出来。

这个函数通常用来从重迭窗口中建立组合屏幕。例如下面的程序片断中 overlay 用两个不同的窗口组合成了标准屏幕。

WINDOW *info, *cmdinfo ;

overlay(info, stdscr) ;

overlay(cmdmenu, stdscr) ;

refresh();

注意这个函数不能适用于非关联窗口,所谓非关联窗口即两个窗口之间没有任何关联。

 

与overlay() 相同, overwrite() 也可以将一个窗口中的内容拷贝到另外一个窗口。它与overlay() 唯一的区别就是它是一种破坏性的拷贝 (destructive) 。它用源窗口的所有内容完全覆盖目标窗口,包括空字符。因此一旦在第二个窗口上写入第一个窗口的内容,第二个窗口以前的内容将被销毁。函数 overwrite ()的参数的含义如下:

■   srcwin是拷贝的源窗口。

■  dstwin 是拷贝的目标窗口。

与overlay() 一样,如果 srcwin 的大小超过了 dstwin ,那么函数仅仅拷贝srcwin 中与 dstwin 对应的部分。

例如,在下面的程序片断中, overwrite 将一个窗口中的内容拷贝到标准屏幕上。

WINDOW *work;

overwrite(word, stdscr) ;

refresh();

 

不管是overlay() 还是 overwrite() ,它们实际上底层调用的都是 copywin() 函数。如果你不想使用 overlay() 和 overwrite() ,你可以直接使用 copywin() 。只是 copywin() 的函数形式更为复杂一些。它的参数含义如下:

srcwin是拷贝的源窗口指针。

dstwin是拷贝的目标窗口指针。

dminrow、 dmincol 是目标窗口中拷贝区域的左上角的位置,而 dmaxrow 和 dmaxcol 是区域的高度和宽度。

sminrow、 smincol 是源窗口中需要拷贝的区域的左上角的位置,它的右下角的位置由目标窗口中的区域决定,通常为 sminrow+dmaxrow 和 smincol+dmaxcol 。

overlay用来表明拷贝是破坏性还是非破坏性的。

copywin()的功能较 overlay() 和 overwrite() 相比更为强大,它可以将源窗口的任何部分

复制到目标窗口的任何部分。因此overlay() 和 overwrite() 仅仅是 copywin() 的一个功能子集。

下面我们看一个窗口拷贝的例子,其中我们尤其需要注意的是overlay() 和 overwrite() 的区别:对目标窗口的破坏性。在程序中我们将屏幕背景设置为字符 ACS_CKBOARD ,这样对比效果能够清楚一些。

程序3-1  窗口拷贝函数使用示例

程序名称   win_copy .c
编译命令cc  – o win_copy win_copy.c -lcurses
#include <curses.h>
main(void)
{   WINDOW *scrwin;
int ch;
int w, h , i , j ;
initscr();
getmaxyx(stdscr, h , w) ;
for(i=0; i<h-1 ; i++)
for(j=0; j<w ; j++)
mvaddch(i, j , ACS_CKBOARD) ;
refresh();  
 
scrwin=newwin(5, 25 , 4 , 15) ;
box(scrwin, 0 , 0) ;
mvwaddstr(scrwin, 2 , 2 , " 窗口拷贝函数使用示例 ") ;
refresh();
wrefresh(scrwin);
ch=getch();
if(ch=='c')
{
overlay(scrwin, stdscr) ;
mvwin(scrwin, 10 , 15) ;
refresh();
wrefresh(scrwin);
sleep(1);
endwin();
}
else
endwin();
}
程序运

行结果如图3.2 所示:


图3.2

从图3.2 可以看出,使用 overlay() 函数,目标窗口 stdscr 中的确有一些部分没有被破坏掉,而它们对应的源窗口部分正好是空字符。

如果将overlay() 替换成 overwrite() ,则程序运行结果则如图 3.3 所示:


图3.3

从图3.3 也能够看出 overlay() 会将源窗口中的所有字符包括空字符拷贝到目标窗口上。

3 .2.6 移动窗口

mvwin() 函数将一个指定的窗口从一个位置移动到另外一个位置。移动后窗口的左上角位置由函数指定。函数语法如表3.7 所示。

表 3. 7  mvwin 函数概述

头文件

curses.h

概述

int mvwin(win, y , x)

window *win;

int  y, x ;

返回

成功

失败

OK

ERR

函数的参数含义如下:

■  win 是需要移动的窗口的指针。

■  y 为窗口移动后左上角所在的行数。

■  x 为窗口移动后右上角所在的列数。

如果我们需要查看的窗口被其余的窗口覆盖,那么就需要使用移动函数将原来的窗口移开。在下面的程序中我们给出了一个完整的例子,通过方向键我们可以任意的在屏幕上移动给定的窗口 alertWindow 。

程序3-2  窗口移动示例程序

程序名称  mvwin.c

编译命令  cc –o mvwin  mvwin.c –lcurses
#include <curses.h>
#include <stdio.h>
#include <stdlib.h>
main()
{
int ch;
int y, x ;
WINDOW *alertWindow;
initscr();
noecho();
keypad(stdscr, TRUE) ;
refresh();
alertWindow = newwin(8, 40 , 6 , 20) ;
box(alertWindow, ' ┃ ', ' ━ ');
mvwaddstr(alertWindow, 2 , 8 , "please move by  ←↑→↓ ");
mvwaddstr(alertWindow, 4 , 8 , "press any key to exit") ;
wattron(alertWindow, A_REVERSE) ;
mvwaddstr(alertWindow, 6 , 18 , "OK") ;
wattroff(alertWindow, A_REVERSE) ;
wrefresh(alertWindow);
ch=getch();
while(ch==KEY_RIGHT||ch==KEY_LEFT||ch==KEY_UP||ch==KEY_DOWN)
{
clear();   refresh();
y=alertWindow->_begy;  
x=alertWindow->_begx;  
switch (ch){
case KEY_RIGHT:
x++;
mvwin(alertWindow, y , x) ;
wrefresh(alertWindow);
break;
case KEY_LEFT:
x--;
mvwin(alertWindow, y , x) ;
wrefresh(alertWindow);
break;
case KEY_UP:
y--;
mvwin(alertWindow, y , x) ;
wrefresh(alertWindow);
break;
case KEY_DOWN:
y++;
mvwin(alertWindow, y , x) ;
wrefresh(alertWindow);
break;
}
ch=getch();
}
delwin(alertWindow);
endwin();
}

 

 


程序运行结果如图3.4 所示:

图3.4

3 .2.7 激活窗口

通常情况下,窗口之间是相互重迭的, touchwin() 函数可以将特定窗口激活,使它获取当前操作的焦点。它通过调用 refresh() 函数进行刷新从而将这个窗口的所有内容显示在标准屏幕上。函数语法如表3.8 所示。

表 3. 8  touchwin 函数概述

头文件

curses.h

概述

void touchwin(win)

WINDOW *win;

返回

成功

失败

OK

ERR

win 是需要激活的窗口的指针。假设有两个窗口 rightscreen 和 leftscreen , leftscreen 被 rightscreen 窗口覆盖,下面的程序代码可以用来激活窗口 leftscreen 的:

touchwin(leftscreen);

下面我们给出程序3-3 ,完整的演示了窗口激活函数的用法,在主窗口中我们一旦输入一个不是‘ q ’的键,则主窗口中将弹出一个消息框,继续则消息框消失。函数执行过程中使用了 touchwin() 函数激活消息窗口。关键部分我们给出注释。

程序3 - 3   窗口激活示例程序

程序3-3  窗口激活示例程序
程序名称  touchwin.c
编译命令  cc –o touchwin  touchwin.c –lcurses
#include <curses.h>
#include <stdio.h>
#include <stdlib.h>
main()
{
int ch, i ;
WINDOW *alertWindow; 
char strArr[7][80]; 
strcpy (strArr[0], " #####   #     #  ######    #####   #######   #####") ;
strcpy (strArr[1], "#     #  #     #  #     #  #     #  #        #     #") ;
strcpy (strArr[2], "#        #     #  #     #  #        #        #") ;
strcpy (strArr[3], "#        #     #  ######    #####   #####     #####") ;
strcpy (strArr[4], "#        #     #  #   #          #  #              #") ;
strcpy (strArr[5], "#     #  #     #  #    #   #     #  #        #     #") ;
strcpy (strArr[6], " #####    #####   #     #   #####   #######   #####") ;
initscr(); // 初始化 curses 库
noecho();
keypad(stdscr, TRUE) ;
refresh();
, ' ┃ ', ' ━ '); // 在窗口的四周进行边界装饰
for (i=0;  i<7 ;  i++)
{
mvwaddstr(stdscr, 8+i ,  16 ,  strArr[i]) ; // 在主窗口上显示 ”CURSES” 字样
}
alertWindow = newwin(8, 40 , 6 , 20) ;  // 创建弹出窗口
while ( (ch = getch()) != 'q')//
{
, ' ┃ ', ' ━ ');
mvwaddstr(alertWindow, 2 , 8 , " 这是一个弹出窗口 ");
mvwaddstr(alertWindow, 4 , 8 , " 请按任意键继续 !");
wattron(alertWindow, A_REVERSE) ; // 打开窗口反显属性
mvwaddstr(alertWindow, 6 , 18 , "OK") ; // 反色显示 ”OK” 字样
wattroff(alertWindow, A_REVERSE) ; // 关闭窗口反显属性
touchwin(alertWindow); // 激活消息框窗口
wrefresh(alertWindow); // 刷新显示
getch();
touchwin(stdscr); // 激活 stdscr 窗口
wrefresh(stdscr);
}
delwin(alertWindow); // 一旦推出将删除消息窗口
endwin();
}

 

 


程序的执行结果如图3.5 和 3.6 所示:

图3.5

 

 


图3.6

3 .2.8 窗口边界修饰

从前面的例子中我们看到在创建窗口之后我们都调用了 box() 函数,这个函数是用来在创建的窗口周围装饰边框的。事实上到目前位置如果没有 box() 函数,我们创建的窗口在屏幕上其实还是看不出来的,窗口的周围并没有我们想象中的边框。在标准屏幕上我们无法将创建的窗口与标准屏幕区分开来。区分的唯一办法就是调用 box() 函数在创建的窗口的周围加上边框。 box() 函数语法如表3.9 所示。

表 3. 9 b ox ()函数概述

头文件

curses.h

概述

int , vert ,  hort)

WINDOW *win;

chtype vert;

chtype hort;

返回

成功

失败

OK

ERR

win 是需要画出边界的窗口。

vert 是垂直方向的字符,通常情况下为 ACS_VLINE ,即 ' ┃ ' 。

hort 是水平方向的字符,通常情况下为 ACS_HLINE ,即 ' ━ '  。

需要注意的是 , ‘┃’和 ' ━ ' 不是通常键盘上的‘ | ’和   ‘ - ’。‘ | ’和‘ - ’是单字节字符,而‘┃’和‘━’   是双字节字符。

例如下面的程序代码在给定的窗口 cmdmenu 周围画上边界。

WINDOW *cmdmenu;

cmdmenu = subwin(stdscr , 5  ,  80  ,  19  ,  0 ) ;

box(cmdmenu,  ' ┃ ', ' ━ ');

如果你不想使用 box(cmdmenu , ' ┃ ' , ' ━ ') ,可以使用box(cmdmenu , 0 , 0) ,它们的效果一样。

提到box()  函数,不能不提 border() 函数,它可以在给定的窗口绘制边界,它的作用与 box() 类似,只是用法更为复杂。函数语法如表 3.10 所示。

表 3. 10 border函数概述

头文件

curses.h

概述

int border(win, ls , rs , ts , bs , tl , tr , bl , br) ;

WINDOW *win;

chtype ls, rs , ts , bs , tl , tr , bl , br ;

返回

成功

失败

OK

ERR

参数WINDOW 是需要绘制边界的窗口, ls , rs , ts , bs , tl , tr , bl , br 分别是各个方向上绘制的字符。

变量名

解释

ls

窗口左边字符

rs

窗口右边字符

ts

窗口上边字符

bs

窗口下边字符

tl

窗口左上角字符

tr

窗口右上角字符

bl

窗口左下角字符

br

窗口右下角字符

除了绘制窗口边界,curses 中还提供了两个绘制直线的函数 vline() 和 hline() 。它们分别绘制垂直线和水平线。函数语法如表 3.11 所示。

表 3. 11直线绘制函数概述

头文件

curses.h

概述

int hline(chtype ch, int n) ;

int vline(chtype ch, int n) ;

 

int whline(WINDOW *win, chtype ch , int n) ;

int wvline(WINDOW *win, chtype ch , int n) ;

返回

成功

失败

OK

ERR

上面的函数中,ch 是直线字符,对于 hline 是‘ ━ ’,对于vline 是‘ ┃ ’。n 用来指定最大的线长。 hline() 和 vline() 通常在标准屏幕中绘制直线,而 whline() 和 wvline() 则可以在任意窗口中绘制直线, win 是窗口指针。

3 .2.9 设置窗口标志

每一个窗口都具有一定的标志,包括光标(cursor) ,滚动 (scroll) ,屏幕清除 (clear-screen) 等等。函数 leaveok () ,scrollok ()和 clearok ()等函数可以用来设置或者清除这些标志。这些标志会影响函数 refresh ()产生的效果。函数语法如表 3.12 所示。

表 3. 12窗口标志函数概述

头文件

curses.h

概述

void leaveok(win, state)

WINDOW *win;

bool state;

 

void scrollok(win, state)

WINDOW *win;

bool state;

 

void clearok(win, state)

WINDOW *win;

bool state;

leaveok() 函数设置和清除窗口的cursor 标志,这个标志用来决定 refresh() 函数如何放置终端光标和屏幕刷新后的窗口指针。如果设置了这个标志,refresh() 函数在最后一个字符拷贝到屏幕上后将离开光标,同时将它移动到窗口中的相应位置上,   否则 refresh ()将光标移动到标准屏幕上与当前光标在窗口中同样的位置。

函数的参数含义如下:

■  win 是需要设置标志的窗口的指针。

■  state 是布尔类型的值,用来决定设置还是取消这个标志。如果 state 为 TRUE ,则设置标志,否则如果为 FALSE, 取消设置。

例如下面的例子设置了 curses 标志:

leaveok(stdscr, TRUE) ;

 

scrollok() 函数设置给定窗口的滚动标志,如果标志设置,则窗口允许滚动,否则不允许滚动。函数的参数的意义如下:

■  win 是指向需要设置标志的窗口的指针。

■  state 决定是否需要设置标志,如果为 TRUE, 需要设置,否则不需要设置标志。

如果窗口允许滚动,我们可以通过函数 scroll() 执行滚动操作,函数语法如表3.13 所示。

表 3. 13窗口滚动函数概述

头文件

curses.h

概述

void scroll(win)

WINDOW *win;

返回

成功

失败

OK

ERR

win 是指向需要滚动的窗口的指针。

如果我们所需要的内容在给定的屏幕内无法一次阅读,我们就必须在窗口内进行滚动。滚动也是我们最经常使用技术之一。下面我们给出一个详细的滚动的程序示例3-4 。

程序3-4  窗口滚动示例函数

程序名称  scrollwin.c

编译命令 cc –o scrollwin scrollwin.c –lcurse
#include <curses.h>
#include <signal.h>
static int finish(int sig);
WINDOW *scrwin, *boxwin ;
main()
{
int i;
char ch;
initscr();
cbreak();
noecho();
nonl();
scrwin=newwin(10, 40 , LINES/2-6 , COLS/2-25) ;
boxwin=newwin(12, 42 , LINES/2-7 , COLS/2-26) ;
scrollok(scrwin, TRUE) ;
box(boxwin, ' ┃ ', ' ━ ');
refresh();
wrefresh(boxwin);
signal(SIGINT, finish) ;
signal(SIGQUIT, finish) ;
for(i=0;; i++)
{
if(i%20==0)
sleep(1); // 把这行删掉,看看会有什么后果! J
wprintw(scrwin, "output string %d/n" , i%9) ;
wrefresh(scrwin);
}  
}
 
int finish(int sig)
{
endwin();
exit(0);
}

程序运行结果如图3.7 所示。程序中按下 DEL 键后程序将退出。

在讨论clearok() 函数之前我们先来讨论一下 WINDOW结构中的 _clear标志位 。 如果设置了该标志 ,那么 当调用它时 , 它会用refresh () 来发送控制代码到终端 , refresh检查窗体的宽度是否是屏幕的宽度 ( 使用 _FULLWIN 标志位 ) 。  如果是的话 , 它将用内置的终端方法刷新屏幕 , 它将写入除了空白字符外的文本字符到屏幕 , 这是一种非常快速清屏的方法 。 为什么仅仅当窗体的宽度和屏幕宽度相等时才用内置的终端方法清屏呢 ? 那是因为控制终

图3.7

端代码不仅仅只清除窗体自身 , 它还可以清除当前屏幕 。 _clear标志位由 clearok () 函数控制 。

 

 


因此 clearok() 函数实际上是对给定的屏幕设置或者取消屏幕清除(_clear) 标志,参数的含义跟上面的两个函数的参数一样。下面的例子中对标准屏幕设置了 clear 标志。

clearok(stdscr, true) ;

如果对当前屏幕 curscr 设置了_ clear 标志,那么对于 refresh() 的每一次调用都会自动的清除屏幕,而不管是哪个窗口在调用中。

3 .2.10 窗口刷新

在前面我们不止一次谈到了刷新的问题,目前curses 中还提供了如表 3.14 所示的一些刷新函数。

表 3.1 4窗口刷新函数概述

头文件

curses.h

概述

int wrefresh(win)

WINDOW  *win

 

int wnoutrefresh(win)

WINDOW  *win

 

int doupdate(win)

WINDOW  *win

int redrawwin(win)

WINDOW  *win

 

int wredrawln(win, beg_line , num_lines) ;

WINDOW *win;

int beg_line;

int num_lines

返回

成功

失败

OK

ERR

wrefresh() 函数刷新屏幕上特定窗口的内容,它将指定虚拟窗口的内容写入终端物理屏幕上。它实际上是依次调用函数 wnoutrefresh() 和 doupdate() 。除了所有的窗口结构之外,系统中定义了另外两个数据结构来描述终端屏幕:物理屏幕和虚拟屏幕。物理屏幕描述了屏幕上实际显示的内容,虚拟屏幕描述了将要显示的内容。 wrefresh() 首先调用函数 wnoutrefresh() 将指定的窗口内容拷贝到虚拟屏幕的数据结构中,接着调用 doupdate() 进行刷新。在刷新过程中虚拟屏幕与实际屏幕进行比较然后仅仅刷新不同之处。

如果同时有几个窗口都必须刷新,那么每一次对 wrefresh() 函数的调用实际上都是对 wnoutrefresh() 和 doupdate() 轮流调用,这样的结果就是进行多次重复的屏幕输出。

 

事实上,这正是直接调用 wrefresh() 的一个缺点,它是一种较低效率的刷新。如果我们直接分开调用 wnoutrefresh() 和 doupdate() ,刷新效率比使用 wrefresh() 本身直接刷新更有效。通过对每一个窗口调用 wnoutrefresh() ,然后只集中进行一次 doupdate() ,这样需要输出的字符的总数量和总的处理时间得到最小化。在程序 3- 5中我们多次 wnoutrefresh() ,最后只使用了一次 doupdate() 函数。

程序3-5  窗口刷新示例程序

程序名称  doupdate.c

编译命令 cc –o doupdate doupdate.c –lcurse
#include <curses.h>
main()
{
WINDOW *w1,  *w2 ;
initscr();
w1=newwin(10, 40 , 8 , 36) ;
box(w1, ' ┃ ', ' ━ ');
w2=newwin(8, 14 , 5 , 5) ;
box(w2, ' ┃ ', ' ━ ');  waddstr(w1 , ”bulls”) ;
wnoutrefresh(w1);
waddstr(w2, ”eye”) ;
wnoutrefresh(w2);
doupdate();
endwin();
}

在程序3-5 中我们使用 wnoutrefresh() 和 doupdate() 函数代替 wrefresh() 的效果我们可以从图3.8 、 3.9 、 3.10 看出来。

 

 


图3.8

 

 

 

 

 

 

 

 

 


图3.9

 

 

 

 

 

 

 

 

 


 

 


图3.10

同样在基垫中函数 poutrefresh ()和 doupdate ()可以用来代替 prefresh ()来完成更高效的刷新。

除了刷新窗口以外,有的时候我们在操作的时候可能会将窗口内容破坏掉,为了恢复,这就需要重画窗口。curses 中提供了两个函数 redrawwin() 和 wredrawln() 分别完成窗口整体和局部的重画。

redrawwin()用来通知 curses 系统指定窗口中的内容可能部分被破坏,在向它写入新的内容之前必须将它重画。它的重画针对整个窗口,函数指针 win 指定需要重画的窗口。

与redrawwin() 类似, wredrawln() 也完成窗口的重画,但它只完成窗口指定区域内的重画,区域范围通过参数 beg_line 和 num_lines 指定。 beg_line 为区域的起始行, num_lines 为区域的行数。 win 为窗口指针。

过多的使用redrawwin() 函数可能导致通讯量猛增,因为即使小范围重画, redrawwin() 也会刷新整个窗口。 redrawwin() 重画可能导致了通讯中传输了大量的无效数据。因此大部分情况我们宁可使用 wredrawln() 。

3.2.11屏幕转储

在window 下我们可以抓取屏幕,在 Unix 终端环境下我们却无法做到这一点。但 curses 中还是提供了两个函数 putwin() 和 getwin() 我们可以将窗口中内容写入某个文件中或者从文件中恢复窗口内容。这两个函数的语法如表 3.15 所示。

表 3. 15 屏幕转储函数概述

头文件

curses.h

概述

int putwin(win, filep) ;

WINDOW *win;

FILE *filep;

 

WINDOW *getwin(filep);

FILE *filep;

返回

成功

失败

OK

ERR

putwin()将窗口 win 中的所有数据拷贝到由 filep 指定的已经打开的文件中,而 getwin() 则返回由 filep 中的内容创建的窗口的指针。这就是所谓的屏幕窗口转储。

 

除了上面两个函数,scr_dump() 和 scr_restore() 可以完成同样的效果。只不过它们只能针对标准屏幕 stdscr 操作,而不是窗口。函数语法如表 3.16 所示。

表 3. 16 屏幕转储函数概述

头文件

curses.h

概述

int scr_dump(const char *filename);

int scr_restore(const char *filename);

返回

成功

失败

OK

ERR

需要注意的是如果屏幕使用scr_dump() 存储,则只能使用 scr_restore() 进行恢复。它们与 putwin() 和 getwin() 不能混合使用。如果使用 scr_restore() 读取 putwin() 写入的数据或者使用 getwin() 读取 scr_dump() 写入的数据将导致错误。

在程序3-6 和 3-7 中我们演示了 putwin() 和 getwin() 函数的用法, putwin() 将窗口 win 保存到文件 a.txt 中,窗口 win 之外的则不保存; getwin() 函数则将文件 a.txt 中的内容恢复出来。

程序3 - 6   屏幕转储函数putwin() 示例

程序3-6  窗口转储 putwin() 示例程序

程序名称   putwin .c
编译命令 cc  – o putwin putwin.c -lcurses
#include <curses.h>
#include <stdio.h>
#include <stdlib.h>
main(void)
{
FILE *fp;
WINDOW *win;
int ch;
if((fp=fopen("a.txt", "wr"))==NULL)
printf("无法打开文件 a.txt/n") ;  
initscr();
printw("窗口之外的内容 ") ;
win=newwin(10, 30 , 5 , 20) ;
box(win, 0 , 0) ;
mvwaddstr(win, 2 , 5 , "putwin() 函数示例程序 ") ;
refresh();
wrefresh(win);
ch=getch();
putwin(win, fp) ;
endwin();  
}
程序3-7 屏幕转储示例程序
程序名称   getwin .c
编译命令 cc  – o getwin getwin.c -lcurses
#include <curses.h>
#include <stdio.h>
#include <stdlib.h>
main(void)
{
FILE *fp;
WINDOW *win;
int ch;
if((fp=fopen("a.txt", "r"))==NULL)
printf("无法打开文件 a.txt/n") ;  
initscr();
win=getwin(fp);
box(win, 0 , 0) ;
refresh();
wrefresh(win);
ch=getch();
delwin(win);
endwin();  
}

 

3.2.12窗口使用示例——使用窗口构建菜单

在应用系统中我们经常使用到菜单,通过菜单我们可以进行各种选择。但是Unix 下编写菜单程序并不是一件简单的事情,如果做的扩充性更好一些则更不容易。综合前面的关于 curses 窗口的学习,到现在我们可以使用 curses 窗口自己创建一个简单的菜单程序。另外 curses 包中提供了菜单库,这样我们可以直接使用菜单库方便的创建菜单了。菜单程序如下:

程序3-8  使用 curses 窗口创建菜单例程序

程序名称   menuwin .c
编译命令 cc –o  menuwin  menuwin.c –lcurse
#include <curses.h>
#include <stdlib.h>
 
#define ENTER 10
#define ESCAPE 27
void init_curses()
{
;
;
, COLOR_WHITE , COLOR_BLUE) ;
, COLOR_BLUE , COLOR_WHITE) ;
, COLOR_RED , COLOR_WHITE) ;
, COLOR_GREEN , COLOR_BLUE) ;
;
;
, TRUE) ;
}
void draw_menubar(WINDOW *menubar)
{
, COLOR_PAIR(2)) ;
, "  Menu1") ;
, COLOR_PAIR(3)) ;
, "(F1)") ;
, COLOR_PAIR(3)) ;
, 0 , 20) ;
, "  Menu2") ;
, COLOR_PAIR(3)) ;
, "(F2)") ;
, COLOR_PAIR(3)) ;
}
WINDOW **draw_menu(int start_col)
{
;
;
;
 
, 19 , 2 , start_col) ;
, COLOR_PAIR(2)) ;
, ACS_VLINE , ACS_HLINE) ;
, 1 , 17 , 3 , start_col+1) ;
, 1 , 17 , 4 , start_col+1) ;
, 1 , 17 , 5 , start_col+1) ;
, 1 , 17 , 6 , start_col+1) ;
, 1 , 17 , 7 , start_col+1) ;
, 1 , 17 , 8 , start_col+1) ;
, 1 , 17 , 9 , start_col+1) ;
, 1 , 17 , 10 , start_col+1) ;
; i<9 ; i++)
, "Item%d" , i) ;
, COLOR_PAIR(2)) ;
;
;
}
void delete_menu(WINDOW **items, int count)
{
;
; i<count ; i++)
;
;
}
int scroll_menu(WINDOW **items, int count , int menu_start_col)
{
;
;
        while (1) {
;
                if (key==KEY_DOWN || key==KEY_UP) {
, COLOR_PAIR(2)) ;
;
                        if (key==KEY_DOWN) {
;
                        } else {
;
                        }
, COLOR_PAIR(1)) ;
;
;
                } else if (key==KEY_LEFT || key==KEY_RIGHT) {
, count+1) ;
;
;
;
, 8 , 20-menu_start_col) ;
                } else if (key==ESCAPE) {
;
                } else if (key==ENTER) {
;
                }
        }
}
int main()
{
;
, *messagebar ;
    
;
    
;
, 1 , 80 , 1 , 0) ;
, 1 , 79 , 23 , 1) ;
;
, 1) ;
;
;
, 0 , 0) ;
;
 
    do {
;
;
;
;
;
        if (key==KEY_F(1)) {
;
, 8 , 0) ;
, 9) ;
            if (selected_item<0)
, "You haven't selected any item.") ;
            else
,
, selected_item+1) ;
;
;
        } else if (key==KEY_F(2)) {
;
, 8 , 20) ;
, 9) ;
            if (selected_item<0)
, "You haven't selected any item.") ;
            else
,
, selected_item+1) ;
;
;
        }
;
    
;
;
;
;
}

程序的运行界面如图3.11 所示:


图3.11

3 .3 基垫——另一种窗口

基垫也是窗口,但是它与窗口稍微有点不同。窗口的大小不能超出物理屏幕的范围,而基垫则没有这个限制,它可以非常大甚至完全不可见。基垫显示在物理屏幕上的部分由基垫的刷新函数决定。大部分窗口操作的函数都可以直接用在基垫上,只有少数才是基垫独有的,比如创建基垫和子基垫以及刷新基垫的函数等等。

由于基垫可以完全不可见,因此这也导致了基垫和窗口的一个最主要的差异,即就是基垫是与标准屏幕没有直接关联的,理解这一点是理解基垫的关键。

3 .3.1 创建和销毁基垫

newpad ()函数可以创建一个基垫,并且返回一个 WINDOW 类型的指针,这一点与 newwin ()非常的类似。其函数语法如表 3.17 所示。

表 3.1 7基垫创建函数概述

头文件

curses.h

概述

WINDOW *newpad(lines, cols)

in t lines, cols ;

返回

成功

失败

WINDOW*

(*WINDOW)NULL

参数 lines  和 cols 指定了创建的基垫的总行数和总列数。它们的值可以超出LINES 和 COLS 的范围。比如下面的代码就可以创建一个 80 行 128 列的基垫:

WINDOW  *pad;

pad=newpad(80, 128) ; 

将newpad(lines , cols) 和 newwin(lines , cols , begin_x , begin_y) 两个函数一对比就可以看出, newpad() 中缺少了 newwin() 中的定位参数 begin_x 和 begin_y 。正如上面所言,由于与标准屏幕没有任何的关联,因此这些在标准屏幕中定位的参数在这儿就用不到。

3 .3.2 创建子基垫

subpad ()函数创建一个基垫的子基垫,并且返回创建的子基垫的窗口指针。子基垫共享父基垫的全部或者部分字符空间。函数语法如表 3.18 所示。

表 3.1 8子基垫创建函数概述

头文件

curses.h

概述

WINDOW *subpad(pad, lines , cols , begin_y , begin_x)

WINDOW *pad;

int lines, cols , begin_y , beign_x ;

返回

成功

失败

WINDOW*

(*WINDOW)NULL

s ubpad ()的函数参数的意义如下:

■  pad 是需要创建的子基垫的父基垫。

■ lines 和 cols 是创建的子基垫的行数和列数。

■  begin_y 和 begin_x 是创建的子基垫的左上角在父基垫中的相对位置。

下面的代码在pad 中创建一个子基垫 subpad :

WINDOW  *subpad;

subpad=subpad(pad, 10 , 10 , 30 , 40) ;

 

3 .3.3 刷新基垫

 

 


窗口一旦创建,它与标准屏幕的相对位置就固定下来了,因此我们可以使用wrefresh() 对窗口进行刷新。而基垫则不同,创建后它与标准屏幕的相对位置并不固定,它游离于标准屏幕之外,因此我们不能使用 wrefresh() 进行刷新,而只能使用 prefresh ()函数。 p refresh ()和 wrefresh ()的作用是一样的。但是由于基垫不与标准屏幕的任何部分关联,因此我们必须用额外的参数来指定基垫中需要刷新的区域,以及指定它在屏幕上显示的区域,这两块区域大小相等。它们之间的关系可以从图 3.12 看出来。在下面的表述中为了简单起见,我们将基垫中需要刷新的区域简称为 B 区域,屏幕上显示区域称之为 A 区域。 prefresh() 的语法见表 3.19 。

 

图3.12

表 3.1 9基垫刷新函数概述

头文件

curses.h

概述

int  prefresh(pad, prow , pcol , sminrow , smincol , smaxrow , smaxcol)

WINDOW *pad;

int prow, pcol ;

int sminrow, smincol , smaxrow , smaxcol ;

返回

成功

失败

OK

ERR

其中参数含义如下:

■  pad 是需要刷新的基垫的指针。

■  prow 和 pcol 描述了在基垫中需要刷新的区域,即B 区域的左上角的位置。

sminrow, smincol , smaxrow , smaxcol  指定标准屏幕上显示基垫的矩形区域,即A 区域。 sminrow , smincol 是 A 区域左上角的位置, smaxrow , smaxcol 是 A 区域右下角的位置。如果 prow , pcol , sminrow , smincol 等的值为负值,则将它们当作 0 处理。

上面的参数的具体意义从图3-12 中也可以看出来。另一方面, prefresh() 函数中只给出了 B 区域的左上角位置,却没有给出右下角的位置。由于 A 、 B 两个区域尺寸必须相同,因此实际上我们可以根据 A 区域推断出 B 区域的右下角的位置的。这两个矩形完整的包含在它们各自的数据结构中。

与wrefresh() 一样, prefresh() 也是一种低效率的刷新,它也是 p noutrefresh ()函数和 doupdate ()函数的联合使用。我们也可以通过单独使用 pnoutrefresh() 和 doupdate() 来提高刷新效率。其原因和用法与 wnoutrefresh() 和 doupdate() 相同,这儿不再重复。

 

3.3.4基垫使用示例

虽然基垫也是窗口的一种,但是由于它与窗口不同的创建和刷新方法,尤其是刷新区域和显示区域的关系,使得初次接触基垫的人容易困惑。下面我们给出示例程序3-9 ,然后根据上面的描述,配以图表,详细讲解,希望能够解惑。

建议:看完程序之后运行一下,看看结果,然后再看讲解!

程序3 - 9   基垫使用示例程序

程序名称  usepad.c

编译命令 cc  – o usepad usepad.c  – lcurses
1   #include <curses.h>
2   #include <unistd.h>
3   main()
4   {
5    int i, j ;
6    int w, h ;
7    char lines[128];
8    WINDOW *pad;
9    initscr();
10    getmaxyx(stdscr, h , w) ;
11    for(i=0; i<h-1 ; i++)
12   for(j=0; j<w ; j++)
13   mvaddch(i, j , ACS_CKBOARD) ;
14   refresh();
15   pad=newpad(80, 128) ;
16   for(i=0; i<80 ; i++)
17   {
18   sprintf(lines, "This is Lines %d" , i) ;
19   mvwprintw(pad, i , 0 , "%s" , lines) ;  
20   }
21   prefresh(pad, 0 ,0 , 5 , 5 , 15 , 45) ;
22   sleep(2);
23   i=1;
24   for(i=0; i<50 ; i++)
25   {
26   prefresh(pad, i++ , 0 , 5 , 5 , 15 ,45) ;
27   usleep(30000);  
28   }
29   getch();
30   delwin(pad);
31   endwin();
32  return 0;
33  }

在15 至 20 行处我们创建了一个基垫 pad ,共有 80 行, 128 列。接着在基垫 pad 上输出“ This is Lines 0 ”类似的字符,共八十行,结果如图 3.13 所示:

 

图3.13

在第21 行我们调用 prefresh(pad, 0 ,0 , 5 , 5 , 15 , 45) ,在标准屏幕上创建基垫显示区域,左上角的位置为(5 , 5) ,右下角的位置为 (15 , 45) ,这样标准屏幕上基垫的显示区域为 10 行 40 列。同时可以确定基垫中刷新区域范围,左上角为 (0 , 0) ,右下角为 (9 , 39) 。程序的运行结果就是将基垫内矩形区域 (0 , 0 , 9 , 39) 的内容在标准屏幕的 (5 , 5 , 15 , 45) 范围内显示出来。如图 3.14 所示:

图3.14

接着第24 行到第 28 行进入循环,它每次将基垫窗口中的不同部分刷新显示到标准屏幕上。第一次是 (0 , 0) 到 (9 , 39) 的区域,第二次是 (1 , 0) 到 (10 , 39) 的区域,按此规律依次进行五十次。由于显示内容的连续性,使得产生滚动的效果。