C++, GDIplus clien windo partial repaint and overlap of scaled objects on window resizing

Tom S 1 Reputation point
2021-09-15T05:33:09.687+00:00

PREFACE: Old programmer (C and C++ ca'1999... I've met Bjarne S. 'm old, but I'm a Fun Gi;))
Coming back to Windows C++ graphics programming; trying to learn GDIplus; using MS Visual Studio Community 2019, Ver 16.10.2

GOAL: Attempting to draw a a simple chess board, that scales with Window main form...

PROBLEM: Program compiles and paints initial window fine. But, when the window is resized larger in x, y, or x&y by mouse click and drag, it (appears it) only does a partial client area repaint; leaving artifacts at previous scale; painting only the new region of board at larger changed scale...

CONJECTURE: I (probably) need to force a repaint of the entire client every time(?).

FURTHER ISSUE: I cannot figure out the method to force full client repaint on my own (despite hitting PC repeatedly while supplicating BAAL, &c.).

Please Help.

Below are my code, some images of what happens when the window is resized by mouse click & drag, and the VS build and debug run output messages...
(I'm sure it's way overkill, but I like to be thorough.)

Thank You, All, in advance, for your time and assistance!
Peace - Tom

//=============================================================================================================
//<BEGIN - CODE>

#include <windows.h>  
#include <gdiplus.h>  
  
LRESULT CALLBACK WindowProcessMessages(HWND, UINT, WPARAM, LPARAM);  // Main Window message processing function prototype  
void drawBoard(HDC, HWND); // Prototype for function to draw a Chess Board prototype  
  
int WINAPI WinMain(_In_ HINSTANCE currentInstance, _In_opt_ HINSTANCE previousInstance, _In_ PSTR cmdLine, _In_ INT cmdCount) {  
	//Initialize GDI+  
	Gdiplus::GdiplusStartupInput gdiplusStartupInput;  
	ULONG_PTR gdiplusToken;  
	Gdiplus::GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);  
  
	const char* CLASS_NAME = "WinDrawWin32Class";  
	WNDCLASS wc{};  
	wc.hInstance = currentInstance;  
	wc.lpszClassName = CLASS_NAME;  
	wc.hCursor = LoadCursor(nullptr, IDC_ARROW);  
	wc.hbrBackground = (HBRUSH)COLOR_WINDOW;  
	wc.lpfnWndProc = WindowProcessMessages;  
	RegisterClass(&wc);  
  
	// Craeat the App's Main Window  
	CreateWindow(CLASS_NAME, "WinDraw Main Window",  
		WS_OVERLAPPEDWINDOW | WS_VISIBLE,	// Start On Top and Visible  
		CW_USEDEFAULT,	// initial X position (default)  
		CW_USEDEFAULT,  // initial Y position (default)  
		800, 600,		// Width (x) & Height (y)  
		nullptr, nullptr, nullptr, nullptr);  // Last 4 args unused  
  
	// Window Message Processing Loop  
	MSG msg{};  // Structure holds the windows message  
	while (GetMessage(&msg, nullptr, 0, 0)) {  
		TranslateMessage(&msg);  
		DispatchMessage(&msg);  
	}  
  
	// shut down GDI  
	Gdiplus::GdiplusShutdown(gdiplusToken);  
	return 0;  
}  
  
LRESULT CALLBACK WindowProcessMessages(HWND hwnd, UINT msg, WPARAM param, LPARAM lparam) {  
	HDC hdc;  
	PAINTSTRUCT ps;  
	const RECT* lprcUpdate = nullptr;  
  
	switch (msg) {  
		case WM_PAINT:		  
			// Draw entities within BeginPaint and EndPaint fn calls.  
			hdc = BeginPaint(hwnd, &ps);  
			drawBoard(hdc, hwnd);  
			EndPaint(hwnd, &ps);  
			return 0;  
		case WM_DESTROY:  
			// Done, get out.  
			PostQuitMessage(0);  
			return 0;  
		default:  
			// Fall through to default handler  
			return DefWindowProc(hwnd, msg, param, lparam);  
	}	  
}  
  
void drawBoard(HDC hdc, HWND hwnd){  
	Gdiplus::Graphics gf(hdc);  
	Gdiplus::Pen Blackpen(Gdiplus::Color(255, 0, 0, 0));  // Black pen  
	Gdiplus::SolidBrush Bluebrush(Gdiplus::Color(255, 0, 0, 210));  // Dark Blue brush  
	Gdiplus::SolidBrush Whitebrush(Gdiplus::Color(255, 255, 255, 255));  // White brush, used to clear the screen (experiment)  
  
	double pctWinFill = 0.8;  
	int winHeight = 600, winWidth = 800;  // these are the height and width of the main window; they are determinf using a GetWindowRect function on a passed HWND  
	int bdULX, bdULY;	// these are the upper-left corner X & Y values for the overall board (these won't change once set)  
	int bdEdgeSize;		// This is the edge length of the overall board; since the board is square, we need onlt one variable for both length and width.  
	int sqULX, sqULY; // these are the upper-left corner X & Y values for each board white/black square (these will change as the board is drawn)  
	int sqEdgeSize;  // This is the edge length of the individual squares; again, since the board is square, we need only one variable here too.  
	int row=1; // this is a current the row (1 origin)  
	int col=1; // this is a current the col (1 origin)  
	RECT rect;  // used the querry main window for actual height and width upon resize/redraw...  
  
	// Get the main window size using the past HWND  
	GetWindowRect(hwnd, &rect);  
	winHeight = (rect.bottom-rect.top);  
	winWidth = (rect.right-rect.left);  
	  
	// The board must be kept square; so it's size is limited by the smaller of the height and width of the main window, multiplied by the percentage fill.  
	bdEdgeSize = (int)(pctWinFill * winHeight);			// We use the height as default because it's the smaller of the two by default (as set in main window).    
	if (winHeight > winWidth) {						// but, we will change to using the width, if the height turns out to be larger.   
		bdEdgeSize = (int)(pctWinFill * winWidth);  // cast as int to suppress warning as we are multiplying an int by a float and assigning to an int.  
	}  
  
	//Calculate the largest square size we can use, then adjust board accordignly (this is done because the %fill of screen H&W may not divide evenly by 8.)   
	sqEdgeSize = bdEdgeSize / 8;					// Get the largest square possible  (!!!MAGIC NUMBER!!! <-AGAIN, CHANGE IN DISTRIBUTION)  
	bdEdgeSize = 8 * sqEdgeSize;					// force the board(er) to match the squaresize array perfectly  
	  
	// The first square always coincides with the ULX of the board, then changes thereafter as the board gets drawn  
	bdULX = (winWidth - bdEdgeSize) / 2;			// set the bdULX of the board  
	bdULY = (winHeight - bdEdgeSize) / 2;			// set the bdULY   
  
	// Draw the board using squares at 1/8th scale (!!!MAGIC NUMBER!!! <-AGAIN, CHANGE IN DISTRIBUTION)  
	for (sqULY = bdULY, row = 1; row <= 8; sqULY += sqEdgeSize, row += 1) {  
		for (sqULX=bdULX, col=1; col<=8; sqULX += sqEdgeSize, col+=1) {  
			if((row % 2) != (col % 2)) gf.FillRectangle(&Bluebrush, sqULX, sqULY, sqEdgeSize, sqEdgeSize);  
		}  
	}  
	// Drwa border for edge definition  
	gf.DrawRectangle(&Blackpen, bdULX, bdULY, bdEdgeSize, bdEdgeSize);  // >>> MAY CHANGE Use filled rectangle to providing better edge apperance ???  
	  
	return;  
}  

//<END - CODE>

//<BEGIN - VS BUILD MESSAGES>

This is build result:

Rebuild started...
1>------ Rebuild All started: Project: WinDraw_1, Configuration: Debug Win32 ------
1>ChessBoard.cpp
1>MainWin.cpp
1>Generating Code...
1>WinDraw_1.vcxproj -> C:\Users\solar3\Desktop\C++ VS Dev\Projects\WinDraw_1\WinDraw_1\Debug\WinDraw_1.exe

==========
Rebuild All: 1 succeeded, 0 failed, 0 skipped ==========

//<END - VS BUILD MESSAGES>

//<BEGIN - RUN SAMPLE IMAGES>

Board looks OK when first drawn:
132164-chessboard-normal.jpg

Moving/translating window is no problem; making smaller does not update at all (I guess because partial update region is out of scope(?); not sure):
132236-chessboard-upon-moved-around-ok.jpg132157-chessboard-upon-resized-smaller.jpg

What it looks like when resized larger by mouse click and drag (note residual artifact of smaller board and partial at rescaled size):
132235-chessboard-upon-resizing.jpg

And after multiple resizings:
132217-chessboard-upon-multiple-resizings.jpg

If window with artifacts is minimized and then restored, it repaints properly:
132188-chessboard-upon-min-and-rest-post-mult-resize.jpg

//<END - RUN SAMPLE IMAGES>

//<BEGIN - DEBUG OUTPUT MESSAGES>

This is the Debug Output up to Exit:
WinDraw_1.exe' (Win32): Loaded 'C:\Users\solar3\Desktop\C++ VS Dev\Projects\WinDraw_1\WinDraw_1\Debug\WinDraw_1.exe'. Symbols loaded.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\ntdll.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\kernel32.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\KernelBase.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Program Files\Norton Security\NortonData\22.21.6.53\Definitions\BASHDefs\20210913.004\UMEngx86.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\winsxs\x86_microsoft.windows.gdiplus_6595b64144ccf1df_1.1.7601.24542_none_5c0717c7a00ddc6d\GdiPlus.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\msvcrt.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\user32.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\gdi32.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\lpk.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\usp10.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\advapi32.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\sechost.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\rpcrt4.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\sspicli.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\cryptbase.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\ole32.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\vcruntime140d.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\ucrtbased.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\api-ms-win-core-localization-l1-2-0.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\api-ms-win-core-processthreads-l1-1-1.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\api-ms-win-core-file-l1-2-0.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\api-ms-win-core-timezone-l1-1-0.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\api-ms-win-core-file-l2-1-0.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\api-ms-win-core-synch-l1-2-0.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\imm32.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\msctf.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\Jaksta\AC\x86\jaudcap.dll'.
2780) CheckProcessInRegistry: Got include list but no match was found.
2780) IsProcessAllowed(C:\Users\solar3\Desktop\C++ VS Dev\Projects\WinDraw_1\WinDraw_1\Debug\WinDraw_1.exe) (80004005)
2780) DllMain(ProcDetach) [OK]
2780) DSound_Unhook
2780) MCIWave_Unhook
2780) AudioClient_Unhook
2780) CAudioStreamMgr::Shutdown
'WinDraw_1.exe' (Win32): Unloaded 'C:\Windows\Jaksta\AC\x86\jaudcap.dll'
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\uxtheme.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\dwmapi.dll'.
'WinDraw_1.exe' (Win32): Loaded 'C:\Windows\SysWOW64\ole32.dll'.
'WinDraw_1.exe' (Win32): Unloaded 'C:\Windows\SysWOW64\ole32.dll'
The thread 0x25d0 has exited with code 1 (0x1).
The thread 0x2dbc has exited with code 0 (0x0).
The program '[10112] WinDraw_1.exe' has exited with code 0 (0x0).

//<END - DEBUG OUTPUT MESSAGES>
//=============================================================================================================

Windows API - Win32
Windows API - Win32
A core set of Windows application programming interfaces (APIs) for desktop and server applications. Previously known as Win32 API.
2,511 questions
C++
C++
A high-level, general-purpose programming language, created as an extension of the C programming language, that has object-oriented, generic, and functional features in addition to facilities for low-level memory manipulation.
3,626 questions
Visual Studio Debugging
Visual Studio Debugging
Visual Studio: A family of Microsoft suites of integrated development tools for building applications for Windows, the web and mobile devices.Debugging: The act or process of detecting, locating, and correcting logical or syntactical errors in a program or malfunctions in hardware. In hardware contexts, the term troubleshoot is the term more frequently used, especially if the problem is major.
966 questions
0 comments No comments
{count} votes

1 answer

Sort by: Most helpful
  1. Castorix31 83,101 Reputation points
    2021-09-15T09:18:39.277+00:00

    A way =>

    132372-grid-test.gif

    #include <windows.h>  
    #include <tchar.h>  
      
    #include "gdiplus.h"  
    using namespace Gdiplus;  
    #pragma comment(lib, "gdiplus.lib")  
      
    #include <uxtheme.h> // BeginBufferedPaint  
    #pragma comment (lib, "uxtheme")  
      
    #pragma comment(linker,"\"/manifestdependency:type='win32' \  
    name='Microsoft.Windows.Common-Controls' version='6.0.0.0' \  
    processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")  
      
    HINSTANCE hInst;  
    LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);  
    int nWidth = 600, nHeight = 600;  
    int nXMax = 8, nYMax = 8;  
      
    int APIENTRY wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance, _In_ LPWSTR lpCmdLine, _In_ int nCmdShow)  
    {  
    	GdiplusStartupInput gdiplusStartupInput;  
    	ULONG_PTR gdiplusToken;  
    	GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, NULL);  
      
    	hInst = hInstance;  
    	WNDCLASSEX wcex =  
    	{  
    		sizeof(WNDCLASSEX), CS_HREDRAW | CS_VREDRAW, WndProc, 0, 0, hInst, LoadIcon(NULL, IDI_APPLICATION),  
    		LoadCursor(NULL, IDC_ARROW), (HBRUSH)(COLOR_WINDOW + 1), NULL, TEXT("WindowClass"), NULL,  
    	};  
    	if (!RegisterClassEx(&wcex))  
    		return MessageBox(NULL, TEXT("Cannot register class !"), TEXT("Error"), MB_ICONERROR | MB_OK);  
    	int nX = (GetSystemMetrics(SM_CXSCREEN) - nWidth) / 2, nY = (GetSystemMetrics(SM_CYSCREEN) - nHeight) / 2;  
    	HWND hWnd = CreateWindowEx(0, wcex.lpszClassName, TEXT("Test"), WS_OVERLAPPEDWINDOW & ~WS_MAXIMIZEBOX, nX, nY, nWidth, nHeight, NULL, NULL, hInst, NULL);  
    	if (!hWnd)  
    		return MessageBox(NULL, TEXT("Cannot create window !"), TEXT("Error"), MB_ICONERROR | MB_OK);  
    	ShowWindow(hWnd, SW_SHOWNORMAL);  
    	// for same width/height  
    	RECT rect, clientRect;  
    	GetWindowRect(hWnd, &rect);  
    	GetClientRect(hWnd, &clientRect);  
    	int nAddX = (rect.right - rect.left) - clientRect.right;  
    	int nAddY = (rect.bottom - rect.top) - clientRect.bottom;  
    	int nBottom = rect.bottom = rect.top + (LONG)((float)(rect.right - rect.left - nAddX) / 1) + nAddY;  
    	int nRight= rect.left + (LONG)((float)(rect.bottom - rect.top - nAddY) * 1) + nAddX;  
    	SetWindowPos(hWnd, NULL, 0, 0, nRight - rect.left, nBottom -rect.top, SWP_NOMOVE | SWP_NOOWNERZORDER | SWP_SHOWWINDOW);  
    	UpdateWindow(hWnd);  
    	MSG msg;  
    	while (GetMessage(&msg, NULL, 0, 0))  
    	{  
    		TranslateMessage(&msg);  
    		DispatchMessage(&msg);  
    	}  
    	return (int)msg.wParam;  
    }  
      
    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)  
    {  
    	switch (message)  
    	{  
    	case WM_CREATE:  
    	{  
    		return 0;  
    	}  
    	break;	  
    	case WM_PAINT:  
    	{  
    		PAINTSTRUCT ps;  
    		HDC hDC = BeginPaint(hWnd, &ps);  
    		HDC hDCTarget = hDC;  
    		HDC hBufferedDC = NULL;  
    		RECT rect;  
    		GetClientRect(hWnd, &rect);  
    		HPAINTBUFFER hBufferedPaint = BeginBufferedPaint(hDCTarget, &rect, BPBF_TOPDOWNDIB, 0, &hBufferedDC);  
    		if (hBufferedPaint == NULL)  
    			hBufferedDC = hDCTarget;  
    		Graphics* g = new Graphics(hBufferedDC);		  
    		if (g && g->GetLastStatus() == Ok)  
    		{  
    			SolidBrush blueBrush(Color::Blue);  
    			SolidBrush whiteBrush(Color::White);  
    			//g->FillRectangle(&whiteBrush, 0, 0, rect.right, rect.bottom);  
    			float nCellSize = (float)(rect.right - rect.left) / (float)nXMax;  
    			for (int x = 0; x < nXMax; x++)  
    			{  
    				for (int y = 0; y < nYMax; y++)  
    				{  
    					if ((x % 2 == 0 && y % 2 == 0) || (x % 2 != 0 && y % 2 != 0))  
    						g->FillRectangle(&blueBrush, x * nCellSize, y * nCellSize, nCellSize, nCellSize);  
    					else  
    						g->FillRectangle(&whiteBrush, x * nCellSize, y * nCellSize, nCellSize, nCellSize);  
    				}  
    			}  
    			delete g;  
    		}  
    		EndBufferedPaint(hBufferedPaint, TRUE);  
    		EndPaint(hWnd, &ps);  
    		return 0;  
    	}  
    	break;  
    	case WM_SIZING:  
    	{  
    		float nAspectRatio = 1;  
    		RECT rect, clientRect;  
    		GetWindowRect(hWnd, &rect);  
    		GetClientRect(hWnd, &clientRect);  
    		int nAddX = (rect.right - rect.left) - clientRect.right;  
    		int nAddY = (rect.bottom - rect.top) - clientRect.bottom;  
    		LPRECT r = LPRECT(lParam);  
    		switch (wParam)  
    		{  
    		case WMSZ_LEFT:  
    		case WMSZ_BOTTOMLEFT:  
    		case WMSZ_BOTTOMRIGHT:  
    		case WMSZ_RIGHT:  
    			r->bottom = r->top + (LONG)((float)(r->right - r->left - nAddX) / nAspectRatio) + nAddY;  
    			break;  
    		case WMSZ_TOPRIGHT:  
    		case WMSZ_TOP:  
    		case WMSZ_BOTTOM:  
    			r->right = r->left + (LONG)((float)(r->bottom - r->top - nAddY) * nAspectRatio) + nAddX;  
    			break;  
    		case WMSZ_TOPLEFT:  
    			r->left = r->right - (LONG)((float)(r->bottom - r->top) * nAspectRatio) + nAddX;  
    			break;  
    		}  
    		if (r->bottom - r->top + nAddY <= 200)  
    			r->bottom = r->top + 200 - nAddY - 2;  
    		return TRUE;  
    	}  
    	break;  
    	case WM_DESTROY:  
    	{  
    		PostQuitMessage(0);  
    		return 0;  
    	}  
    	break;  
    	default:  
    		return DefWindowProc(hWnd, message, wParam, lParam);  
    	}  
    	return 0;  
    }  
      
      
    
    0 comments No comments