Multiple background threads calling SetForegroundWindow() act as if they are all foreground threads at the same time

141 views Asked by At

Why does calling SetForegroundWindow() in multiple background threads act as if they are all foreground threads at the same time?

In my understanding, calling SetForegroundWindow() after creating a window in a background thread will set the calling thread as the foreground thread. However, whenever another thread calls SetForegroundWindow(), then that thread becomes the foreground thread, and the previous foreground thread returns to the background.

I've tested 3 cases by clicking the title bar of a window and dragging it.

  • Case 1 : A process with a single threaded message loop with multiple windows
  • Case 2 : A process with multiple threaded message loops with one window per thread
  • Case 3 : A process with multiple threaded message loop with one window per thread calling SetForegroundWindow() after creating each window

The results of each cases were:

  • Case 1 : Any window is draggable with a single click.
  • Case 2 : Any background window is set the the foreground but is not draggable with a click. I have to click twice on the window to perform dragging.
  • Case 3 : Any window is draggable with a single click.

The reason why I express "as if they are all foreground threads" in my question is that case 3 should act the same as case 2 unless the clicked window is already in the foreground.

References I looked at are as follows:

[EDIT]

Here's the sample code that reproduces Case 2. If SetForegroundWindow is called for each windows in each thread, Case 3 is reproduced. Case 1 can be reproduced if SINGLETHREAD is defined.

#include <Windows.h>

HINSTANCE g_hInstance = nullptr;
HANDLE g_hEvent0 = nullptr;
HANDLE g_hEvent1 = nullptr;

//#define SINGLETHREAD

LRESULT WINAPI WndProc(HWND _hWnd, UINT _unMessage, WPARAM _wParam, LPARAM _lParam)
{
    switch (_unMessage)
    {
    case WM_DESTROY:
        {
            PostQuitMessage(0);
            return 0;
        }
    default:
        break;
    }
    
    return DefWindowProc(_hWnd, _unMessage, _wParam, _lParam);
}

DWORD WINAPI Thread0(LPVOID _lpParam)
{
    WNDCLASSEXW stWndClassExW0
    {
        sizeof(WNDCLASSEXW),
        CS_VREDRAW | CS_HREDRAW,
        &WndProc,
        0,
        0,
        g_hInstance,
        nullptr,
        static_cast<HICON__*>(LoadImageW(nullptr, static_cast<const wchar_t*>(IDC_ARROW), static_cast<unsigned int>(IMAGE_CURSOR), 0, 0, static_cast<unsigned int>(LR_SHARED))),
        static_cast<HBRUSH__*>(GetStockObject(WHITE_BRUSH)),
        L"MenuName",
        L"ClassName0",
        nullptr
    };

    ATOM usWindowClass0 = RegisterClassExW(&stWndClassExW0);

    HWND hWnd0 = CreateWindowExW(0,
                                 reinterpret_cast<const wchar_t*>(usWindowClass0),
                                 L"0",
                                 WS_OVERLAPPEDWINDOW,
                                 0,
                                 0,
                                 1200,
                                 800,
                                 nullptr,
                                 nullptr,
                                 g_hInstance,
                                 nullptr);

    ShowWindow(hWnd0, SW_SHOW);

    //SetForegroundWindow(hWnd0);

    MSG msg{};

    while (true)
    {
        if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE) != 0)
        {
            if (msg.message == WM_QUIT)
            {
                break;
            }

            TranslateMessage(&msg);
            DispatchMessageW(&msg);
        }
    }

    SetEvent(g_hEvent0);

    return 0ul;
}

DWORD WINAPI Thread1(LPVOID _lpParam)
{
    WNDCLASSEXW stWndClassExW1
    {
        sizeof(WNDCLASSEXW),
        CS_VREDRAW | CS_HREDRAW,
        &WndProc,
        0,
        0,
        g_hInstance,
        nullptr,
        static_cast<HICON__*>(LoadImageW(nullptr, static_cast<const wchar_t*>(IDC_ARROW), static_cast<unsigned int>(IMAGE_CURSOR), 0, 0, static_cast<unsigned int>(LR_SHARED))),
        static_cast<HBRUSH__*>(GetStockObject(WHITE_BRUSH)),
        L"MenuName",
        L"ClassName1",
        nullptr
    };

    ATOM usWindowClass1 = RegisterClassExW(&stWndClassExW1);

    HWND hWnd1 = CreateWindowExW(0,
                                 reinterpret_cast<const wchar_t*>(usWindowClass1),
                                 L"1",
                                 WS_OVERLAPPEDWINDOW,
                                 200,
                                 200,
                                 1200,
                                 800,
                                 nullptr,
                                 nullptr,
                                 g_hInstance,
                                 nullptr);

    ShowWindow(hWnd1, SW_SHOW);

    //SetForegroundWindow(hWnd1);

    MSG msg{};

    while (true)
    {
        if (PeekMessageW(&msg, nullptr, 0, 0, PM_REMOVE) != 0)
        {
            if (msg.message == WM_QUIT)
            {
                break;
            }

            TranslateMessage(&msg);
            DispatchMessageW(&msg);
        }
    }

    SetEvent(g_hEvent1);

    return 0ul;
}

int WINAPI wWinMain(_In_ HINSTANCE _hInstance,
                    _In_opt_ HINSTANCE _hPrevInstance,
                    _In_ LPWSTR _lpCmdLine,
                    _In_ int _nCmdShow)
{
    g_hInstance = _hInstance;

#ifdef SINGLETHREAD

    WNDCLASSEXW stWndClassExW0
    {
        sizeof(WNDCLASSEXW),
        CS_VREDRAW | CS_HREDRAW,
        &WndProc,
        0,
        0,
        g_hInstance,
        nullptr,
        static_cast<HICON__*>(LoadImageW(nullptr, static_cast<const wchar_t*>(IDC_ARROW), static_cast<unsigned int>(IMAGE_CURSOR), 0, 0, static_cast<unsigned int>(LR_SHARED))),
        static_cast<HBRUSH__*>(GetStockObject(WHITE_BRUSH)),
        L"MenuName",
        L"ClassName0",
        nullptr
    };

    ATOM usWindowClass0 = RegisterClassExW(&stWndClassExW0);

    HWND hWnd0 = CreateWindowExW(0,
                                 reinterpret_cast<const wchar_t*>(usWindowClass0),
                                 L"0",
                                 WS_OVERLAPPEDWINDOW,
                                 0,
                                 0,
                                 1200,
                                 800,
                                 nullptr,
                                 nullptr,
                                 g_hInstance,
                                 nullptr);

    ShowWindow(hWnd0, SW_SHOW);

    WNDCLASSEXW stWndClassExW1
    {
        sizeof(WNDCLASSEXW),
        CS_VREDRAW | CS_HREDRAW,
        &WndProc,
        0,
        0,
        g_hInstance,
        nullptr,
        static_cast<HICON__*>(LoadImageW(nullptr, static_cast<const wchar_t*>(IDC_ARROW), static_cast<unsigned int>(IMAGE_CURSOR), 0, 0, static_cast<unsigned int>(LR_SHARED))),
        static_cast<HBRUSH__*>(GetStockObject(WHITE_BRUSH)),
        L"MenuName",
        L"ClassName1",
        nullptr
    };

    ATOM usWindowClass1 = RegisterClassExW(&stWndClassExW1);

    HWND hWnd1 = CreateWindowExW(0,
                                 reinterpret_cast<const wchar_t*>(usWindowClass1),
                                 L"1",
                                 WS_OVERLAPPEDWINDOW,
                                 200,
                                 200,
                                 1200,
                                 800,
                                 nullptr,
                                 nullptr,
                                 g_hInstance,
                                 nullptr);

    ShowWindow(hWnd1, SW_SHOW);

    tagMSG stMSG{};

    while (true)
    {
        if (PeekMessageW(&stMSG, nullptr, 0, 0, static_cast<unsigned int>(PM_REMOVE)) != 0)
        {
            if (stMSG.message == static_cast<unsigned int>(WM_QUIT))
            {
                break;
            }

            TranslateMessage(&stMSG);
            DispatchMessageW(&stMSG);
        }
    }
#else

    g_hEvent0 = CreateEvent(nullptr, 0, 0, nullptr);
    g_hEvent1 = CreateEvent(nullptr, 0, 0, nullptr);

    DWORD dwThreadID0 = 0ul;
    DWORD dwThreadID1 = 0ul;

    HANDLE hThread0 = CreateThread(nullptr, 0, &Thread0, nullptr, 0, &dwThreadID0);
    HANDLE hThread1 = CreateThread(nullptr, 0, &Thread1, nullptr, 0, &dwThreadID1);

    WaitForSingleObject(g_hEvent0, INFINITE);
    WaitForSingleObject(g_hEvent1, INFINITE);

#endif // SINGLETHREAD

    return 0;
}
0

There are 0 answers