案例参考手册-第四章 Curses字符界面.docx

摘自:https://max.book118.com/html/2016/1229/78155438.shtm

摘自:https://www.docin.com/p-1290752854.html

摘自:http://www.doc88.com/p-4337729364386.html 

摘自:https://www.freesion.com/article/60331206409/

上一个专题复习了shell的编程,当然只是非常基础的东西,包括awk,sed这些命令
使用都没有进行详细的介绍。这些问题就交给读者,自行学习了,下面我们希望在终端中能开发出像dialog这样的工具。
如果自己手动的对终端进行操作是非常繁琐的事情,就好比你要写个应用程序,居然是从寄存器开始进行编写的,不仅工作量大,而且非常容易出错。现在对终端的操作有了现成的函数库可以使用,那就是curses函数库。

一.安装


在一些linux发行版中,可能并不带有curses函数库。这时就需要自己手动进行安装
对了,这里说一句题外话,我们可以自行的打包自己的linux发行版,比如叫什么xbuntu。关于如何自作linux发行版,国外已经有人再做了,叫做LFS,这里给个网站,可以直接按照上面的步骤来:http://www.linuxfromscratch.org/网上已经有中文版的教程了,直接百度就可以看见。在你自己的发行版本中,可以预置curses库。
这里给出ubuntu下安装curses函数库的命令:

    sudo apt-get install libncurses5-dev libncursesw5-dev

 

如果安装成功,可以看到curses的头文件。如下:

ls –l /usr/include/*curses.h

 

笔者的系统显示如下:

-rw-r--r-- 1 root root 76291 Nov 18  2011 /usr/include/curses.h
lrwxrwxrwx 1 root root     8 Nov 18  2011 /usr/include/ncurses.h -> curses.h

 

可以看到我们在安装curses的时候,使用的libncurses.中间有一个n,其实这个n是单词new的缩写,这个就必须说到curses的历史了。
curses最早出现在BSD版本的unix系统上,在后面被用到了systemv上面。再后来由X/Open组织对curses进行了标准化。因为是unix所以,linux总想着怎么来个开源版本,so,new curses出现了,简称成ncurses,这个几乎可以完全不用修改移植到unix系统上。
如果你是其他的linux发行版本,比如fedora,centos,RHEL等等。要安装curses可以google,这里给一个地址,写得比较详细:
https://www.cyberciti.biz/faq/linux-install-ncurses-library-headers-on-debian-ubuntu-centos-fedora/
二,来个体验的例子
任何一个程序教程,如果不能让学习者,快速的熟悉,就是瞎说,因此这也造就了hello world的世界。同样我们也用curses显示一个hello world
注意:这里使用的语言是c语言,已经不是前面介绍的shell语言了。c语言的学习很有必要,甚至推荐学习x86的汇编。这些已经超出本节的范围,具体细节可以借助论坛,百度,google向已经学习过的人,请教学习步骤和经验。
建立first.c文件,在first.c文件中键入如下的内容:

#include<unistd.h>
#include<stdlib.h>
#include<curses.h>
int main(){
        initscr();

        move(5,18);
        printw("%s","hello world");
        refresh();
        sleep(3);
        endwin();
}

 

编译:

gcc first.c –lncurses

 

生成a.out,你也可以gcc –o “生成的可执行文件的名字” first.c –lncurses
关于gcc命令的使用和参数作用,相比读者都应该清楚,如果不清楚,可以百度看下,没什么难处的。
运行a.out效果图如下:
这里写图片描述
对程序进行简单的说明:
initscr();//初始化函数
move(5,18);//将光标移动到第五行,第18列。
printw(“%s”,”hello world”);//打印hello world在当前光标下
refresh();//将其显示到物理屏幕上
sleep(3);//这里停留3秒,否则,马上就结束了,不容易看到结果;
endwin();//结束时,需要处理的东西。
有了上面的小程序,就可以来深入的学习curses的里面的函数api了。

二.基本概念

在curses的世界里,为了更好的管理屏幕。定义了一个WINDOW的数据结构,这个
数据结构就是对屏幕的抽象。比如我们前面说的shell标准输入流,就可以说是对键盘的抽象。我们对屏幕的操作都理解为对WINDOW的操作,然后通过refresh()函数将操作一次性的写入到物理屏幕上。
这里为什么不操作一次,就自动对屏幕进行刷新,这个主要涉及到性能问题。在cpu和屏幕之间的物理连线,并不是并行的,而是串行,有时,有些接口甚至连数据总线和控制总线都是公用的,再加上屏幕像素点的增加,每次刷新需要传送的数据非常多,再加上一次刷新还需要进行多次传送,导致性能受限制。因此为了尽量减少刷新次数,curses内部有一个WINDOW结构的变量叫做stdscr,他相当于屏幕的一个缓存,调用refresh()函数之后,才将stdscr里面的东西刷新到屏幕上。
对于curses来说屏幕里面的位置用一个二位数组来表示,左上角表示(0,0),行号在前,列号在后。

三.单屏幕

在使用curses库时,必须先调用initscr()函数,进行相应的初始化工作,当然这些函数
的定义都放在了curses.h头文件中。
在使用完curses库之后,必须调用endwin()函数,进行相应的释放操作。
在调用完initscr()函数之后,curses库已经初始化了一个WINDOW结构,它就是stdscr变量。
我们可以对其进行写入字符串,也可以从光标处读取键入的字符串。还可设置WINDOW的颜色,以及字符串的各种属性等等。
3.1 输出到屏幕
addch(const chtype char_to_add);//在光标处增加一个字符
addchstr(chtype *const string_to_add );//在光标处增加一个字符串
printw(char *format,…);//格式化输出
insch(chtype char_to_insert);//在光标处插入一个字符
insertln(void);//再光标处插入一空行
delch(void);//删除光标处的字符
deleteln(void);//删除一行

refresh(void);//刷新
box(WINDOW *win_ptr,chtype vertical_char,chtype horizontal_char);//构建一个外框
beep(void);//蜂鸣器发出声音
flash(void);//屏幕闪烁
3.2 读取字符
chtype inch(void);//从光标处都取一个字符
int instr(char *string);//读取字符到string所指向的字符串中
int innstr(char *string,int number_of_characters);//读取number_of_characters个字符到string所指向的字符串中
3.3 移动光标
move(int new_y,int new_x);//移动光标到(new_y,new_x)处

3.4 设置字符属性
int attron(chtype attribute);//打开attribute属性
int attroff(chtype attribute);//关闭attribute属性
int attrset(chtype attribute);//打开attribute属性
3.5 清屏
clear();//清楚屏幕
erase();//清除屏幕
这两个函数的区别是:erase()函数是在每个位置写上空白字符。clear()函数在内部也调用一个clearok()函数,clearok()函数会再下一次刷新屏幕的时候,清楚屏幕,并重绘整个屏幕。
3.6 一个简单的程序
有了上面的简单的接口,我们可以开始编写一个简单的应用程序。如下

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

int main(){
const char *witch_one = “First”;
const char *scan_ptr;

/*属性的打开和关闭*/
initscr();
move(5,15);
attron(A_BOLD);
printw(“%s”,”wanbiao  ”);
attroff(A_BOLD);
printw(“%s”,”wanbiao”);
refresh();
sleep(2);

/*插入字符串*/
scan_ptr = witch_one+strlen(witch_one)-1;
while(scan_ptr != witch_one){
move(6,10);
insch(*scan_ptr);
scan_ptr--;
refresh();
sleep(1);
}

endwin();
}

 

四.多窗口

上面的例程只有一个窗口,下面说一下多窗口的api调用。这里有两个函数用于新窗口的创建和销毁
WINDOW *newwin(int num_of_line,int num_of_cols,int starty_y,int start_x);
int delwin(WINDOW *window_to_delete);
其中,newwin函数是创建一个窗口,该窗口从(start_y,start_x)处开始,总共有num_of_line行,和num_of_cols列。如果num_of_line和num_of_cols,大于了当前屏幕的总函数,那么这个函数将调用失败。屏幕的总行数,和总列数保存在:LINES和COLS中。

关于新窗口的其他调用函数,在上一小节中,介绍了大部分的函数,同样这些函数还有一个对应的以w,mv,mvw为前缀的函数,w表示用于窗口,参数列表中必定有一个WINDOW指针的形参,mv表示需要移动,那么参数必定有一个(y,x)坐标的形参,mvw则是前两者的结合,举例如下:
addch(const chtype char);//增加一个字符,那么一定有一个w为前缀的函数,如下:
waddch(WINDOW *window_pointer,const chtype char);//在指定的windo_pointer窗口中,增加一个字符
同样的也有mv为前缀的函数,以及以mvw为前缀的函数,如下:
mvaddch(int y,int x,const chtype char);
mvwaddch(WINDOW * window_pointer,int y,int x ,const chtype char);
4.1 多窗口例程
对于其他的函数,同样也有相同的前缀,这些函数就不再举例,如果需要查看完整的api手册,只需要调用man即可。同样给个例子,这个例子就是上面例子的一个翻版,不过是用在一个新创建的窗口中。

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

int main(){
const char *witch_one = "First";
const char *scan_ptr;
const WINDOW *new_window;
int line,col;
char tmp='A';

initscr();
new_window = newwin(LINES-10,COLS-10,5,5);
/*给以前的窗口添加一个背景*/
for(line=0;line<LINES-1;line++){
    for(col=0;col<COLS-1;col++){
        addch(tmp);
        tmp++;
        if(tmp == 'Z') tmp ='A';
    }
}
refresh();

wrefresh(new_window);
sleep(2);
/*属性的打开与关闭*/
wmove(new_window,5,15);
wattron(new_window,A_BOLD);
wprintw(new_window,"%s","wanbiao   ");
wattroff(new_window,A_BOLD);
wprintw(new_window,"%s","wanbiao");
wrefresh(new_window);
sleep(2);
/*插入字符*/
scan_ptr = witch_one+strlen(witch_one)-1;
while(scan_ptr != witch_one){
wmove(new_window,6,10);
winsch(new_window,*scan_ptr);
scan_ptr--;
wrefresh(new_window);
sleep(1);
}
delwin(new_window);
endwin();
}

 

 

从上面可以看到,函数都有一个w的前缀,并且也有WINDOW结构的参数,对于其他的函数来说也是这种情况
效果如下:
这里写图片描述

4.2 多窗口的管理
当前程序有多个窗口的时候,如何控制那个窗口在上面,那个窗口在下面,并且控制窗口移动到那个位置,下面给出相应的api调用
mvwin(WINDOW *window_to_move,int new_y,int new_x);//移动窗口到(new_y,new_x)位置。
touchwin(WINDOW *window_ptr);//调用该函数,表示该窗口需要更新。比如,在窗口没有任何字符更新的情况下,调用该函数,那么就会在refresh时,将该窗口显示在上面。
scrollok(WINDOW *window_ptr,bool scroll_flag);//打开window_ptr窗口的滚动功能。
scroll(WINDOW *window_ptr);//像上滚动一行
来个例子:

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

int main(){
    WINDOW *new_window_1,*new_window_2;
    int x_loop,y_loop;
    char a_letter = 'a';

    initscr();

    move(5,5);
    printw("%s","wanbiao---multi window");
    refresh();
/*给stdscr填充一个背景*/
    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(2);

    new_window_1 = newwin(10,20,5,5);
    mvwprintw(new_window_1,2,2,"%s","first window");
    wrefresh(new_window_1);
    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();//新窗口将会消失,显示的是stdscr
    sleep(2);

    wrefresh(new_window_1);//更新新窗口,也不会显示在屏幕上,因为这里没有什么需要新的
    sleep(2);

    touchwin(new_window_1);//调用该函数,表示这个窗口需要更新在最上面。
    wrefresh(new_window_1);
    sleep(2);

new_window_2 = newwin(10,20,8,8);
    box(new_window_2,'|','-');//给2号新窗口画一个外框
    mvwprintw(new_window_2,5,5,"%s","second window");
    wrefresh(new_window_2);
    sleep(2);

    touchwin(new_window_1);//显示第一个新窗口
    wrefresh(new_window_1);
    sleep(1);

    wclear(new_window_1);//清除第一个新窗口
    wrefresh(new_window_1);
    sleep(1);

    delwin(new_window_1);
    touchwin(new_window_2);//显示第二个新窗口
    wrefresh(new_window_2);
    sleep(1);

    delwin(new_window_2);
    touchwin(stdscr);
    refresh();
    sleep(1);

    endwin();
}

 

效果如下:
这里写图片描述

五.子窗口
在上面的历程中,每一个WINDOW结构都有一个缓冲,使用子窗口将会和父窗口公用一个缓冲区。
子窗口的建立使用下面的api
WINDOW *subwin(WINDOW *parent ,int num_of_lines,int num_of_cols,int start_y,int start_x);//创建一个子窗口
delwini(WINDOW *sub_window);//删除指定的窗口

子窗口的函数调用,和其他的普通窗口的调用几乎一模一样。子窗口似乎没有什么特别的用处,因为和父窗口几乎一样的显示和操作,子窗口的主要作用是:滚动一个窗口的一部分内容,如父窗口的一部分区域需要上下滚动,或者其他的操作,可以使用子窗口来操作。
注意:因为子窗口和父窗口公用一个缓冲,将缓冲画到屏幕上的时候,需要调用touchwin(WINDOW*);函数,因此,在对子窗口显示之前,需要调用父窗口的touchwin函数。
例子如下:

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

int main(){
    WINDOW *sub_window;
    int x_loop,y_loop;
    int counter;

    char a_letter = '1';
    initscr();
    for(y_loop =0;y_loop<LINES;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';
        }
    }

    sub_window = subwin(stdscr,10,20,10,10);
    wclear(sub_window);
    mvwprintw(sub_window,5,5,"%s","this eara will scroll");
    scrollok(sub_window,1);//打开滚动功能
    touchwin(stdscr);//刷新之前需要调用父窗口的touchwin
    refresh();
    sleep(1);

    for(counter =0;counter<5;counter++){
        wscrl(sub_window,-counter);//滚动counter行,负号表示向下滚动
        touchwin(stdscr);
        refresh();
        sleep(1);
    }

    endwin();
}

 

六,扩展的窗口
对于上面介绍的窗口来说,都有一个限制,那就是新窗口的行数,和列数不能超过屏幕的限制,为了突破这个限制,curses有了pad的概念。
pad就和window是一样的概念,并且都是WINDOW结构,但是,pad能够突破屏幕行,列的限制。
创建函数如下:
WINDOW * newpad(int number_of_lines,int number_of_colums);//number_of_lines行,numberk_of_colums列的pad。
delwin(WINDOW *pad);//同样使用delwin函数来删除window函数。
对于pad的刷下,就跟普通的窗口有点不同,他的刷新函数如下:
prefresh(WINDOW *pad_ptr,int pad_row,int pad_colum,int screen_row_min,int screen_col_min,int screen_row_max,int screen_col_max);//将pad从坐标(pad_row,pad_column)开始的区域显示到屏幕的(screen_row_min,screen_col_min)到(screen_row_max,screen_col_max)所表示的区域中。
关于pad的例子,就不给出了,有兴趣的读者,可以自己写写看看。
七.优化屏幕刷新
当终端是通过网络进行连接时,可能受限与当时的网络,那么在高速刷新的情况下,终端可能显示的非常慢,为了减少每次传输的数据,curses设计了如下两个函数
wnoutrefresh(WINDOW *window_ptr);
doupdate(void);
wnoutrefresh函数用于决定将那些字符更新到屏幕上,但是并没有真正的进行发送,而是在内部进行计算,那些字符需要更新到屏幕上,调用doupdate函数,则将字符更新到屏幕上。对于多个窗口的情况下,每个窗口都调用wnoutrefresh函数,进行计算需要更新到屏幕的字符,然后在最后再调用doupdate函数,一次性将所有需要更新的字符更新到屏幕上。这样最大限度的减少了传送的数据长度。
八,键盘输入
curses不仅提供了屏幕的操作,还提供了,对键盘的操作。
在curses的世界里,键盘的输入分为两种模式,一种模式叫做:cooked模式,另外一种模式叫做:cbreak模式。
cooked模式,表示只有当按下回车键之后,输入的数据才会送给程序。
cbreak模式:表示只要有按键按下,就传送给程序。
注意在这两种模式下,特殊字符也是被使用的。如果想要关闭特殊字符的使用,可以调用raw(void)函数。
int echo(void);//打开输入字符的回显功能
int noecho(void);//关闭输入字符的回显功能
int cbreak(void);//打开cbreak模式
int nocbreak(void);//关闭从break模式
int raw(void);//关闭特殊字符的处理
int noraw(void);//同时恢复cooked模式和特殊字符的处理。

从光标处读取的api:
int getch(void);//从光标处读取一个字符
int getstr(char *string);//从光标处读取一个字符串
int getnstr(char *string ,int number_of_characters);//从光标处读取n个字符
int scanw(char *format,…);//格式化读入
来个例子,这个例子,摘自《linux程序设计》

#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 = "123456";

    int i=0;
    initscr();

    move(5,10);
    printw("%s","please login");
    move(7,10);
    printw("%s","user name:");
    getstr(name);

    move(8,10);
    printw("%s","passwd:");
    refresh();
    cbreak();
    noecho();
    /*初始化password*/
    memset(password,'\0',sizeof(password));
    while(i<PW_LEN){
        password[i] = getch();
        if(password[i] == '\n') break;
        move(8,20+i);
        addch('*');
        refresh();
        i++;
    }
//这里需要将echo打开,同时回复cooked模式。
    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(2);
    endwin();
}

 

效果图如下:
这里写图片描述
九.颜色属性
对于curses来说,他也是有颜色的,当然,以前老式的终端是不支持颜色的,支不支持颜色,可以使用bool has_colors(void);来进行判断。
curses的世界里,颜色是成对出现的,即前景色和背景色这两种。如果需要使用颜色,需要调用int start_color(void);函数来初始化函数。一旦初始化成功,就会将COLOR_PAIRS,和COLORS赋初值。COLOR_PAIRS表示终端能够支持的颜色的组合数,一般情况下为64。COLORS表示终端支持的颜色数,一般为8
初始化颜色对使用:
int init_pair(short pair_number,short foreground,shor background);
int COLOR_PAIR(int pair_number);
下面的例子将初始化一个红色前景色,绿色背景色的颜色对,并将其定义为1号颜色对
init_pair(1,COLOR_RED,COLOR_GREEN);
然后将这个1号颜色对,设置成stdscr的属性如下;
wattron(window_ptr,COLOR_PAIR(1));
这个表达式之后,的所有字符将会是红色前景,绿色背景。
同样如果需要给WINDOW设置背景,则使用下面的函数:
wbkgd(WINDOW * window,chtype attribute);
下面给出一个关于颜色的例子,该例子摘录自《linux程序设计》

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

int main(){
    int i;
    initscr();
    if(! has_colors()){//判断是否可以使用颜色
        endwin();
        fprintf(stderr,"Error - no color support on this teerminal\n");
        exit(1);
    }

    if(start_color() != OK){//初始化颜色
        endwin();
        fprintf(stderr,"Error - could not initilaize colors\n");
        exit(2);
    }

    clear();
    mvprintw(5,5,"There are %d COLORs, and %d COLOR_PAIRS available", COLORS,COLOR_PAIRS);
    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));//打开颜色
        mvprintw(6+i,5,"color pair %d",i);
        attrset(COLOR_PAIR(i)|A_BOLD);//打开粗体和颜色
        mvprintw(6+i,25,"BOLD color pair %d",i);
        refresh();
        sleep(1);
    }

    endwin();

}

 

效果图如下:

这里写图片描述

最近较忙,没时间复习以前看过的内容,只能每天写点。

posted @ 2021-05-27 17:36  LiuYanYGZ  阅读(219)  评论(0编辑  收藏  举报