curses library: why does getch() clear my screen?

2020-07-08 06:17发布

I'm trying to learn the curses library (pdcurses, as I'm in Windows OS), with C++. I have a program that displays 3 windows, then a while loop to do some processing based in key presses captured by getch(). The loop gets exited when the F1 key is pressed.

However, despite refreshing all three windows with wrefresh(), nothing appears before I enter my first key press. Without the while loop, everything is displayed fine. I've made numerous tests and it's like the first call to getch() will completely clear the screen, but not the subsequent ones.

My question is: what did I miss? At first, I was thinking that maybe getch() was calling an implicit refresh(), but then why do subsequent calls to it not have the same behaviour?

Thank you very much in advance for your help.

Here is the code.

#include <curses.h>

int main()
{
    initscr();
    raw();
    keypad(stdscr, TRUE);
    noecho();
    curs_set(0);

    WINDOW *wmap, *wlog, *wlegend;
    int pressed_key;
    int map_cursor_y = 10, map_cursor_x = 32;

    wlog = newwin(5, 65, 0, 15);
    wlegend = newwin(25, 15, 0, 0);
    wmap = newwin(20, 65, 5, 15);

    box(wmap, 0 , 0);
    box(wlog, 0 , 0);
    box(wlegend, 0 , 0);

    mvwprintw(wlog, 1, 1, "this is the log window");
    mvwprintw(wlegend, 1, 1, "legends");
    mvwaddch(wmap, map_cursor_y, map_cursor_x, '@');

    wrefresh(wlog);
    wrefresh(wmap);
    wrefresh(wlegend);

    while ((pressed_key = getch()) != KEY_F(1))
    {
         /* process keys to move the @ cursor (left out because irrelevant) */

         box(wmap, 0 , 0);
         box(wlog, 0 , 0);
         box(wlegend, 0 , 0);
         wrefresh(wmap);
         wrefresh(wlog);
         wrefresh(wlegend);
    }

    endwin();
    return 0;
}

3条回答
ゆ 、 Hurt°
2楼-- · 2020-07-08 06:49

Your first instinct was correct: getch() does an implicit refresh(). Specifically, getch() is equivalent to wgetch(stdscr), so it's an implicit wrefresh(stdscr) -- updating a window (stdscr) that you're not otherwise using, that just happens to fill the screen. The reason that subsequent calls have no effect from that point on, is that stdscr is already up to date, as far as curses is concerned, since you never write to it after that (never mind that its contents have been overwritten on the actual screen).

The solution is either to call refresh() explicitly at the top, before you begin drawing; or, my preference, to call wgetch() on a different window (whichever is most appropriate), instead of getch(), and ignore the existence of stdscr entirely. Just remember that all the functions that don't let you specify a window -- getch(), refresh(), etc. -- are really calls to their "w" equivalents, with stdscr as the implicit window parameter.

查看更多
别忘想泡老子
3楼-- · 2020-07-08 06:57

By default getch() blocks until a key is pressed. Change your loop to be a do {} while(); loop:

pressed_key = /* some value that will be benign or indicate that nothing has been pressed */

do {
     /* process keys to move the @ cursor (left out because irrelevant) */

     box(wmap, 0 , 0);
     box(wlog, 0 , 0);
     box(wlegend, 0 , 0);
     wrefresh(wmap);
     wrefresh(wlog);
     wrefresh(wlegend);
} while ((pressed_key = getch()) != KEY_F(1));

If you need getch() to be non-blocking, the way to do that is to set curses for nodelay mode on the default window.

From the pdcurses docs:

With the getch(), wgetch(), mvgetch(), and mvwgetch() functions, a character is read from the terminal associated with the window. In nodelay mode, if there is no input waiting, the value ERR is returned. In delay mode, the program will hang until the system passes text through to the program.

So call:

nodelay(stdscr, TRUE);

if you want getch() to be non-blocking; it will return ERR if no key has been pressed.

查看更多
Root(大扎)
4楼-- · 2020-07-08 07:07

getch() doesn't clears your screen, it just does what it was made to do, blocking your while loop, waiting to get a character from your keyboard.

So here's something that could fix your problem. Before curses.h, include conio.h, and make your while loop like this:

do
{
box(wmap, 0 , 0);
 box(wlog, 0 , 0);
 box(wlegend, 0 , 0);
 wrefresh(wmap);
 wrefresh(wlog);
 wrefresh(wlegend);
 if(kbhit())
    pressed_key = getch();
}while (pressed_key  != KEY_F(1));

Here's another fix for you, that will also make @Kaz happy. This time we'll use windows.h instead of conio.h, and you don't need that pressed_key anymore. Make your while loop like this:

do
{
     /* process keys to move the @ cursor (left out because irrelevant) */
     box(wmap, 0 , 0);
     box(wlog, 0 , 0);
     box(wlegend, 0 , 0);
     wrefresh(wmap);
     wrefresh(wlog);
     wrefresh(wlegend);

}
while (!GetAsyncKeyState(VK_F1));

By the way using nodelay, as suggested in another answer will fix the current problem, but it will pretty much make the use of curses.h "useless", except for the fact you can still do some quick graphics in the console, that can be made with a bit of skill without the use of any library. You'll see what I mean if you'll make a little animation in that menu, like a moving cursor driven by your keyboard and so on. Basically curses are mostly used just because of its delaying capabilities, making things look more natural in the console, so they won't flicker, especially when it comes to details / animations generated through repetitive loops..

查看更多
登录 后发表回答