question

TomS-2158 avatar image
0 Votes"
TomS-2158 asked Castorix31 answered

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

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>
//=============================================================================================================





c++windows-api-generalvs-debugging
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.

1 Answer

Castorix31 avatar image
0 Votes"
Castorix31 answered

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;
 }




grid-test.gif (685.1 KiB)
5 |1600 characters needed characters left characters exceeded

Up to 10 attachments (including images) can be used with a maximum of 3.0 MiB each and 30.0 MiB total.