Search code examples
c++windowswinapibuttonwindow

Selecting button in winapi causes other functionality to break


I am writing an app with the Win32 API. The layout basically consists of a left window that never changes and a right window that changes between two windows. To control this change, I am using ShowWindow(hWnd, SW_SHOW/SW_HIDE).

In the left window, there is a button that reveals one window and hides the other. I also have keyboard inputs that show and hide the window.

The keyboard functionality works fine until I click the button. Once the button is clicked, the keys stop working.

I thought at first this was because the button is a different window; however, when I try to catch the key input in the child window, nothing happens.

Here is a minimum reproducible example that has just a button and a child window. Note that clicking Shift/Tab causes the child window to appear/disappear. Clicking the button causes the child to disappear once, but it also seemingly disables the key inputs. Even clicking outside of the button and child does not restore them.

What could the problem be?

//libraries
#pragma comment ("lib", "Comctl32.lib")
#pragma comment ("lib", "d2d1.lib")


#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>
// C RunTime Header Files

#include <vector>
#include <string>


#define IDS_APP_TITLE           103
#define IDI_PRACTICE            107
#define IDI_SMALL               108
#define IDC_PRACTICE            109

#define IDC_BUTTON              101
#define MAX_LOADSTRING          100




// Global Variables:
HINSTANCE hInst;                                // current instance
WCHAR szTitle[MAX_LOADSTRING];                  // The title bar text
WCHAR szWindowClass[MAX_LOADSTRING];            // the main window class name




LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);
LRESULT CALLBACK WndProcChild(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam);

ATOM MyRegisterClass(HINSTANCE hInstance);

HWND childHWND;

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow);

int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);
    // Initialize global strings
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_PRACTICE, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    SetProcessDpiAwarenessContext(DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2);

    // Perform application initialization:
    if (!InitInstance(hInstance, nCmdShow))
    {
        return FALSE;
    }
    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_PRACTICE));
    MSG msg;
    // Main message loop:
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return (int)msg.wParam;
}


LRESULT CALLBACK WndProcChild(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    return DefWindowProc(hWnd, message, wParam, lParam);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{  
    switch (message)
    {
    case WM_CREATE:
    {
        HWND button = CreateWindowW(L"button", L"Hide", WS_CHILD | WS_VISIBLE, 100, 100, 200, 100, hWnd, (HMENU)IDC_BUTTON, hInst, nullptr);
        break;
    }
    case WM_COMMAND:
    {
        int wmld = LOWORD(wParam);

        switch (wmld)
        {
        case IDC_BUTTON:
            ShowWindow(childHWND, SW_HIDE);
            break;
        }
        break;
    }
    case WM_DESTROY:
        PostQuitMessage(0);
        break;

    case WM_KEYDOWN:
        switch (wParam)
        {
        case VK_SHIFT:
            ShowWindow(childHWND, SW_HIDE);

            break;
        case VK_TAB:
            ShowWindow(childHWND, SW_SHOW);

            break;
        }
        break;

    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
    
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEXW wcex;
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.style = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc = WndProc;
    wcex.cbClsExtra = 0;
    wcex.cbWndExtra = 0;
    wcex.hInstance = hInstance;
    wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_PRACTICE));
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(GetStockObject(WHITE_BRUSH));
    wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_PRACTICE);
    wcex.lpszClassName = L"Parent";
    wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
    

    //Child wnd class
    WNDCLASSEXW wcexChild;
    wcexChild.cbSize = sizeof(WNDCLASSEX);
    wcexChild.style = CS_HREDRAW | CS_VREDRAW;
    wcexChild.lpfnWndProc = WndProcChild;
    wcexChild.cbClsExtra = 0;
    wcexChild.cbWndExtra = 0;
    wcexChild.hInstance = hInstance;
    wcexChild.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_PRACTICE));
    wcexChild.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcexChild.hbrBackground = (HBRUSH)(GetStockObject(BLACK_BRUSH));
    wcexChild.lpszMenuName = MAKEINTRESOURCEW(IDC_PRACTICE);
    wcexChild.lpszClassName = L"Child";
    wcexChild.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
    return RegisterClassExW(&wcexChild) && RegisterClassExW(&wcex);
}


BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    hInst = hInstance; // Store instance handle in our global variable
    HWND hWnd = CreateWindowW(L"Parent", L"PARENT", WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, nullptr, nullptr, hInstance, nullptr);

    childHWND = CreateWindowW(L"Child", szTitle, WS_CHILD | WS_VISIBLE,
        500, 500, 500, 500, hWnd, nullptr, hInstance, nullptr);

    if (!hWnd)
    {
        return FALSE;
    }
    ShowWindow(childHWND, nCmdShow);
    UpdateWindow(childHWND);

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    return TRUE;
}

Solution

  • When you press a button, the focus will be on that button, and only the main window handles keyboard keystrokes.

    You have a simple way to adjust the focused window by calling SetFocus:

        case IDC_BUTTON:
            ShowWindow(childHWND, SW_HIDE);
            SetFocus(hWnd);
            break;
    

    Of course, as Remy said, you can subclass the button by SetWindowSubclass, and then process the keyboard message by processing the keyboard message, or modify the focus and other methods.