• 案例参考手册-第四章 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,'',sizeof(password));
        while(i<PW_LEN){
            password[i] = getch();
            if(password[i] == '
    ') 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
    ");
            exit(1);
        }
    
        if(start_color() != OK){//初始化颜色
            endwin();
            fprintf(stderr,"Error - could not initilaize colors
    ");
            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();
    
    }

    效果图如下:

    这里写图片描述

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

  • 相关阅读:
    虚树
    最小树形图
    分块
    斜率优化
    单调队列优化DP
    树套树
    2-SAT
    莫队
    单调队列
    单调栈
  • 原文地址:https://www.cnblogs.com/LiuYanYGZ/p/14818927.html
Copyright © 2020-2023  润新知