Why is the size of the client area for Tab Control received seem a little bit too large?

Cristoforo Montanari 0 Reputation points
2025-04-24T00:00:08.0266667+00:00
#include "framework.h"
#include "Win32-Desktop-Tab-Overlay.h"
#include <commctrl.h>
#include <iostream>
#pragma comment(lib, "comctl32.lib")
#define ID_TABCTRL 100
#define MAX_LOADSTRING 100
HINSTANCE hInst;
HWND hWnd;
RECT hWnd_Rect;
HWND hwndTabMain;
HWND hwndDrawArea;
HWND hwndOverlay; // Added for overlay window
WCHAR szTitle[MAX_LOADSTRING];
WCHAR szWindowClass[MAX_LOADSTRING];
ATOM                RegisterWindowClasses(HINSTANCE hInstance);
BOOL                InitInstance(HINSTANCE, int);
LRESULT CALLBACK    WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK    About(HWND, UINT, WPARAM, LPARAM);
void AllocateConsole();
void AllocateConsole() {
    // Allocate a console window
    AllocConsole();
    // Redirect standard output to the console
    FILE* fpOut;
    freopen_s(&fpOut, "CONOUT$", "w", stdout);
    // Redirect standard input to the console
    FILE* fpIn;
    freopen_s(&fpIn, "CONIN$", "r", stdin);
    // Redirect standard error to the console
    FILE* fpErr;
    freopen_s(&fpErr, "CONOUT$", "w", stderr);
    // Test output to the console
    std::cout << "Console window initialized successfully!" << std::endl;
}
// Window procedure for the drawing area
LRESULT CALLBACK DrawAreaProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
    case WM_PAINT: {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);
        RECT rect;
        GetClientRect(hwnd, &rect);
        HBRUSH whiteBrush = (HBRUSH)GetStockObject(WHITE_BRUSH);
        FillRect(hdc, &rect, whiteBrush);
        HPEN blackPen = (HPEN)GetStockObject(BLACK_PEN);
        HPEN oldPen = (HPEN)SelectObject(hdc, blackPen);
        MoveToEx(hdc, 0, 0, NULL);
        LineTo(hdc, rect.right, rect.bottom);
        SelectObject(hdc, oldPen);
        EndPaint(hwnd, &ps);
        return 0;
    }
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}
LRESULT CALLBACK OverlayProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) {
    switch (msg) {
    case WM_PAINT: {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hwnd, &ps);
        RECT rect;
        GetClientRect(hwnd, &rect);
        // Fill the background with black
        HBRUSH keyBrush = CreateSolidBrush(RGB(0, 0, 0));
        FillRect(hdc, &rect, keyBrush);
        DeleteObject(keyBrush);
        // Calculate the center of the window
        int cx = (rect.left + rect.right) / 2;
        int cy = (rect.top + rect.bottom) / 2;
        // Create a red pen for drawing the cross
        HPEN redPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
        HPEN oldPen = (HPEN)SelectObject(hdc, redPen);
        // Draw the cross
        // Line from top-left to bottom-right
        MoveToEx(hdc, rect.left, rect.top, NULL);
        LineTo(hdc, rect.right, rect.bottom);
        // Line from top-right to bottom-left
        MoveToEx(hdc, rect.right, rect.top, NULL);
        LineTo(hdc, rect.left, rect.bottom);
        // Restore the old pen and delete the red pen
        SelectObject(hdc, oldPen);
        DeleteObject(redPen);
        EndPaint(hwnd, &ps);
        return 0;
    }
    }
    return DefWindowProc(hwnd, msg, wParam, lParam);
}
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_WIN32DESKTOPTABOVERLAY, szWindowClass, MAX_LOADSTRING);
    RegisterWindowClasses(hInstance);
    AllocateConsole();
    if (!InitInstance(hInstance, nCmdShow))
    {
        return FALSE;
    }
    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WIN32DESKTOPTABOVERLAYC));
    MSG msg;
    while (GetMessage(&msg, nullptr, 0, 0))
    {
        if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
        {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    }
    return (int)msg.wParam;
}
ATOM RegisterWindowClasses(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_WIN32DESKTOPTABOVERLAY));
    wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
    wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WIN32DESKTOPTABOVERLAY);
    wcex.lpszClassName = szWindowClass;
    wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
    RegisterClassExW(&wcex);
    // Register DrawArea class
    WNDCLASS wcDrawingArea = { 0 };
    wcDrawingArea.lpfnWndProc = DrawAreaProc;
    wcDrawingArea.hInstance = hInstance;
    wcDrawingArea.lpszClassName = L"DrawArea";
    RegisterClass(&wcDrawingArea);
    WNDCLASSEX wcOverlay = { 0 };
    wcOverlay.cbSize = sizeof(WNDCLASSEX);
    wcOverlay.lpfnWndProc = OverlayProc;
    wcOverlay.hInstance = hInstance;
    wcOverlay.lpszClassName = L"OverlayWindow";
    wcOverlay.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);
    wcOverlay.style = CS_HREDRAW | CS_VREDRAW;
    wcOverlay.hCursor = LoadCursor(nullptr, IDC_ARROW);
    return RegisterClassEx(&wcOverlay);
}
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    hInst = hInstance;
    hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
    if (!hWnd)
    {
        return FALSE;
    }
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
    // Get the initial size and position of the main window
    hWnd_Rect;
    GetWindowRect(hWnd, &hWnd_Rect);
    // Print the size and position to the console
    std::cout << "Main Window Initial Position and Size:" << std::endl;
    std::cout << "Left: " << hWnd_Rect.left << ", Top: " << hWnd_Rect.top << std::endl;
    std::cout << "Right: " << hWnd_Rect.right << ", Bottom: " << hWnd_Rect.bottom << std::endl;
    std::cout << "Width: " << (hWnd_Rect.right - hWnd_Rect.left) << ", Height: " << (hWnd_Rect.bottom - hWnd_Rect.top) << std::endl;
    return TRUE;
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_CREATE: {
        INITCOMMONCONTROLSEX icex = { sizeof(INITCOMMONCONTROLSEX), ICC_TAB_CLASSES };
        InitCommonControlsEx(&icex);
        hwndTabMain = CreateWindowEx(0, WC_TABCONTROL, L"",
            WS_CHILD | WS_VISIBLE,
            0, 0, 0, 0, hWnd, (HMENU)ID_TABCTRL, hInst, NULL);
        TCITEM tcItem1 = { TCIF_TEXT, 0, 0, (LPWSTR)L"Tab 1" };
        TabCtrl_InsertItem(hwndTabMain, 0, &tcItem1);
        hwndDrawArea = CreateWindowEx(0, L"DrawArea", L"",
            WS_CHILD | WS_VISIBLE,
            0, 0, 0, 0, hWnd, NULL, hInst, NULL);
        hwndOverlay = CreateWindowEx(
            WS_EX_LAYERED,                // Extended style for layered window
            L"OverlayWindow",             // Class name
            L"",                          // Window name
            WS_POPUP | WS_VISIBLE,        // Use WS_POPUP instead of WS_CHILD
            0, 0, 0, 0,                   // Position and size
            NULL,                         // No parent window
            NULL,                         // No menu
            hInst,                        // Instance handle
            NULL                          // No additional parameters
        );
        SetLayeredWindowAttributes(hwndOverlay, RGB(0, 0, 0), 0, LWA_COLORKEY);
        // Get the position and size of hwndDrawArea
        RECT drawAreaRect;
        GetWindowRect(hwndDrawArea, &drawAreaRect);
        // Convert screen coordinates to client coordinates relative to the parent window
        POINT topLeft = { drawAreaRect.left + 600, drawAreaRect.top + 600 };
        POINT bottomRight = { drawAreaRect.right, drawAreaRect.bottom };
        ScreenToClient(hWnd, &topLeft);
        ScreenToClient(hWnd, &bottomRight);
        // Set the position and size of hwndOverlay
        SetWindowPos(hwndOverlay, HWND_TOP,
            topLeft.x, topLeft.y,
            bottomRight.x - topLeft.x, bottomRight.y - topLeft.y,
            SWP_NOACTIVATE);
        SendMessage(hWnd, WM_SIZE, 0, 0);
        return 0;
    }
    case WM_SIZE: {
        std::cout << "WndProc:WM_SIZE:" << std::endl;
        RECT rect;
        GetClientRect(hWnd, &rect);
        MoveWindow(hwndTabMain, 0, 0, rect.right, rect.bottom, TRUE);
        // Get tab control's window and client rectangles
        RECT tabWindowRect, tabClientRect;
        GetWindowRect(hwndTabMain, &tabWindowRect); // Full window including borders
        GetClientRect(hwndTabMain, &tabClientRect); // Client area excluding borders
        // Convert client rectangle to screen coordinates
        POINT clientTL = { tabClientRect.left, tabClientRect.top }; // Usually (0, 0)
        POINT clientBR = { tabClientRect.right, tabClientRect.bottom };
        ClientToScreen(hwndTabMain, &clientTL);
        ClientToScreen(hwndTabMain, &clientBR);
        // Debug: Print raw rectangles
        std::cout << "Tab Window Rect: (" << tabWindowRect.left << ", " << tabWindowRect.top << ", "
            << tabWindowRect.right << ", " << tabWindowRect.bottom << ")" << std::endl;
        std::cout << "Tab Client Rect (Screen): (" << clientTL.x << ", " << clientTL.y << ", "
            << clientBR.x << ", " << clientBR.y << ")" << std::endl;
        // Calculate border widths
        int borderLeft = clientTL.x - tabWindowRect.left;
        int borderTop = clientTL.y - tabWindowRect.top;
        int borderRight = tabWindowRect.right - clientBR.x;
        int borderBottom = tabWindowRect.bottom - clientBR.y;
        std::cout << "Tab Control Borders: Left=" << borderLeft << ", Top=" << borderTop
            << ", Right=" << borderRight << ", Bottom=" << borderBottom << std::endl;
        // Use window rectangle for overlay positioning (includes borders)
        RECT tabRectSC = tabWindowRect; // Already in screen coordinates
        // Get tab height for content area adjustment
        RECT tabItemRect;
        SendMessage(hwndTabMain, TCM_GETITEMRECT, 0, (LPARAM)&tabItemRect);
        int tabHeight = tabItemRect.bottom - tabItemRect.top;
        // Adjust top to cover content area only (below tabs)
        tabRectSC.top += tabHeight + borderTop;
        // Size the drawing area
        RECT displayRect1 = { 0, 0, rect.right, rect.bottom };
        TabCtrl_AdjustRect(hwndTabMain, FALSE, &displayRect1);
        MoveWindow(hwndDrawArea, displayRect1.left, displayRect1.top,
            displayRect1.right - displayRect1.left, displayRect1.bottom - displayRect1.top, TRUE);
        // Size and position the overlay
        int overlayWidth = tabRectSC.right - tabRectSC.left;
        int overlayHeight = tabRectSC.bottom - tabRectSC.top;
        SetWindowPos(hwndOverlay, HWND_TOPMOST,
            tabRectSC.left, tabRectSC.top,
            overlayWidth, overlayHeight, SWP_SHOWWINDOW);
        return 0;
    }
    case WM_COMMAND:
    {
        int wmId = LOWORD(wParam);
        switch (wmId)
        {
        case IDM_ABOUT:
            DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
            break;
        case IDM_EXIT:
            DestroyWindow(hWnd);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
    }
    break;
    case WM_PAINT:
    {
        PAINTSTRUCT ps;
        HDC hdc = BeginPaint(hWnd, &ps);
        EndPaint(hWnd, &ps);
    }
    break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
    }
    return 0;
}
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (message)
    {
    case WM_INITDIALOG:
        return (INT_PTR)TRUE;
    case WM_COMMAND:
        if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
        {
            EndDialog(hDlg, LOWORD(wParam));
            return (INT_PTR)TRUE;
        }
        break;
    }
    return (INT_PTR)FALSE;
}

Tab_RedX

`
Console window initialized successfully!

WndProc:WM_SIZE:

Tab Window Rect: (138, 181, 1562, 965)

Tab Client Rect (Screen): (138, 181, 1562, 965)

Tab Control Borders: Left=0, Top=0, Right=0, Bottom=0

WndProc:WM_SIZE:

Tab Window Rect: (138, 181, 1562, 965)

Tab Client Rect (Screen): (138, 181, 1562, 965)

Tab Control Borders: Left=0, Top=0, Right=0, Bottom=0

Main Window Initial Position and Size:

Left: 130, Top: 130

Right: 1570, Bottom: 973

Width: 1440, Height: 843
`` ` ```

Although I was able to position and size the overlay window in a way that is acceptable to start to use it mostly, the actual client area received by the system seems to be slightly too large. I tried to determine if there is a border to account for. So far the border is being received by the system as 0. Is there a border issue here? Could it be explained if there is an exact number of pixels of padding here to account for that I can call out from something like system metrics function?

As I dug into this a bit deeper I found that in terms of mouse clicks that the two windows seem to be 4 pixels apart from one another. What I am trying to determine is why that is.

C++
C++
A high-level, general-purpose programming language, created as an extension of the C programming language, that has object-oriented, generic, and functional features in addition to facilities for low-level memory manipulation.
3,913 questions
0 comments No comments
{count} votes

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.