• 使用curses管理基于文本的屏幕--(四)


    窗口

    直到现在,我们一直将终端作为一个全屏幕的媒介来使用。通常对于小而简单的程序来说这已经足够了,但是curses库所走的路还要更远一些。我们可以同时在物理屏幕上显示多个不同的尺寸的窗口。在这一节将要讨论到一些函数只会被X/Open扩展的curses所支持。然而,因为他们被ncurses所支持,所以在大多数的平台上使用并没有太多的问题。我们现在就来讨论使用多个窗口的问题。我们同时也会看到到目前为止我们所使用的这些命令如何生成多个窗口场景。

    WINDOW结构

    尽管我们已经提到了stdscr,标准屏幕,但是我们现在并不需要使用他,因为到目前为止我们所讨论的几乎所有的函数都假定他们在stdscr上起作用,而且他们并不需要作为一个参数来传递。

    stdscr只是WINDOW结构的一个特殊情况,就如同stdout是文件流的一个特殊情况一样。WINDOW结构通常是在curses.h中定义的,然而试验这个结构是有益的,但是程序绝不应直接访问他,因为这个结构可以而确定在不同的实现之间发生变化。

    我们可以使用newewin与delwin调用来创建和销毁窗口:

    #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)以及指定的行数和列数来创建一个新窗口。他返回一个指向新窗口的指针,如果创建失败则会返回null。如果我们希望将新窗口的右下角位于屏幕的右下角,我们可以将行数或是列数指定为零。所有的窗口必须适合当前屏幕,所以如果新窗口任何部分超出了屏幕区域,newwin函数就会失败。由newwin所创建的新窗口完全独立于已存在的窗口。默认情况下,他将会新生成的窗口放置在任何已存在的窗口之上,并且隐藏(不是改变)其内容。

    delwin函数删除一个由newwin所生成的窗口。因为也许在调用newwin时已经分配了内存,所以我们应总是在不再需要窗口时删除这些窗口。要小心,绝不要试图删除curses自己的窗口,stdscr与curscr。

    创建了一个新窗口之后,如何向其输出内容呢?答案是到目前我们所看到的这些函数都有通用的版本可以在特殊的窗口上进行操作,而且为了方便,同时也包括光标动作。

    通用函数

    我们已经使用addch和printw函数向屏幕添加字符。与许多其他函数一样,他们可以使用前缀进行扩展,窗口为的前缀为w,移动的前缀为mv,移动与窗口的为mvw。如果我们查看curscr头文件,我们就会发现到目前为止我们所看到的这些函数只是简单的调用这些通用函数的宏。

    当添加w前缀时,一个额外的WINDOW指针必须添加到参数列表中。当添加mv前缀时,两个额外的参数,y与x位置,必须添加到参数列表中。他们指明了执行操作的位置。y与x是相对于窗口的,而不是屏幕,(0,0)是窗口的左上角。

    当添加mvw前缀进,三个额外的参数,一个WINDOW指针与y和x的值必须添加到参数列表中。令人不解的是,WINDOW指针必须总是位于屏幕坐标的前面,尽管有时前缀指示y与x在前面。

    作为一个例子,下面是一个完整的addch与printw函数的原型集合。

    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,同具有move与window变体可用。

    移动与更新一个窗口

    下面的这些命令允许我们移动与重绘窗口。

    #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库其参数所指向的窗口内容已经发生了改变。这就意味着curses库会在下一次调用wrefresh时重新绘制窗口,尽管我们并没有实际的改变窗口内容。当我们有多个窗口在屏幕上层叠显示而我们需要决定显示哪个窗口时,这个函数会非常有用。

    两个滚动函数控制一个窗口的滚动。scrollok函数,当传递一个布尔值true(通常为非零)时会允许一个窗口的滚动。默认情况下,窗口并不可以滚动。scroll函数只是简单的将窗口向上滚动一行。一些curses实现也会有一个wsctl函数,这个函数带有一个要滚动的行数,通常是一个负数。我们会在后面回到滚动的话题。

    试验--多窗口

    现在我们已经了解了如何来管理多个窗口,现在我们将这些新函数放在一个程序multiwl.c中。为了描述的简洁,我们在这里省去了错误检测。

    1 如平常一样,首先我们由定义开始:

    #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();

    2 然后我们使用字符来填充基本窗口,一旦逻辑屏幕填充完毕刷新实际的屏幕。

    move(5, 5);
        printw(“%s”, “Testing multiple windows”);
        refresh();
    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’;
        }
    }
    /* Update the screen */
    refresh();
    sleep(2);

    3 现在我们创建一个新的10x20的窗口,并且在屏幕上绘制出窗口之前向其中添加一些文本。

    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(2);

    4 现在我们改变背景窗体的内容,当我们刷新屏幕时, 由new_window_ptr所指向的窗体会变得模糊。

    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(2);

    5 如果进行函数调用刷新新窗体,那么不会改动任何内容,因为我们还没有改动新窗体。

    wrefresh(new_window_ptr);
    sleep(2);

    6 但是如果我们首先触发新窗体,并且使得curses认为窗体已经发生了改变,接下来调用wrefresh则会将新窗体再一次出现在前端。

    touchwin(new_window_ptr);
    wrefresh(new_window_ptr);
    sleep(2);

    7 接下来,我们再添加另外一个有盒子包围的重叠窗体。

    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(2);

    8 然后我们在清除并删除他们之前使用新的弹出窗体。

      touchwin(new_window_ptr);
      wrefresh(new_window_ptr);
      sleep(2);
      wclear(new_window_ptr);
      wrefresh(new_window_ptr);
      sleep(2);
      delwin(new_window_ptr);
      touchwin(popup_window_ptr);
      wrefresh(popup_window_ptr);
      sleep(2);
      delwin(popup_window_ptr);
      touchwin(stdscr);
      refresh();
      sleep(2);
      endwin();
      exit(EXIT_SUCCESS);
    }

    不幸的是,在本书中显示运行效果是不实际的。

    正如我们由这段示例代码中所看到的,我们需要小心的刷新窗体来保证他们以正确的顺序出现在屏幕上。如果我们要求curses刷新一个窗体,他并不会存储关于窗体层次的任何信息。为了保证curses以正确的顺序绘制窗体,我们必须以正确的顺序对他们进行刷新。其中一个方法就是将我们窗体的所有指针存储在一个数组或是列表中,而这个数组或是列表是以他们应出现在屏幕上的顺序而维护的。

    优化屏幕刷新


    正如我们在前面所看到的,刷新多个窗体需要一些繁琐,但并不是完全的繁重。然而,当要更新的终端是在一个慢速的链接上时就会出现一个更为严重的问题。幸运的时,现在这已不再是一个严重的问题,但是为了完整性我们将会演示这个问题的处理是多么的容易。

    目标就是要尽量减少要在屏幕上的绘制的字符数,因为在一个慢速的链接上,屏幕的绘制相当的慢。curses使用两个特殊的函数来进行处理,wnoutrefresh与doupdate。

    #include <curses.h>
    int wnoutrefresh(WINDOW *window_ptr);
    int doupdate(void);

    wnoutrefresh函数决定哪些字符需要发送到屏幕,但是并不实际的发送。doupdate函数向终端发送实际的改变。如果我们只是简单的调用wnoutrefresh,其后立即调用doupdate,其效果就如同调用wrefresh一样。

    然而,如果我们希望重新绘制一个窗体栈,我们可以在每一个窗体(当然需要以正确的顺序)来调用wnoutrefresh函数,然而在最后一个wnoutrefresh函数之后调用doupdate函数。这个使得curses按顺序在每一个窗体上执行屏幕更新计算,并且只输出更新的屏幕。这会使得curses尽量减少需要发送的字符数。

  • 相关阅读:
    C语言的标准输入输出
    Java 循环中标签的作用
    Java并发包中CountDownLatch的工作原理、使用示例
    Java并发包中CyclicBarrier的工作原理、使用示例
    Java并发包中Semaphore的工作原理、源码分析及使用示例
    ScheduleThreadPoolExecutor的工作原理与使用示例
    Java 循环中标签的作用
    线程池ThreadPoolExecutor、Executors参数详解与源代码分析
    线程池的工作原理及使用示例
    Callable、Future、RunnableFuture、FutureTask的原理及应用
  • 原文地址:https://www.cnblogs.com/dyllove98/p/2461982.html
Copyright © 2020-2023  润新知