Search code examples
cwinapimenu

How to ignore "key-up events" while using ReadConsoleInput


There is my code, a simple console program that consists of menu() function for now.

void menu() {
    int k = 0;

    HANDLE s_in = GetStdHandle(STD_INPUT_HANDLE);
    DWORD dr;
    INPUT_RECORD  rec;

    printf("Choose a num\n");
    char vars[] = { '1. A', '2. B', '3. C' };

    while (true)
    {
        for (int i = 0; i < sizeof(vars) / sizeof(vars[0]); i++) {
            if (k == i) printf("%c [*]\n", vars[i]);
            else printf("%c\n", vars[i]);
        }

        ReadConsoleInput(s_in, &rec, sizeof(INPUT_RECORD), &dr);
        FlushConsoleInputBuffer(s_in);
        
        if (rec.EventType == KEY_EVENT) {
            if (rec.Event.KeyEvent.bKeyDown) {
                switch (rec.Event.KeyEvent.wVirtualKeyCode) {
                case VK_UP:
                    if (k > 0) k--;
                    break;
                case VK_DOWN:
                    if (k < ((sizeof(vars) / sizeof(vars[0])) - 1)) k++;
                    break;
                default:
                    printf("Use UP or DOWN arrows\n");
                    break;
                }
            }
        }
        
    }

 
   
}

int main()
{
    menu();
}

Unfortunately, there is one problem that ruins everything. I'm using <windows.h> to read keyboard buttons from console, but ReadConsoleInput() function catches all states of button (pressed and released), so I'm getting double printed vars[] every every time I press a key (up and down arrows in this case). How can "key-ups" be ignored, and only "key-downs" can be catched?


Solution

  • Every time ReadConsoleInput returns, you go through the if and then go back to the beginning of the loop and print the "menu" (vars). ReadConsoleInput returns whenever a button is pressed or released. You can even see that pressing a button that isn't VK_UP or VK_DOWN will print "Use UP or DOWN arrows\n", but then releasing that button will not print this line.

    A simple solution is to wrap ReadConsoleInput in a loop to filter out the events you don't want.

    do {
        ReadConsoleInput(s_in, &rec, 1, &dr);
        FlushConsoleInputBuffer(s_in);
    } while (rec.EventType == KEY_EVENT && !rec.Event.KeyEvent.bKeyDown);
    

    Probably a better solution is to move the menu printing outside of the loop and repeat it at the end of the keydown processing:

    for (int i = 0; i < sizeof(vars) / sizeof(vars[0]); i++) {
        if (k == i) printf("%c [*]\n", vars[i]);
        else printf("%c\n", vars[i]);
    }
    
    while (true)
    {
        ReadConsoleInput(s_in, &rec, 1, &dr);
    
        if (rec.EventType == KEY_EVENT) {
            if (rec.Event.KeyEvent.bKeyDown) {
                switch (rec.Event.KeyEvent.wVirtualKeyCode) {
                case VK_UP:
                    if (k > 0) k--;
                    break;
                case VK_DOWN:
                    if (k < ((sizeof(vars) / sizeof(vars[0])) - 1)) k++;
                    break;
                default:
                    printf("Use UP or DOWN arrows\n");
                    break;
                }
    
                for (int i = 0; i < sizeof(vars) / sizeof(vars[0]); i++) {
                    if (k == i) printf("%c [*]\n", vars[i]);
                    else printf("%c\n", vars[i]);
                }
            }
        }
    }
    

    The menu printing should really be a function at this point, so you don't have to repeat these lines of code. Note that this also eliminates the need for FlushConsoleInputBuffer.