curses库:为什么getch()会清空我的屏幕?

9

我正在学习使用curses库(pdcurses,在Windows操作系统下),并结合C++使用。

我有一个程序,可以显示3个窗口,然后在while循环中根据通过getch()获取的按键进行一些处理。当按下F1键时,循环会被退出。

但是,在使用wrefresh()刷新所有三个窗口后,在输入第一个按键之前什么也不会显示。如果没有while循环,一切都会正常显示。我已经进行了多次测试,就好像第一次调用getch()将完全清空屏幕,但随后的调用不会产生相同的行为。

我的问题是:我错过了什么?起初,我认为可能getch()调用了一个隐式的refresh(),但为什么随后的调用没有相同的行为呢?

非常感谢您的帮助。

以下是代码:

#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个回答

16
你的第一直觉是正确的:`getch()` 包含隐式的 `refresh()`。具体而言,`getch()` 等价于 `wgetch(stdscr)`,因此它包含隐式的 `wrefresh(stdscr)`,即更新一个窗口(`stdscr`),你在其他情况下没有使用它,但其实填充了屏幕。从那时开始,后续调用没有效果的原因是,就 curses 而言,`stdscr` 已经是最新的,因为你之后没有向它写入内容(忽略实际屏幕上被覆盖的内容)。解决方案要么是在顶部显式地调用 `refresh()`,然后再开始绘制;要么是按照我的喜好,在不同的窗口上调用 `wgetch()`(最合适的窗口),而不是调用 `getch()`,并完全忽略 `stdscr` 的存在。只需记住,所有没有让你指定窗口的函数 — `getch()`、`refresh()` 等等 — 实际上都是对它们的“w”等效函数的调用,其中 `stdscr` 是隐式窗口参数。

从newwin分配的窗口调用wgetch似乎仍会刷新我的屏幕 - 有什么想法吗?这很令人沮丧,因为我想清除屏幕,然后收集输入,然后根据输入进行绘制,在某些情况下会导致闪烁。 - AlgoRythm
抱歉,我不应该在凌晨3点时上SO。这确实是我的错,我忘记将我的“nodelay”调用更改为新的输入窗口,它仍然在我的“stdscr”上,所以当我请求输入时,它会在那里暂停执行。无论如何,还是谢谢! - AlgoRythm

1
默认情况下,getch() 会阻塞直到按下一个键。将您的循环更改为 do {} while(); 循环即可:
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));

如果您需要使getch()非阻塞,方法是在默认窗口上设置curses为nodelay模式。 从pdcurses文档中可以看到:
使用getch()wgetch()mvgetch()mvwgetch()函数时,会从与窗口关联的终端读取字符。在nodelay模式下,如果没有等待输入,将返回值ERR。在延迟模式下,程序将一直等待,直到系统将文本传递给程序。
因此,请调用:
nodelay(stdscr, TRUE);

如果您希望getch()成为非阻塞的,则会返回ERR,如果没有按下任何键。

-1

getch()函数不会清除屏幕,它只是执行它的本职工作,即阻塞while循环,等待从键盘获取字符。

因此,这里有一个可以解决您问题的方法。在curses.h之前,包含conio.h,并将while循环修改为以下内容:

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

这里有另一个修复方法,也会让@Kaz感到高兴。这次我们将使用windows.h而不是conio.h,并且您不再需要那个pressed_key了。将while循环设置为以下内容:

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

顺便提一下,如另一个答案所建议的使用nodelay将解决当前问题,但这基本上会使curses.h的使用“无用”,除了您仍然可以在控制台中进行一些快速图形设计,这可以通过一些技巧而不使用任何库来完成。如果您在该菜单中制作一个小动画,例如由键盘驱动的移动光标等,您就会明白我的意思。基本上,curses主要是因为其延迟功能而被广泛使用,在控制台中使事物看起来更自然,因此它们不会闪烁,特别是当涉及通过重复循环生成的细节/动画时。

2
将 curses(一个起源于 Unix 终端世界的屏幕控制库,现在基本上是跨平台的)与 conio.h(一个具有 Borland C 起源的 DOS/BIOS 接口工具)混合使用是非常愚蠢的。 - Kaz
是的,但kbhit()是我目前想到的,而且它来自conio。 - user1508332

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接