Search code examples
cwinapievent-handling

Win32 API (C): Window permanently disappearing at runtime after changing focus?


I am creating a windowed application, and I am currently working on a Windows platform layer in C. I want the application update loop to be suspended when its window is not in focus, and not suspended otherwise.

The way I currently handle events works fine for handling the [X] (close) icon and the maximize icon (i.e. resizing the window). However, whenever I click the [-] (minimize) icon at the top right of the application window that Windows spawns, the window disappears forever, and I cannot get it to reappear or get focus back to it to quit via key-bindings; I have to kill the application from the command line instead. The same happens any time the window changes focus to the background; it disappears and cannot be brought into focus again.

All my other event code that is unrelated to window focus changing works fine: key events, mouse events, window close/resize, etc.

Minimal reproducible example:

#include <stdio.h>
#include <string.h>
#include <windows.h>

typedef _Bool bool;
#define true    ( ( bool ) 1 )
#define false   ( ( bool ) 0 )

bool suspended;
bool running;

void
platform_update
( void )
{
    MSG message;
    while ( PeekMessageA ( &message , 0 , 0 , 0 , PM_REMOVE ) )
    {
        TranslateMessage ( &message );
        DispatchMessageA ( &message );
    }
}

LRESULT CALLBACK
platform_process_message
(   HWND    window
,   UINT    message
,   WPARAM  w_param
,   LPARAM  l_param
)
{
    switch ( message )
    {
        case WM_CLOSE:
        {
            printf ( "Quitting application.\n" );
            running = false;
            return 0;
        }

        case WM_DESTROY:
        {
            PostQuitMessage ( 0 );
            return 0;
        }

        case WM_ACTIVATE:
        {
            if ( running )
            {
                if ( LOWORD ( w_param ) == WA_INACTIVE )
                {
                    printf ( "Suspending application.\n" );
                    suspended = true;
                }
                else
                {
                    printf ( "Resuming application.\n" );
                    suspended = false;
                }
            }
        }
        break;

        case WM_SIZE:
        {
            printf ( "Resized application window.\n" );
        }
        break;
    }
    return DefWindowProcA ( window , message , w_param , l_param );
}

int
main
( void )
{
    HINSTANCE h_instance = GetModuleHandleA ( 0 );

    // Register window class.
    WNDCLASSA class;
    memset ( &class , 0 , sizeof ( class ) );
    class.style = CS_DBLCLKS;
    class.lpfnWndProc = platform_process_message;
    class.cbClsExtra = 0;
    class.cbWndExtra = 0;
    class.hInstance = h_instance;
    class.hIcon = LoadIcon ( h_instance, IDI_APPLICATION );
    class.hCursor = LoadCursor ( 0 , IDC_ARROW );
    class.hbrBackground = 0;
    class.lpszClassName = "test_window";
    if ( !RegisterClassA ( &class ) )
    {
        printf ( "Error: RegisterClassA failed.\n" );
        return 1;
    }

    // Initialize window dimensions.
    int window_x = 100;
    int window_y = 100;
    int window_width = 200;
    int window_height = 200;

    // Initialize window style.
    int window_style = WS_OVERLAPPED | WS_SYSMENU | WS_CAPTION
                     | WS_MAXIMIZEBOX | WS_MINIMIZEBOX | WS_THICKFRAME
                     ;
    int window_ex_style = WS_EX_APPWINDOW;
    RECT border = { 0 , 0 , 0 , 0 };
    AdjustWindowRectEx ( &border , window_style , 0 , window_ex_style );
    window_x += border.left;
    window_y += border.top;
    window_width += border.right - border.left;
    window_height += border.bottom - border.top;

    // Create the window.
    HWND window = CreateWindowExA ( window_ex_style
                                  , class.lpszClassName
                                  , "Test window"
                                  , window_style
                                  , window_x
                                  , window_y
                                  , window_width
                                  , window_height
                                  , 0
                                  , 0
                                  , h_instance
                                  , 0
                                  );
    if ( !window )
    {
        printf ( "Error: CreateWindowExA failed.\n" );
        return 2;
    }

    // Display the window.
    ShowWindow ( window , SW_SHOW );

    // Update loop.
    running = true;
    suspended = false;
    while ( running )
    {
        if ( suspended )
        {
            continue;
        }
        platform_update ();
        Sleep ( 16 );// Simulates other stuff being done here.
    }
    running = false;

    // Destroy window.
    if ( !DestroyWindow ( window ) )
    {
        printf ( "Error: DestroyWindow failed." );
        return 3;
    }

    return 0;
}

Solution

  • When the window becomes inactive, you set suspended to true, which stops your main loop from calling platform_update() ever again, thus your window stops receiving messages and can't behave properly from that point on.

    You need to continue processing window messages, just suspend the rest of your code instead, eg:

    while ( running )
    {
        platform_update (); // <-- move here
        if ( suspended )
        {
            continue;
        }
        Sleep ( 16 );// Simulates other stuff being done here.
    }
    

    Do note, however, that if there are no messages to process, and no work to perform, your loop will become unyielding and eat up CPU cycles like crazy. You should put the thread to sleep when there is nothing to do. For instance, if platform_update() doesn't receive a message, and suspended is true, then call Sleep(0) to yield the CPU before going to the next loop iteration, eg:

    bool
    platform_update
    ( void )
    {
        MSG message;
        if ( !PeekMessageA ( &message , 0 , 0 , 0 , PM_REMOVE ) )
        {
            return false;
        }
        do
        {
            TranslateMessage ( &message );
            DispatchMessageA ( &message );
        }
        while ( PeekMessageA ( &message , 0 , 0 , 0 , PM_REMOVE ) );
        return true;
    }
    
    ...
    
        while ( running )
        {
            bool got_msg = platform_update ();
            if ( suspended )
            {
                if ( !got_msg )
                {
                    Sleep(0); // <-- add this
                }
                continue;
            }
            Sleep ( 16 );// Simulates other stuff being done here.
        }