This is the best I've come up with and resizes great although the backbuffer blitting causes some edge flickering, haven't tested with DX or OGL yet but it should work even better with hardware acceleration. It's a bit bulky but will do as a proof of concept.
If the canvas could be clipped without using MDI that would be even better, like using a bitmask buffer.
One thing i'm not happy about is the position coords of the child window because they might not work on all systems, but a combination of GetSystemMetrics calls to get border and caption sizes should fix that.
#include <Windows.h>
#include <gdiplus.h>
#pragma comment (lib,"Gdiplus.lib")
using namespace Gdiplus;
#define XRES 1600
#define YRES 900
bool resizing = false;
HWND parent, child;
Bitmap * buffer;
void Render() {
RECT rc;
GetClientRect(parent, &rc);
Graphics * g = Graphics::FromImage(buffer);
g->Clear(Color(100, 100, 100));
Pen pen(Color(255, 180, 180, 180));
g->DrawRectangle(&pen, 10, 10, rc.right - 20, rc.bottom - 20);
pen.SetColor(Color(255, 0, 0, 0));
g->DrawRectangle(&pen, 0, 0, rc.right - 1, rc.bottom - 1);
PAINTSTRUCT ps;
HDC hdc = BeginPaint(child, &ps);
Graphics graphics(hdc);
graphics.DrawImage(buffer, Point(0, 0));
EndPaint(child, &ps);
}
LRESULT CALLBACK MDICallback(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
switch(message) {
case WM_LBUTTONDOWN:
resizing = true;
return 0;
break;
case WM_KEYDOWN:
if(wparam == VK_ESCAPE) {
PostQuitMessage(0);
}
TranslateMessage((const MSG *)&message);
return 0;
break;
case WM_PAINT:
Render();
return 0;
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
break;
}
return DefMDIChildProc(hwnd, message, wparam, lparam);
}
LRESULT CALLBACK WndCallback(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
return DefFrameProc(hwnd, child, message, wparam, lparam);
}
bool CreateWindows(void) {
WNDCLASSEX wndclass;
ZeroMemory(&wndclass, sizeof(wndclass)); wndclass.cbSize = sizeof(wndclass);
wndclass.style = CS_NOCLOSE;
wndclass.lpfnWndProc = WndCallback;
wndclass.hInstance = GetModuleHandle(NULL);
wndclass.lpszClassName = "WNDCALLBACKPARENT";
wndclass.hIcon = LoadIcon(NULL, IDI_WINLOGO);
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
if(!RegisterClassEx(&wndclass)) return false;
wndclass.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;
wndclass.lpfnWndProc = MDICallback;
wndclass.lpszClassName = "MDICALLBACKCANVAS";
if(!RegisterClassEx(&wndclass)) return false;
DWORD style = WS_POPUP | WS_CLIPCHILDREN;
DWORD exstyle = 0;
RECT rc;
rc.right = 640;
rc.bottom = 480;
AdjustWindowRectEx(&rc, style, false, exstyle);
rc.left = 20;
rc.top = 20;
if(!(parent = CreateWindowEx(exstyle, "MDICLIENT", "MDI Resize", style, rc.left, rc.top, rc.right, rc.bottom, NULL, NULL, wndclass.hInstance, NULL))) return false;
style = MDIS_ALLCHILDSTYLES;
exstyle = WS_EX_MDICHILD;
rc.left = - 8;
rc.top = - 30;
rc.right = XRES;
rc.bottom = YRES;
AdjustWindowRectEx(&rc, style, false, exstyle);
if(!(child = CreateWindowEx(exstyle, "MDICALLBACKCANVAS", "", style, rc.left, rc.top, rc.right, rc.bottom, parent, NULL, wndclass.hInstance, NULL))) return 8;
ShowWindow(child, SW_SHOW);
ShowWindow(parent, SW_SHOWNORMAL);
return true;
}
void Resize(void) {
RECT rc, rcmdi;
GetClientRect(child, &rcmdi);
GetWindowRect(parent, &rc);
POINT mp;
GetCursorPos(&mp);
rc.right = mp.x - rc.left + 10;
rc.bottom = mp.y - rc.top + 10;
if(rc.right < 240) rc.right = 240; if(rc.bottom < 180) rc.bottom = 180;
if(rc.right > rcmdi.right) rc.right = rcmdi.right; if(rc.bottom > rcmdi.bottom) rc.bottom = rcmdi.bottom;
SetWindowPos(parent, NULL, rc.left, rc.top, rc.right, rc.bottom, SWP_NOZORDER | SWP_NOMOVE);
GetClientRect(child, &rc);
InvalidateRect(child, &rc, false);
UpdateWindow(child);
if(!(GetKeyState(VK_LBUTTON) & 1 << (sizeof(short) * 8 - 1)))
resizing = false;
}
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE pinstance, LPSTR cmdline, int cmdshow) {
ULONG_PTR gdiplusToken;
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
buffer = new Bitmap(XRES, YRES, PixelFormat24bppRGB);
if(!CreateWindows()) return 1;
bool running = true;
MSG message;
while(running) {
if(PeekMessage(&message, NULL, 0, 0, PM_REMOVE)) {
if(message.message == WM_QUIT) {
running = false;
} else {
if(!TranslateMDISysAccel(child, &message)) {
TranslateMessage(&message);
DispatchMessage(&message);
}
}
}
if(resizing)
Resize();
Sleep(1);
}
delete buffer;
GdiplusShutdown(gdiplusToken);
return 0;
}
Edit: Another example using "bitmask"/layered window.
#include <Windows.h>
#include <gdiplus.h>
#pragma comment (lib,"Gdiplus.lib")
using namespace Gdiplus;
Bitmap * backbuffer;
int xres, yres;
bool move, size;
POINT framePos, frameSize, mouseOffset;
void Render(void) {
if(!backbuffer) return;
Graphics * gfx = Graphics::FromImage(backbuffer);
gfx->Clear(Color(255, 0, 255));
SolidBrush brush(Color(120, 120, 120));
gfx->FillRectangle(&brush, framePos.x, framePos.y, frameSize.x, frameSize.y);
}
void Paint(HWND hwnd) {
if(!hwnd) return;
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
Graphics gfx(hdc);
gfx.DrawImage(backbuffer, Point(0, 0));
EndPaint(hwnd, &ps);
}
void HandleMove(HWND hwnd) {
POINT mouse;
GetCursorPos(&mouse);
framePos.x = mouse.x - mouseOffset.x;
framePos.y = mouse.y - mouseOffset.y;
Render();
InvalidateRect(hwnd, NULL, false);
UpdateWindow(hwnd);
if(!(GetKeyState(VK_LBUTTON) & 1 << (sizeof(short) * 8 - 1)))
move = false;
}
void HandleSize(HWND hwnd) {
POINT mouse;
GetCursorPos(&mouse);
frameSize.x = mouse.x + mouseOffset.x - framePos.x;
frameSize.y = mouse.y + mouseOffset.y - framePos.y;
Render();
InvalidateRect(hwnd, NULL, false);
UpdateWindow(hwnd);
if(!(GetKeyState(VK_RBUTTON) & 1 << (sizeof(short) * 8 - 1)))
size = false;
}
LRESULT CALLBACK WindowCallback(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam) {
POINTS p;
switch(msg) {
case WM_KEYDOWN:
if(wparam == VK_ESCAPE) PostQuitMessage(0);
return 0;
break;
case WM_LBUTTONDOWN:
p = MAKEPOINTS(lparam);
mouseOffset.x = p.x - framePos.x;
mouseOffset.y = p.y - framePos.y;
move = true;
break;
case WM_RBUTTONDOWN:
p = MAKEPOINTS(lparam);
mouseOffset.x = framePos.x + frameSize.x - p.x;
mouseOffset.y = framePos.y + frameSize.y - p.y;
size = true;
break;
case WM_PAINT:
Paint(hwnd);
return 0;
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
break;
}
return DefWindowProc(hwnd, msg, wparam, lparam);
}
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE pinstance, LPSTR cmdline, int cmdshow) {
xres = GetSystemMetrics(SM_CXSCREEN);
yres = GetSystemMetrics(SM_CYSCREEN);
move = false; size = false;
framePos.x = 100; framePos.y = 80;
frameSize.x = 320; frameSize.y = 240;
mouseOffset.x = 0; mouseOffset.y = 0;
ULONG_PTR gdiplusToken;
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
backbuffer = ::new Bitmap(xres, yres, PixelFormat24bppRGB);
Render();
WNDCLASSEX wc; ZeroMemory(&wc, sizeof(wc)); wc.cbSize = sizeof(wc);
wc.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;
wc.lpfnWndProc = WindowCallback;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = "SingleResizeCLASS";
wc.hIcon = LoadIcon(NULL, IDI_WINLOGO);
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION);
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
if(!RegisterClassEx(&wc)) return 1;
HWND hwnd;
DWORD style = WS_POPUP;
DWORD exstyle = WS_EX_LAYERED;
if(!(hwnd = CreateWindowEx(exstyle, wc.lpszClassName, "Resize", style, 0, 0, xres, yres, NULL, NULL, wc.hInstance, NULL)))
return 2;
SetLayeredWindowAttributes(hwnd, 0, 0, LWA_ALPHA);
ShowWindow(hwnd, SW_SHOWNORMAL);
UpdateWindow(hwnd);
SetLayeredWindowAttributes(hwnd, RGB(255, 0, 255), 0, LWA_COLORKEY);
MSG msg;
bool running = true;
while(running) {
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if(msg.message == WM_QUIT) {
running = false;
} else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
if(move) { HandleMove(hwnd); }
if(size) { HandleSize(hwnd); }
Sleep(1);
}
::delete backbuffer;
backbuffer = NULL;
GdiplusShutdown(gdiplusToken);
return 0;
}