诅咒库:为什么 getch() 会清除我的屏幕

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

本文关键字:清除 我的 屏幕 为什么 getch 诅咒      更新时间:2023-10-16

我正在尝试用C++学习curses库(pdcurses,因为我在Windows操作系统中)。我有一个程序显示 3 个窗口,然后是一个 while 循环,根据 getch() 捕获的按键进行一些处理。按下 F1 键时,循环将退出。

但是,尽管使用 wrefresh() 刷新了所有三个窗口,但在我第一次按键之前没有任何内容出现。如果没有 while 循环,一切都显示正常。我做了许多测试,就像第一次调用getch()会完全清除屏幕,但不会清除后续的屏幕。

我的问题是:我错过了什么?起初,我在想也许 getch() 正在调用隐式刷新(),但是为什么后续调用它的行为不相同呢?

提前非常感谢您的帮助。

这是代码。

#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;
}

你的第一直觉是正确的:getch()做一个隐含的refresh()。具体来说,getch()等同于wgetch(stdscr),所以它是一个隐式wrefresh(stdscr) - 更新一个你没有使用的窗口(stdscr),它恰好填满了屏幕。从那一刻起,后续调用不起作用的原因是,就诅咒而言,stdscr已经是最新的,因为在那之后你再也不会写信给它(不要介意它的内容在实际屏幕上被覆盖了)。

解决方案是在开始绘制之前在顶部显式调用refresh();或者,我更喜欢在不同的窗口(以最合适的方式)上调用wgetch(),而不是getch(),并完全忽略stdscr的存在。请记住,所有不允许指定窗口的函数 - getch()refresh()等 - 实际上是对它们的"w"等价物的调用,stdscr作为隐式窗口参数。

默认情况下,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()是非阻塞的,那么这样做的方法是在默认窗口中为 nodelay 模式设置 curses。

来自 pdcurses 文档:

随着getch()wgetch()mvgetch()mvwgetch() 函数,从与 窗。在 nodelay 模式下,如果没有输入等待,则值ERR 被返回。在延迟模式下,程序将挂起,直到系统挂起 将文本传递到程序。

所以打电话:

nodelay(stdscr, TRUE);

如果您希望getch()是非阻塞的;如果没有按下任何键,它将返回ERR

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 的使用变得"无用",除了您仍然可以在控制台中制作一些快速图形的事实,这可以通过一点技巧制作而无需使用任何库。如果你在该菜单中制作一个小动画,比如由键盘驱动的移动光标等等,你就会明白我的意思。基本上,诅咒大多只是因为它的延迟功能而被使用,使事情在控制台中看起来更自然,因此它们不会闪烁,尤其是在涉及通过重复循环生成的细节/动画时。