Why is the size of the client area for Tab Control received seem a little bit too large?
#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;
}
`
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.