// Win32-Desktop-Tab-Overlay-Splitter-RichEdit.cpp : Defines the entry point for the application.
//
#include "framework.h"
#include <commctrl.h>
#include <iostream>
#include "resource.h"
#pragma comment(lib, "comctl32.lib")
#define ID_TABCTRL 100
#define ID_SPLITTER 101
#define MAX_LOADSTRING 100
HINSTANCE hInst;
HWND hWnd;
RECT hWnd_Rect;
HWND hwndTabMain;
HWND hwndDrawArea;
HWND hwndOverlay;
HWND hSplitter;
HWND hRichEdit;
int splitterPos = 200; // Initial splitter position
static WNDPROC OldSplitterProc;
static bool isDragging = false;
WCHAR szTitle[MAX_LOADSTRING];
WCHAR szWindowClass[MAX_LOADSTRING];
ATOM RegisterWindowClasses(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK SplitterProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
void AllocateConsole();
void UpdateOverlayPosition(HWND hWnd, HWND hwndTabMain, HWND hwndOverlay);
void AllocateConsole() {
AllocConsole();
FILE* fpOut;
freopen_s(&fpOut, "CONOUT$", "w", stdout);
FILE* fpIn;
freopen_s(&fpIn, "CONIN$", "r", stdin);
FILE* fpErr;
freopen_s(&fpErr, "CONOUT$", "w", stderr);
std::cout << "Console window initialized successfully!" << std::endl;
}
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;
}
case WM_LBUTTONDOWN: {
int x = LOWORD(lParam);
int y = HIWORD(lParam);
std::cout << "DrawArea Left Click at: (" << x << ", " << y << ")" << std::endl;
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);
HPEN redPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
HPEN oldPen = (HPEN)SelectObject(hdc, redPen);
MoveToEx(hdc, rect.left, rect.top, NULL);
LineTo(hdc, rect.right, rect.bottom);
MoveToEx(hdc, rect.right, rect.top, NULL);
LineTo(hdc, rect.left, rect.bottom);
MoveToEx(hdc, rect.left, rect.top, NULL);
LineTo(hdc, rect.right, rect.top);
MoveToEx(hdc, rect.right, rect.top, NULL);
LineTo(hdc, rect.right, rect.bottom);
MoveToEx(hdc, rect.right, rect.bottom, NULL);
LineTo(hdc, rect.left, rect.bottom);
MoveToEx(hdc, rect.left, rect.bottom, NULL);
LineTo(hdc, rect.left, rect.top);
SelectObject(hdc, oldPen);
DeleteObject(redPen);
EndPaint(hwnd, &ps);
std::cout << "OverlayProc: WM_PAINT completed" << std::endl;
return 0;
}
case WM_LBUTTONDOWN: {
int x = LOWORD(lParam);
int y = HIWORD(lParam);
std::cout << "Overlay Left Click at: (" << x << ", " << y << ")" << std::endl;
POINT pt = { x, y };
ClientToScreen(hwnd, &pt);
ScreenToClient(hwndDrawArea, &pt);
pt.x -= 4;
pt.y -= 4;
if (pt.x < 0) pt.x = 0;
if (pt.y < 0) pt.y = 0;
std::cout << "Forwarding click to DrawArea at: (" << pt.x << ", " << pt.y << ")" << std::endl;
SendMessage(hwndDrawArea, WM_LBUTTONDOWN, wParam, MAKELPARAM(pt.x, pt.y));
return 0;
}
}
return DefWindowProc(hwnd, msg, wParam, lParam);
}
LRESULT CALLBACK SplitterProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) {
switch (msg) {
case WM_LBUTTONDOWN:
SetCapture(hWnd);
isDragging = true;
return 0;
case WM_MOUSEMOVE:
if (isDragging) {
POINT pt;
GetCursorPos(&pt);
ScreenToClient(GetParent(hWnd), &pt);
int newSplitterPos = pt.y;
RECT rect;
GetClientRect(GetParent(hWnd), &rect);
if (newSplitterPos < 100) newSplitterPos = 100;
if (newSplitterPos > rect.bottom - 100) newSplitterPos = rect.bottom - 100;
MoveWindow(hwndTabMain, 0, 0, rect.right, newSplitterPos, TRUE);
MoveWindow(hSplitter, 0, newSplitterPos, rect.right, 5, TRUE);
MoveWindow(hRichEdit, 0, newSplitterPos + 5, rect.right, rect.bottom - newSplitterPos - 5, TRUE);
RECT displayRect1 = { 0, 0, rect.right, newSplitterPos };
TabCtrl_AdjustRect(hwndTabMain, FALSE, &displayRect1);
MoveWindow(hwndDrawArea, displayRect1.left, displayRect1.top,
displayRect1.right - displayRect1.left, displayRect1.bottom - displayRect1.top, TRUE);
UpdateOverlayPosition(GetParent(hWnd), hwndTabMain, hwndOverlay);
splitterPos = newSplitterPos;
// Invalidate draw area and overlay to force repaint
InvalidateRect(hwndDrawArea, NULL, TRUE);
InvalidateRect(hwndOverlay, NULL, TRUE);
}
return 0;
case WM_LBUTTONUP:
ReleaseCapture();
isDragging = false;
return 0;
case WM_SETCURSOR:
SetCursor(LoadCursor(NULL, IDC_SIZENS));
return TRUE;
case WM_PAINT: {
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
RECT rc;
GetClientRect(hWnd, &rc);
FillRect(hdc, &rc, (HBRUSH)GetStockObject(BLACK_BRUSH));
EndPaint(hWnd, &ps);
return 0;
}
}
return CallWindowProc(OldSplitterProc, hWnd, msg, wParam, lParam);
}
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_WIN32DESKTOPSPLITTERTABRICHEDITOVERLAYMOUSECLICKS, szWindowClass, MAX_LOADSTRING);
RegisterWindowClasses(hInstance);
AllocateConsole();
if (!InitInstance(hInstance, nCmdShow))
{
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WIN32DESKTOPSPLITTERTABRICHEDITOVERLAYMOUSECLICKS));
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(IDC_WIN32DESKTOPSPLITTERTABRICHEDITOVERLAYMOUSECLICKS));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_WIN32DESKTOPSPLITTERTABRICHEDITOVERLAYMOUSECLICKS);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
RegisterClassExW(&wcex);
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 = NULL;
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);
GetWindowRect(hWnd, &hWnd_Rect);
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;
}
void UpdateOverlayPosition(HWND hWnd, HWND hwndTabMain, HWND hwndOverlay) {
// Get tab control dimensions
RECT tabWindowRect;
GetWindowRect(hwndTabMain, &tabWindowRect);
RECT tabItemRect;
SendMessage(hwndTabMain, TCM_GETITEMRECT, 0, (LPARAM)&tabItemRect);
int tabHeight = tabItemRect.bottom - tabItemRect.top;
RECT tabClientRect;
GetClientRect(hwndTabMain, &tabClientRect);
POINT clientTL = { tabClientRect.left, tabClientRect.top };
ClientToScreen(hwndTabMain, &clientTL);
int borderTop = clientTL.y - tabWindowRect.top;
RECT tabRectSC = tabWindowRect;
tabRectSC.top += tabHeight + borderTop;
int overlayWidth = tabRectSC.right - tabRectSC.left;
int overlayHeight = tabRectSC.bottom - tabRectSC.top;
// Reposition the overlay window
SetWindowPos(hwndOverlay, HWND_TOP, tabRectSC.left, tabRectSC.top, overlayWidth, overlayHeight, SWP_NOACTIVATE | SWP_SHOWWINDOW);
// Prepare device contexts
HDC hdcScreen = GetDC(NULL);
HDC hdcMem = CreateCompatibleDC(hdcScreen);
// Create a 32-bit bitmap with alpha channel
BITMAPINFO bmi = { 0 };
bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER);
bmi.bmiHeader.biWidth = overlayWidth;
bmi.bmiHeader.biHeight = -overlayHeight; // Top-down bitmap
bmi.bmiHeader.biPlanes = 1;
bmi.bmiHeader.biBitCount = 32; // 32-bit for ARGB
bmi.bmiHeader.biCompression = BI_RGB;
void* pBits;
HBITMAP hbmMem = CreateDIBSection(hdcScreen, &bmi, DIB_RGB_COLORS, &pBits, NULL, 0);
if (!hbmMem) {
ReleaseDC(NULL, hdcScreen);
DeleteDC(hdcMem);
return;
}
SelectObject(hdcMem, hbmMem);
// Cast pBits to access pixel data (ARGB format: AABBGGRR)
DWORD* pixels = (DWORD*)pBits;
for (int y = 0; y < overlayHeight; y++) {
for (int x = 0; x < overlayWidth; x++) {
pixels[y * overlayWidth + x] = 0x00000000; // Fully transparent (A=0, RGB=0)
}
}
// Draw the red 'X' with alpha = 255
HPEN redPen = CreatePen(PS_SOLID, 1, RGB(255, 0, 0));
HPEN oldPen = (HPEN)SelectObject(hdcMem, redPen);
MoveToEx(hdcMem, 0, 0, NULL);
LineTo(hdcMem, overlayWidth - 1, overlayHeight - 1);
MoveToEx(hdcMem, overlayWidth - 1, 0, NULL);
LineTo(hdcMem, 0, overlayHeight - 1);
SelectObject(hdcMem, oldPen);
DeleteObject(redPen);
// Ensure drawn pixels have alpha = 255 (optional, GDI might handle this)
// This step can be skipped if GDI sets alpha correctly for drawn lines
// For precision, you could use GetPixel/SetPixel or a scanline approach, but it's overkill here
// Update the layered window
BLENDFUNCTION bf = { AC_SRC_OVER, 0, 255, AC_SRC_ALPHA }; // Use per-pixel alpha
POINT ptDst = { tabRectSC.left, tabRectSC.top };
SIZE size = { overlayWidth, overlayHeight };
POINT ptSrc = { 0, 0 };
UpdateLayeredWindow(hwndOverlay, hdcScreen, &ptDst, &size, hdcMem, &ptSrc, 0, &bf, ULW_ALPHA);
// Clean up
DeleteObject(hbmMem);
DeleteDC(hdcMem);
ReleaseDC(NULL, hdcScreen);
std::cout << "UpdateOverlayPosition: Left=" << tabRectSC.left << ", Top=" << tabRectSC.top
<< ", Width=" << overlayWidth << ", Height=" << overlayHeight << std::endl;
}
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);
LoadLibrary(L"Msftedit.dll");
RECT rect;
GetClientRect(hWnd, &rect);
int clientWidth = rect.right;
int clientHeight = rect.bottom;
if (clientHeight == 0) clientHeight = 1;
splitterPos = clientHeight / 2;
hwndTabMain = CreateWindowEx(0, WC_TABCONTROL, L"",
WS_CHILD | WS_VISIBLE,
0, 0, clientWidth, splitterPos, hWnd, (HMENU)ID_TABCTRL, hInst, NULL);
TCITEM tcItem1 = { TCIF_TEXT, 0, 0, (LPWSTR)L"Tab 1" };
TabCtrl_InsertItem(hwndTabMain, 0, &tcItem1);
hSplitter = CreateWindowEx(0, L"STATIC", L"",
WS_CHILD | WS_VISIBLE | SS_NOTIFY,
0, splitterPos, clientWidth, 5, hWnd, (HMENU)ID_SPLITTER, hInst, NULL);
hRichEdit = CreateWindowEx(0, L"RICHEDIT50W", L"",
WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE,
0, splitterPos + 5, clientWidth, clientHeight - splitterPos - 5, hWnd, NULL, hInst, NULL);
hwndDrawArea = CreateWindowEx(0, L"DrawArea", L"",
WS_CHILD | WS_VISIBLE,
0, 0, 0, 0, hWnd, NULL, hInst, NULL);
hwndOverlay = CreateWindowEx(
WS_EX_LAYERED,
L"OverlayWindow", L"",
WS_POPUP | WS_VISIBLE,
0, 0, 0, 0,
hWnd,
NULL,
hInst,
NULL
);
SetLayeredWindowAttributes(hwndOverlay, 0, 128, LWA_ALPHA);
OldSplitterProc = (WNDPROC)SetWindowLongPtr(hSplitter, GWLP_WNDPROC, (LONG_PTR)SplitterProc);
SendMessage(hWnd, WM_SIZE, 0, 0);
return 0;
}
case WM_MOVE: {
std::cout << "WndProc:WM_MOVE: Main window moved to (" << LOWORD(lParam) << ", " << HIWORD(lParam) << ")" << std::endl;
UpdateOverlayPosition(hWnd, hwndTabMain, hwndOverlay);
return 0;
}
case WM_SIZE: {
std::cout << "WndProc:WM_SIZE:" << std::endl;
RECT rect;
GetClientRect(hWnd, &rect);
int clientWidth = rect.right;
int clientHeight = rect.bottom;
if (splitterPos < 100) splitterPos = 100;
if (splitterPos > clientHeight - 100) splitterPos = clientHeight - 100;
MoveWindow(hwndTabMain, 0, 0, clientWidth, splitterPos, TRUE);
MoveWindow(hSplitter, 0, splitterPos, clientWidth, 5, TRUE);
MoveWindow(hRichEdit, 0, splitterPos + 5, clientWidth, clientHeight - splitterPos - 5, TRUE);
RECT displayRect1 = { 0, 0, clientWidth, splitterPos };
TabCtrl_AdjustRect(hwndTabMain, FALSE, &displayRect1);
MoveWindow(hwndDrawArea, displayRect1.left, displayRect1.top,
displayRect1.right - displayRect1.left, displayRect1.bottom - displayRect1.top, TRUE);
UpdateOverlayPosition(hWnd, hwndTabMain, hwndOverlay);
return 0;
}
case WM_COMMAND: {
int wmId = LOWORD(wParam);
switch (wmId) {
case IDM_ABOUT:
SetWindowPos(hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
SetWindowPos(hwndOverlay, hwndTabMain, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
std::cout << "IDM_ABOUT: hwndOverlay restored above hwndTabMain" << std::endl;
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;
}

As the splitter is dragged downwards the 'X' in the upper window pane redraws however the it appears so far the old 'X' isn't cleared. I tried a few attempts at clearing it however I didn't find the correct way of doing so yet.
How can this redundant drawing issue be solved for such a window splitter?