Win32: Adjust Window Rectangle

Published:
Preface

The task was to make a window with an image as the window background and do not stretch this image.

The first proposed solution was really simple:
1. Load the image.
2. Detect its size.
3. Create a window with the WS_POPUP style.
4. Set the window size exactly as the loaded image.

For example, if the image has width cx and height cy, the following function will calculate the rectangle in the center of the desktop for my window:

BOOL Calculate(int cx, int cy, RECT& rect)
                      {
                      	HDC hDC = ::GetDC(NULL);
                      	const int w = GetDeviceCaps(hDC, HORZRES);
                      	const int h = GetDeviceCaps(hDC, VERTRES);
                      	::ReleaseDC(NULL, hDC);
                      
                      	rect.left = (w >> 1) - (cx >> 1);
                      	rect.top = (h >> 1) - (cy >> 1);
                      	rect.right = rect.left + cx;
                      	rect.bottom = rect.top + cy;
                      
                      	return TRUE;
                      }

Open in new window

Note: the full source code demonstrating the solution is in the Appendix.

My colleagues said, "It will be great, if it will be possible to move this window."
It's not that easy. But it was solved too (handling of WM_LBUTTONDOWN, WM_MOUSEMOVE and WM_LBUTTONUP messages).

The next request was to make the window resizable.
 - Oh! But you said in the beginning do not stretch the image!
 - Yes. But, but now we see that it will good to resize the window in a predefined range.

Ok. Let's make the window. Now, with the standard styles, it should be the main application window - "sizeable" and "movable".

GetWindowRect with GetClientRect

The question is how set the window size so, that the client rectangle will be exactly as the image size drawn on it?

The window has a frame, a caption, maybe, a menu. GetWindowRect function retrieves the size of the whole window including these attributes. GetClientRect function gives the size of the window internal area. So, if I will detect the difference between these two rectangles, I can calculate the correct rectangle to be used for MoveWindow function call. The following code does the job:

BOOL Calculate(HWND hWnd, int cx, int cy, RECT& rect)
                      {
                      	if (hWnd == NULL)
                      		return FALSE;
                      
                      	HDC hDC = ::GetDC(NULL);
                      	const int w = GetDeviceCaps(hDC, HORZRES);
                      	const int h = GetDeviceCaps(hDC, VERTRES);
                      	::ReleaseDC(NULL, hDC);
                      
                      	RECT rcWindow;
                      	GetWindowRect(hWnd, &rcWindow);
                      	RECT rcClient;
                      	GetClientRect(hWnd, &rcClient);
                      	cx += (rcWindow.right - rcWindow.left) - rcClient.right;
                      	cy += (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
                      
                      	rect.left = (w >> 1) - (cx >> 1);
                      	rect.top = (h >> 1) - (cy >> 1);
                      	rect.right = rect.left + cx;
                      	rect.bottom = rect.top + cy;
                      
                      	return TRUE;
                      }

Open in new window


AdjustWindowRectEx

Something looked strange for me in this solution above. This is the application window and before I launched the message loop, I called few functions and make few calculations. I call GetWindowRect and GetClientRect immediately after Create.
But almost all windows on my desktop look the same - the window caption in all windows has the same height. All windows have the same thin frame.

Yes! The window style determines the difference between the window size and the client area.  And there is a function I saw many times, mainly in the MFC source code, and never knew what is this - AdjustWindowRectEx.

So all code with GetWindowRect and GetClientRect can be replaced by one function call - AdjustWindowRectEx as it shown below:

BOOL Calculate(HWND hWnd, 
                      	DWORD nWndStyle, DWORD nWndStyleEx, 
                      	int cx, int cy, RECT& rect)
                      {
                      	if (hWnd == NULL)
                      		return FALSE;
                      
                      	HDC hDC = ::GetDC(NULL);
                      	const int w = GetDeviceCaps(hDC, HORZRES);
                      	const int h = GetDeviceCaps(hDC, VERTRES);
                      	::ReleaseDC(NULL, hDC);
                      
                      	rect.left = (w >> 1) - (cx >> 1);
                      	rect.top = (h >> 1) - (cy >> 1);
                      	rect.right = rect.left + cx;
                      	rect.bottom = rect.top + cy;
                      
                      	AdjustWindowRectEx(&rect, 
                      		nWndStyle, FALSE, nWndStyleEx);
                      	return TRUE;
                      }

Open in new window


This function receives as a parameter the rectangle of the client area, the window styles and the fact that the window has a menu or not. If the function succeeds, the return value is 0 and the rectangle in the first parameter will have the coordinates of window.

In MSND article you can read about this function that it does not support menu bars with many rows, so the first method, with GetWindowRect and GetClientRect can be helpful in a certain case too.

Control the window resizing

How to set a range, or a minimal and maximal permitted rectangles for my window? MSDN says to handle WM_GETMINMAXINFO message. The handler in my application looks as the following:

case WM_GETMINMAXINFO:
                      	{
                      		LPMINMAXINFO pInfo = (LPMINMAXINFO)lParam;
                      		if (pInfo != NULL)
                      		{
                      			BITMAP bitmap;
                      			GetObject(s_hBitmap, sizeof(BITMAP), &bitmap);
                      			pInfo->ptMinTrackSize.x = bitmap.bmWidth;
                      			pInfo->ptMinTrackSize.y = bitmap.bmHeight;
                      		}
                      	}
                      	break;

Open in new window


Now the window cannot have the width less than cx and the height less than cy.
It is possible to control the window maximal size and position - just fulfill the related fields in the MINMAXINFO structure.

More detailed article about the subject you can find here:
Control Client area minimum size (WM_GETMINMAXINFO) with MFC in C++

Summary

1. The most important conclusion I got from this task is to not use a popup window for windows desktop application.
2. AdjustWindowRect functions from Win32 API calculates the size of the window based on the predefined size of the client area. This size can be used for CreateWindow functions or for SetWindowPos/MoveWindow functions.
3. Win32 API allows to limit the resizing of the window by handling WM_GETMINMAXINFO message.

Appendix

The code of the first application mentioned in this article, that creates the popup window, is below:

#define WIN32_LEAN_AND_MEAN
                      
                      #include <windows.h>
                      
                      LPCWSTR s_szWndName = L"Adjust Window Rectangle";
                      LPCWSTR s_szFileName = L"image.bmp";
                      
                      HBITMAP s_hBitmap = NULL;
                      
                      ATOM RegisterWndClass(HINSTANCE, LPCWSTR);
                      LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
                      BOOL Calculate(int, int, RECT&);
                      
                      int WINAPI wWinMain(HINSTANCE hInstance, 
                      	HINSTANCE, LPWSTR, int nShowCmd)
                      {
                      	s_hBitmap = (HBITMAP)LoadImage(NULL, s_szFileName, 
                      		IMAGE_BITMAP, 0, 0, 
                      		LR_CREATEDIBSECTION | LR_DEFAULTSIZE |
                      		LR_LOADFROMFILE); 
                      
                      	if (s_hBitmap == NULL)
                      		return 0;
                      
                      	RegisterWndClass(hInstance, s_szWndName);
                      
                      	BITMAP bitmap = { 0 };
                      	GetObject(s_hBitmap, sizeof(BITMAP), &bitmap);
                      
                      	HWND hWnd = CreateWindow(s_szWndName, s_szWndName, 
                      		WS_POPUP | WS_CLIPSIBLINGS | WS_CLIPCHILDREN,
                      		CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 
                      		NULL, NULL, hInstance, NULL);
                      
                      	if (hWnd != NULL)
                      	{
                      		RECT rect = { 0 };
                      		Calculate(bitmap.bmWidth, bitmap.bmHeight, rect);
                      
                      		MoveWindow(hWnd, 
                      			rect.left, rect.top,
                      			rect.right - rect.left,
                      			rect.bottom - rect.top,
                      			FALSE);
                      
                      		ShowWindow(hWnd, nShowCmd);
                      		UpdateWindow(hWnd);
                      
                      		MSG msg = { 0 };
                      		while (GetMessage(&msg, NULL, 0, 0))
                      		{
                      			TranslateMessage(&msg);
                      			DispatchMessage(&msg);
                      		}
                      	}
                      
                      	if (s_hBitmap != NULL)
                      		DeleteObject(s_hBitmap);
                      
                      	return 0;
                      }
                      
                      ATOM RegisterWndClass(HINSTANCE hInstance, 
                      	LPCWSTR lpszWndClassName)
                      {
                      	WNDCLASSEX wcex = { 0 };
                      	wcex.cbSize		= sizeof(WNDCLASSEX);
                      	wcex.style		= CS_HREDRAW | CS_VREDRAW;
                      	wcex.lpfnWndProc		= WndProc;
                      	wcex.hInstance		= hInstance;
                      	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
                      	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW + 1);
                      	wcex.lpszMenuName	= NULL;
                      	wcex.lpszClassName	= lpszWndClassName;
                      	return RegisterClassEx(&wcex);
                      }
                      
                      LRESULT CALLBACK WndProc(HWND hWnd, UINT message, 
                      	 WPARAM wParam, LPARAM lParam)
                      {
                      	switch (message)
                      	{
                      	case WM_ERASEBKGND:
                      		if (s_hBitmap != NULL)
                      		{
                      			RECT rect = { 0 };
                      			GetClientRect(hWnd, &rect);
                      			HDC hDC = (HDC)wParam;
                      			HDC hMemDC = CreateCompatibleDC(hDC);
                      			HGDIOBJ hOld = SelectObject(hMemDC, 
                      				s_hBitmap);
                      			BitBlt(hDC, 
                      				0, 0, rect.right, rect.bottom,
                      				hMemDC, 0, 0,
                      				SRCCOPY);
                      			SelectObject(hMemDC, hOld);
                      			DeleteDC(hMemDC);
                      		}
                      		break;
                      
                      	case WM_PAINT:
                      		{
                      			PAINTSTRUCT ps;
                      			HDC hdc = BeginPaint(hWnd, &ps);
                      			EndPaint(hWnd, &ps);
                      		}
                      		break;
                      
                      	case WM_LBUTTONDOWN:
                      		PostMessage(hWnd, WM_CLOSE, 0, 0);
                      		break;
                      
                      	case WM_DESTROY:
                      		PostQuitMessage(0);
                      		break;
                      
                      	default:
                      		return DefWindowProc(hWnd, message, wParam, lParam);
                      	}
                      	return 0;
                      }
                      
                      BOOL Calculate(int cx, int cy, RECT& rect)
                      {
                      	HDC hDC = ::GetDC(NULL);
                      	const int w = GetDeviceCaps(hDC, HORZRES);
                      	const int h = GetDeviceCaps(hDC, VERTRES);
                      	::ReleaseDC(NULL, hDC);
                      
                      	rect.left = (w >> 1) - (cx >> 1);
                      	rect.top = (h >> 1) - (cy >> 1);
                      	rect.right = rect.left + cx;
                      	rect.bottom = rect.top + cy;
                      
                      	return TRUE;
                      }

Open in new window


The application load a bmp-file in a standard Windows way - with LoadImage function, creates a popup window as the application main window and set the window size (MoveWindow function call) to be the same as the size of the loaded image.

The image drawing procedure is located in the WM_ERASEBKGND handler - BitBlt function from GDI is used.

In order to have a possibility to close this test window, I added WM_LBUTTONDOWN handler that close the window on mouse click in the window client area.

Here is a functional sample written in Win32 style demonstrating this article in action:

#define WIN32_LEAN_AND_MEAN
                      
                      #include <windows.h>
                      
                      LPCWSTR s_szWndName = L"Adjust Window Rectangle";
                      LPCWSTR s_szFileName = L"image.bmp";
                      
                      HBITMAP s_hBitmap = NULL;
                      
                      ATOM RegisterWndClass(HINSTANCE, LPCWSTR);
                      LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
                      BOOL Calculate(HWND, int, int, RECT&);
                      
                      int WINAPI wWinMain(HINSTANCE hInstance, 
                      		HINSTANCE, LPWSTR, int nShowCmd)
                      {
                      	s_hBitmap = (HBITMAP)LoadImage(NULL, s_szFileName, 
                      		IMAGE_BITMAP, 0, 0, 
                      		LR_CREATEDIBSECTION | LR_DEFAULTSIZE | 
                      		LR_LOADFROMFILE); 
                      
                      	if (s_hBitmap == NULL)
                      		return 0;
                      
                      	RegisterWndClass(hInstance, s_szWndName);
                      
                      	BITMAP bitmap = { 0 };
                      	GetObject(s_hBitmap, sizeof(BITMAP), &bitmap);
                      
                      	HWND hWnd = CreateWindowEx(
                      		WS_EX_APPWINDOW | WS_EX_WINDOWEDGE, 
                      		s_szWndName, s_szWndName, 
                      		WS_OVERLAPPEDWINDOW | WS_CLIPSIBLINGS | 
                      		WS_CLIPCHILDREN,
                      		CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, 
                      		NULL, NULL, hInstance, NULL);
                      
                      	if (hWnd != NULL)
                      	{
                      		RECT rect = { 0 };
                      		Calculate(hWnd, bitmap.bmWidth, bitmap.bmHeight, rect);
                      
                      		MoveWindow(hWnd, 
                      			rect.left, rect.top,
                      			rect.right - rect.left,
                      			rect.bottom - rect.top,
                      			FALSE);
                      
                      		ShowWindow(hWnd, nShowCmd);
                      		UpdateWindow(hWnd);
                      
                      		MSG msg = { 0 };
                      		while (GetMessage(&msg, NULL, 0, 0))
                      		{
                      			TranslateMessage(&msg);
                      			DispatchMessage(&msg);
                      		}
                      	}
                      
                      	if (s_hBitmap != NULL)
                      		DeleteObject(s_hBitmap);
                      
                      	return 0;
                      }
                      
                      ATOM RegisterWndClass(HINSTANCE hInstance, 
                      		LPCWSTR lpszWndClassName)
                      {
                      	WNDCLASSEX wcex = { 0 };
                      	wcex.cbSize		= sizeof(WNDCLASSEX);
                      	wcex.style		= CS_HREDRAW | CS_VREDRAW;
                      	wcex.lpfnWndProc		= WndProc;
                      	wcex.hInstance		= hInstance;
                      	wcex.hCursor		= LoadCursor(NULL, IDC_ARROW);
                      	wcex.hbrBackground	= (HBRUSH)(COLOR_WINDOW + 1);
                      	wcex.lpszMenuName	= NULL;
                      	wcex.lpszClassName	= lpszWndClassName;
                      	return RegisterClassEx(&wcex);
                      }
                      
                      LRESULT CALLBACK WndProc(HWND hWnd, UINT message, 
                      	WPARAM wParam, LPARAM lParam)
                      {
                      	switch (message)
                      	{
                      	case WM_ERASEBKGND:
                      		if (s_hBitmap != NULL)
                      		{
                      			BITMAP bitmap;
                      			GetObject(s_hBitmap, sizeof(BITMAP), &bitmap);
                      			RECT rect = { 0 };
                      			GetClientRect(hWnd, &rect);
                      			HDC hDC = (HDC)wParam;
                      			HDC hMemDC = CreateCompatibleDC(hDC);
                      			HGDIOBJ hOld = SelectObject(hMemDC, 
                      				s_hBitmap);
                      			StretchBlt(hDC, 
                      				0, 0, rect.right, rect.bottom,
                      				hMemDC, 
                      				0, 0, bitmap.bmWidth, bitmap.bmHeight, 
                      				SRCCOPY);
                      			SelectObject(hMemDC, hOld);
                      			DeleteDC(hMemDC);
                      		}
                      		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;
                      }
                      
                      BOOL Calculate(HWND hWnd, int cx, int cy, RECT& rect)
                      {
                      	if (hWnd == NULL)
                      		return FALSE;
                      
                      	HDC hDC = ::GetDC(NULL);
                      	const int w = GetDeviceCaps(hDC, HORZRES);
                      	const int h = GetDeviceCaps(hDC, VERTRES);
                      	::ReleaseDC(NULL, hDC);
                      
                      	RECT rcWindow;
                      	GetWindowRect(hWnd, &rcWindow);
                      	RECT rcClient;
                      	GetClientRect(hWnd, &rcClient);
                      	cx += (rcWindow.right - rcWindow.left) - rcClient.right;
                      	cy += (rcWindow.bottom - rcWindow.top) - rcClient.bottom;
                      
                      	rect.left = (w >> 1) - (cx >> 1);
                      	rect.top = (h >> 1) - (cy >> 1);
                      	rect.right = rect.left + cx;
                      	rect.bottom = rect.top + cy;
                      
                      	return TRUE;
                      }

Open in new window


The application window has all necessary styles. It's sizable and moveable. The same image is loaded and drawn as the window background.

The change compare to the previous application is in Calculate function - now this function takes into account the difference between the window rectangle and the client area - the image is not stretched, because the window rectangle itself is bigger than in the previous example.

In order to test AdjustWindowRect function, you need to replace the Calculate function with the following:

BOOL Calculate(HWND hWnd, 
                      	DWORD nWndStyle, DWORD nWndStyleEx, 
                      	int cx, int cy, RECT& rect)
                      {
                      	if (hWnd == NULL)
                      		return FALSE;
                      
                      	HDC hDC = ::GetDC(NULL);
                      	const int w = GetDeviceCaps(hDC, HORZRES);
                      	const int h = GetDeviceCaps(hDC, VERTRES);
                      	::ReleaseDC(NULL, hDC);
                      
                      	rect.left = (w >> 1) - (cx >> 1);
                      	rect.top = (h >> 1) - (cy >> 1);
                      	rect.right = rect.left + cx;
                      	rect.bottom = rect.top + cy;
                      
                      	AdjustWindowRectEx(&rect, nWndStyle, FALSE, nWndStyleEx);
                      	return TRUE;
                      }

Open in new window


The same solution presented in the ATL-style looks a bit more attractive:

#define STRICT
                      #define WIN32_LEAN_AND_MEAN
                      
                      #include <atlbase.h>
                      #include <atlwin.h>
                      #include <atlimage.h>
                      
                      static const LPCWSTR s_szFileName = L"image.jpg";
                      static const DWORD s_nWndStyle = WS_OVERLAPPEDWINDOW | 
                      	WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
                      static const DWORD s_nWndStyleEx = WS_EX_APPWINDOW | 
                      	WS_EX_WINDOWEDGE;
                      
                      typedef CWinTraits<s_nWndStyle, s_nWndStyleEx> CMyWindowTraits;
                      
                      class CMyWindow : public CWindowImpl<CMyWindow, CWindow, CMyWindowTraits>
                      {
                      	CImage	m_Bk;
                      	RECT	m_rcMin;
                      
                      public:
                      	DECLARE_WND_CLASS(L"My Window")
                      
                      	BEGIN_MSG_MAP(CMyWindow)
                      		MESSAGE_HANDLER(WM_CREATE, OnCreate)
                      		MESSAGE_HANDLER(WM_GETMINMAXINFO, OnGetMinMaxInfo)
                      		MESSAGE_HANDLER(WM_CLOSE, OnClose)
                      		MESSAGE_HANDLER(WM_DESTROY, OnDestroy)
                      		MESSAGE_HANDLER(WM_ERASEBKGND, OnEraseBkgnd)
                      	END_MSG_MAP()
                      
                      	LRESULT OnCreate(UINT nMsg, WPARAM wParam, 
                      		LPARAM lParam, BOOL& bHandled)
                      	{
                      		HRESULT hr = m_Bk.Load(s_szFileName);
                      		if (SUCCEEDED(hr))
                      		{
                      			int cx = m_Bk.GetWidth();
                      			int cy = m_Bk.GetHeight();
                      			if (cx > 0 && cy > 0)
                      			{
                      				Center(cx, cy, m_rcMin);
                      				MoveWindow(&m_rcMin);
                      			}
                      		}
                      		return 0;
                      	}
                      
                      	LRESULT OnClose(UINT nMsg, WPARAM wParam, 
                      		LPARAM lParam, BOOL& bHandled)
                      	{
                      		DestroyWindow();
                      		return 0;
                      	}
                      
                      	LRESULT OnDestroy(UINT nMsg, WPARAM wParam, 
                      		LPARAM lParam, BOOL& bHandled)
                      	{
                      		PostQuitMessage(0);
                      		return 0;
                      	}
                      
                      	LRESULT OnEraseBkgnd(UINT nMsg, WPARAM wParam, 
                      		LPARAM lParam, BOOL& bHandled)
                      	{
                      		if (m_Bk.IsNull())
                      		{
                      			bHandled = FALSE;
                      			return 1;
                      		}
                      		RECT rect = { 0 };
                      		GetClientRect(&rect);
                      		RECT image = { 0, 0, m_Bk.GetWidth(), m_Bk.GetHeight() };
                      		HDC hDC = (HDC)wParam;
                      		m_Bk.AlphaBlend(hDC, rect, image);
                      		return 1;
                      	}
                      
                      	LRESULT OnGetMinMaxInfo(UINT nMsg, WPARAM wParam, 
                      		LPARAM lParam, BOOL& bHandled)
                      	{
                      		if (m_Bk.IsNull())
                      		{
                      			bHandled = FALSE;
                      			return 1;
                      		}
                      		LPMINMAXINFO pInfo = (LPMINMAXINFO)lParam;
                      		if (pInfo != NULL)
                      		{
                      			pInfo->ptMinTrackSize.x = 
                      				m_rcMin.right - m_rcMin.left;
                      			pInfo->ptMinTrackSize.y = 
                      				m_rcMin.bottom - m_rcMin.top;
                      		}
                      		return 0;
                      	}
                      
                      	BOOL Center(int cx, int cy, RECT& rect)
                      	{
                      		HDC hDC = ::GetDC(NULL);
                      		const int w = GetDeviceCaps(hDC, HORZRES);
                      		const int h = GetDeviceCaps(hDC, VERTRES);
                      		::ReleaseDC(NULL, hDC);
                      
                      		rect.left = (w >> 1) - (cx >> 1);
                      		rect.top = (h >> 1) - (cy >> 1);
                      		rect.right = rect.left + cx;
                      		rect.bottom = rect.top + cy;
                      
                      		AdjustWindowRectEx(&rect, s_nWndStyle, FALSE,
                      			s_nWndStyleEx);
                      		return TRUE;
                      	}
                      
                      	int Run() 
                      	{
                      		HWND hWnd = Create(NULL, CWindow::rcDefault, 
                      			L"Adjust Window Rectangle");
                      		if (hWnd == NULL)
                      			return 0;
                      
                      		ShowWindow(SW_SHOWNORMAL);
                      		UpdateWindow();
                      
                      		MSG msg;
                      		while (GetMessage(&msg, NULL, 0, 0) > 0)
                      		{
                      			TranslateMessage(&msg);
                      			DispatchMessage(&msg);
                      		}
                      		return (int)msg.wParam;
                      	}
                      };
                      
                      int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, 
                      				   LPSTR lpCmdLine, int nShowCmd)
                      {
                      	CMyWindow wnd;
                      	int nRun = wnd.Run();
                      	return nRun;
                      }

Open in new window


ATL CImage class used in the code above allows to load an image in JPEG-format. Here is the application screenshot:
 Application screenshot
0
14,922 Views

Comments (0)

Have a question about something in this article? You can receive help directly from the article author. Sign up for a free trial to get started.