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.
/* Smooth resizing of GDI+ MDI window
*
* Click window to resize, hit Escape or Alt+F4 to quit
*
* Character type is set to multibyte
* Project->Properties->Config Properties->General->Character Set = Multibyte
*
* Pritam 2014 */
// Includes
#include <Windows.h>
#include <gdiplus.h>
#pragma comment (lib,"Gdiplus.lib")
using namespace Gdiplus;
// Max resolution
#define XRES 1600
#define YRES 900
// Globals
bool resizing = false;
HWND parent, child; // child is the canvas window, parent provides clipping of child
Bitmap * buffer;
// Render
void Render() {
// Get parent client size
RECT rc;
GetClientRect(parent, &rc);
// Draw backbuffer
Graphics * g = Graphics::FromImage(buffer);
// Clear buffer
g->Clear(Color(100, 100, 100));
// Gray border
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);
// Draw buffer to screen
PAINTSTRUCT ps;
HDC hdc = BeginPaint(child, &ps);
Graphics graphics(hdc);
graphics.DrawImage(buffer, Point(0, 0));
// Free
EndPaint(child, &ps);
}
// MDI Callback
LRESULT CALLBACK MDICallback(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
switch(message) {
case WM_LBUTTONDOWN:
resizing = true; // Start resizing
return 0;
break;
case WM_KEYDOWN:
if(wparam == VK_ESCAPE) { // Exit on 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);
}
// Parent window callback
LRESULT CALLBACK WndCallback(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam) {
return DefFrameProc(hwnd, child, message, wparam, lparam);
}
// Create windows
bool CreateWindows(void) {
// Parent class
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;
// MDI class
wndclass.style = CS_OWNDC | CS_VREDRAW | CS_HREDRAW;
wndclass.lpfnWndProc = MDICallback;
wndclass.lpszClassName = "MDICALLBACKCANVAS";
if(!RegisterClassEx(&wndclass)) return false;
// Parent window styles
DWORD style = WS_POPUP | WS_CLIPCHILDREN;
DWORD exstyle = 0;
// Set initial window size and position
RECT rc;
rc.right = 640;
rc.bottom = 480;
AdjustWindowRectEx(&rc, style, false, exstyle);
rc.left = 20;
rc.top = 20;
// Create window
if(!(parent = CreateWindowEx(exstyle, "MDICLIENT", "MDI Resize", style, rc.left, rc.top, rc.right, rc.bottom, NULL, NULL, wndclass.hInstance, NULL))) return false;
// MDI window styles
style = MDIS_ALLCHILDSTYLES;
exstyle = WS_EX_MDICHILD;
// Set MDI size
rc.left = - 8; // The sizes occupied by borders and caption, if position is not correctly set an ugly caption will appear
rc.top = - 30;
rc.right = XRES;
rc.bottom = YRES;
AdjustWindowRectEx(&rc, style, false, exstyle);
// Create MDI child window
if(!(child = CreateWindowEx(exstyle, "MDICALLBACKCANVAS", "", style, rc.left, rc.top, rc.right, rc.bottom, parent, NULL, wndclass.hInstance, NULL))) return 8;
// Finalize
ShowWindow(child, SW_SHOW);
ShowWindow(parent, SW_SHOWNORMAL);
// Success
return true;
}
// Resize
void Resize(void) {
// Init
RECT rc, rcmdi;
GetClientRect(child, &rcmdi); // Use mdi window size to set max resize for parent
GetWindowRect(parent, &rc);
// Get mouse position
POINT mp;
GetCursorPos(&mp);
// Set new size
rc.right = mp.x - rc.left + 10;
rc.bottom = mp.y - rc.top + 10;
// Apply min & max size
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;
// Update window size
SetWindowPos(parent, NULL, rc.left, rc.top, rc.right, rc.bottom, SWP_NOZORDER | SWP_NOMOVE);
// Make sure client is entirely repainted
GetClientRect(child, &rc);
InvalidateRect(child, &rc, false);
UpdateWindow(child);
// Stop resizing if mousebutton is up
if(!(GetKeyState(VK_LBUTTON) & 1 << (sizeof(short) * 8 - 1)))
resizing = false;
}
// Main
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE pinstance, LPSTR cmdline, int cmdshow) {
// Initiate GDI+
ULONG_PTR gdiplusToken;
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
buffer = new Bitmap(XRES, YRES, PixelFormat24bppRGB);
// Create windows
if(!CreateWindows()) return 1;
// Main loop
bool running = true;
MSG message;
while(running) {
// Check message or pass them on to window callback
if(PeekMessage(&message, NULL, 0, 0, PM_REMOVE)) {
if(message.message == WM_QUIT) {
running = false;
} else {
if(!TranslateMDISysAccel(child, &message)) {
TranslateMessage(&message);
DispatchMessage(&message);
}
}
}
// Resize
if(resizing)
Resize();
// Sleep a millisecond to spare the CPU
Sleep(1);
}
// Free memmory and exit
delete buffer;
GdiplusShutdown(gdiplusToken);
return 0;
}
Edit: Another example using "bitmask"/layered window.
// Escape to quit, left mousebutton to move window, right mousebutton to resize.
// And again char set must be multibyte
// Include
#include <Windows.h>
#include <gdiplus.h>
#pragma comment (lib,"Gdiplus.lib")
using namespace Gdiplus;
// Globals
Bitmap * backbuffer;
int xres, yres;
bool move, size;
POINT framePos, frameSize, mouseOffset;
// Renders the backbuffer
void Render(void) {
if(!backbuffer) return;
// Clear window with mask color
Graphics * gfx = Graphics::FromImage(backbuffer);
gfx->Clear(Color(255, 0, 255));
// Draw stuff
SolidBrush brush(Color(120, 120, 120));
gfx->FillRectangle(&brush, framePos.x, framePos.y, frameSize.x, frameSize.y);
}
// Paints the backbuffer to window
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) {
// Get mouse position
POINT mouse;
GetCursorPos(&mouse);
// Update frame position
framePos.x = mouse.x - mouseOffset.x;
framePos.y = mouse.y - mouseOffset.y;
// Redraw buffer and invalidate & update window
Render();
InvalidateRect(hwnd, NULL, false);
UpdateWindow(hwnd);
// Stop move
if(!(GetKeyState(VK_LBUTTON) & 1 << (sizeof(short) * 8 - 1)))
move = false;
}
void HandleSize(HWND hwnd) {
// Get mouse position
POINT mouse;
GetCursorPos(&mouse);
// Update frame size
frameSize.x = mouse.x + mouseOffset.x - framePos.x;
frameSize.y = mouse.y + mouseOffset.y - framePos.y;
//frameSize.x = mouse.x + mouseOffset.x;
//frameSize.y = mouse.y + mouseOffset.y;
// Redraw buffer and invalidate & update window
Render();
InvalidateRect(hwnd, NULL, false);
UpdateWindow(hwnd);
// Stop size
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); // Get mouse coords
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);
}
// Main
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE pinstance, LPSTR cmdline, int cmdshow) {
// Init resolution, frame
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;
// Initiate GDI+
ULONG_PTR gdiplusToken;
GdiplusStartupInput gdiplusStartupInput;
GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);
// Init backbuffer
backbuffer = ::new Bitmap(xres, yres, PixelFormat24bppRGB);
Render();
// Window class
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;
// Create window
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;
// Make window fully transparent to avoid the display of unpainted window
SetLayeredWindowAttributes(hwnd, 0, 0, LWA_ALPHA);
// Finalize
ShowWindow(hwnd, SW_SHOWNORMAL);
UpdateWindow(hwnd);
// Make window fully opaque, and set color mask key
SetLayeredWindowAttributes(hwnd, RGB(255, 0, 255), 0, LWA_COLORKEY);
// Main loop
MSG msg;
bool running = true;
while(running) {
// Check message
if(PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) {
if(msg.message == WM_QUIT) {
running = false;
} else {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
// Move or size frame
if(move) { HandleMove(hwnd); }
if(size) { HandleSize(hwnd); }
Sleep(1);
}
// Free memory
::delete backbuffer;
backbuffer = NULL;
GdiplusShutdown(gdiplusToken);
// Exit
return 0;
}
WM_WINDOWPOSCHANGING
after seeing this article by Raymond Chen: blogs.msdn.com/b/oldnewthing/archive/2008/01/16/7123299.aspx – Skiplane