none
How do I force window to repaint immediately in C++? RRS feed

  • Question

  • Hello,

    I am using Visual Studio 2017 and working on a C++ windows application in which I am trying to get a window to update on one second intervals via a timer function I am running in the background of Winmain.  After reading through many threads of similar questioning I understand the following to be true:

    -Windows need to be invalidated to force a WM_PAINT command (at least some portion of them).

    -The OS by default throws events at applications such as WM_PAINT in the case of a WM_RESIZE or similar.

    -Calling RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW) outside of WM_PAINT (as in Winmain - where I am using the command) should force an invalidate and redraw situation immediately. 

    Unfortunately I have found this not to be the case.  The window graphic will redraw itself beautifully in one second intervals only when I am moving the window or manipulating the cursor over it - presumably forcing an OS based event to be thrown at the event handler.  If no interaction with the window takes place things just sit still.  I know the data arrays that render the graphics are all updating in the background still.  Out of ideas.  Please help!



    • Edited by JigglyBit Friday, November 30, 2018 5:21 PM
    Thursday, November 29, 2018 9:39 PM

Answers

  • When you post code please use the "Insert Code Block" button.

    I think the problem is that this code -

    while (GetMessage(&msg, nullptr, 0, 0))
    //this loop is the main program loop and runs indepentently of event driven items thrown from OS
    {
    checkForRedraw();
    calculateSine(1);
    if (refreshWindowTimer) {
    
    //ValidateRect(hWnd, NULL);
    //InvalidateRect(hWnd, NULL, TRUE);
    //UpdateWindow(hWnd);
    
    RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
    
    }
    
    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
    }
    is trying to draw within the message loop.  GetMessage blocks when there are no messages in the queue.  That is the reason why drawing doesn't happen until you jiggle the mouse or do something that causes GetMessage to return.

    • Marked as answer by JigglyBit Tuesday, December 11, 2018 10:00 PM
    Tuesday, December 4, 2018 12:00 AM

All replies

  • Thursday, November 29, 2018 10:00 PM
  • Thank you for the suggestion.  I believe the RedrawWindow() with those two qualifiers is synonymous with the InvalidateRect() and UpdateWindow() pair.  I have tried the pair in the program and it is exhibiting the exact same behavior.  The behavior I am trying to figure out is that although those statements do what they are supposed to do which is clear a segment of the screen and update it, they don't do it at the instant they are called.  It takes continuously jogging the mouse over the window or minimizing it and restoring it to force an update.  Almost like it is waiting exclusively for an OS handled event to trigger it.
    Friday, November 30, 2018 5:05 PM
  • I am using Visual Studio 2017 and working on a C++ windows application in which I am trying to get a window to update on one second intervals via a timer function I am running in the background of Winmain.  After reading through many threads of similar questioning I understand the following to be true:
    Are you running the timer in a secondary thread and doing the drawing from that thread instead of from the UI thread that owns the window?
    Friday, November 30, 2018 5:57 PM
  • RLWA32, The timer is running in Winmain in the message loop (I think we can consider it's existence irrelevant with the exception that it generates a flag of periodicity 1 second to trigger a paint event).  I am no expert on flow of UI since I am new to this environment but I tried to bypass ownership concerns by temporarily declaring the hWnd as a global object just for testing.  The exact same happens whether I call drawing from Winmain() or within the WndProc() with handle to window passed.  If I declare the window in InitInstance() I can access it from WndProc(hWnd,...) but not Winmain() as I would expect so I think this is all resolving in proper scope.

    I know that the WM_PAINT is triggered by system events.  I can't seem to reproduce that using my own program driven events though.  It's like any WM_PAINT that i am *supposed to be generating are just being ignored.

    Friday, November 30, 2018 6:23 PM
  • Instead of us guessing how you have implemented your timer and related attempts to immediately redraw a window share a small demo that reliably reproduces the issue.  That way we can see exactly what is happening.
    Saturday, December 1, 2018 2:40 PM
  • Sure thing.  This is basically it.  The main hWnd is global for now.  Most all the variables are too while I experiment.  checkForRedraw() is global.  It works fine and sets a flag every second.  wWinMain and WndProc are just the stock functions created with the new program default instance.  The kicker is that if I eliminate checkForRedraw() and just run the RedrawWindow() so it runs unconditionally in the main loop it still does the same.  Outside system events throw a WM_PAINT so this pretty much narrows it down to I have to be doing something wrong with RedrawWindow().

    int checkForRedraw() {
    //windowUpdateTimer = time(0);
    mostRecent = time(&windowUpdateTimer);
    if (lastStored != mostRecent) {
    lastStored = mostRecent;
    refreshWindowTimer = TRUE;
    }
    else {
    refreshWindowTimer = FALSE;
    }
    return 0;
    }

    int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
    _In_opt_ HINSTANCE hPrevInstance,
    _In_ LPWSTR    lpCmdLine,
    _In_ int       nCmdShow)
    {
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    // TODO: Place code here.
    ReadFile();

    //InvalidateRect(hWnd, &TextWindow, 1);

    // Initialize global strings
    LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
    LoadStringW(hInstance, IDC_WINDOWSPROJECT1, szWindowClass, MAX_LOADSTRING);
    MyRegisterClass(hInstance);

    // Perform application initialization:

    calculateSine(1);
    calculateCosine(1);

    if (!InitInstance(hInstance, nCmdShow))
    {
    return FALSE;
    }

    HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT1));

    MSG msg;
    //Sleep(3000);
    // Main message loop:
    while (GetMessage(&msg, nullptr, 0, 0)) //this loop is the main program loop and runs indepentently of event driven items thrown from OS
    {
    checkForRedraw();
    calculateSine(1);
    if (refreshWindowTimer) {

    //ValidateRect(hWnd, NULL);
    //InvalidateRect(hWnd, NULL, TRUE);
    //UpdateWindow(hWnd);

    RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);

    }

    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
        }


    LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
    {

    switch (message)
        {
        case WM_COMMAND:
            {
                int wmId = LOWORD(wParam);
    int wmNotification = HIWORD(wParam);
                // Parse the menu selections:
                switch (wmId)
                {
                case IDM_ABOUT:
                    DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
                    break;
                case IDM_EXIT:
                    DestroyWindow(hWnd);
                    break;
    case BN_CLICKED:
    wParamValue = DWORD(lParam);
    swprintf_s(wParamTextValueString, 10, L"%d", wParamValue);
        
    ReadFile();

    MessageBox(NULL, _T("Test."), L"Hi.", NULL);  //wParamTextValueString
    break;
                default:
                    return DefWindowProc(hWnd, message, wParam, lParam);
                }
            }
            break;

    case WM_RBUTTONDOWN:
    {
    MessageBox(NULL, _T("Right button clicked."), _T("Yep."), NULL);
    break;
    }
        case WM_PAINT:
            {
                PAINTSTRUCT ps;
                HDC hdc = BeginPaint(hWnd, &ps); //create paint instance handle
    float span = returnMaxAmplitude() - returnMinAmplitude();
    float origin = 100 + 300/span;
    TextWindow.left = 150; TextWindow.right = 250; TextWindow.top = 410; TextWindow.bottom = 440;

    SelectObject(hdc, GetStockObject(BLACK_PEN));
    Rectangle(hdc, 40,100,640,400);

    Seconds = time(&CurrentTime);
    swprintf_s(timeBuffer, numDigits(Seconds)+6, L"%i", Seconds); //UNIX UTC time
    swprintf_s(xMinBuffer, 10, L"%f", returnMinAmplitude()); //min value in sample array
    swprintf_s(xMaxBuffer, 10, L"%f", returnMaxAmplitude()); //max value in sample array
    Rectangle(hdc, TextWindow.left, TextWindow.top, TextWindow.right, TextWindow.bottom);
    DrawText(hdc, timeBuffer, numDigits(Seconds), &TextWindow, DT_CENTER | DT_SINGLELINE | DT_VCENTER);   //UNIX UTC time

    TextWindow.left = 20; TextWindow.right = 150;  TextWindow.top = 410; TextWindow.bottom = 440;
    DrawText(hdc, L"UNIX time (UTC): ", 18, &TextWindow, DT_LEFT | DT_SINGLELINE | DT_VCENTER);

    TextWindow.left = 5; TextWindow.right = 40; TextWindow.top = 390; TextWindow.bottom = 400;
    DrawText(hdc, xMinBuffer, 4/*numDigits(returnMinAmplitude())*/, &TextWindow, DT_CENTER | DT_SINGLELINE | DT_VCENTER);

    TextWindow.left = 5; TextWindow.right = 40; TextWindow.top = 100; TextWindow.bottom = 110;
    DrawText(hdc, xMaxBuffer, 4/*numDigits(returnMaxAmplitude())*/, &TextWindow, DT_CENTER | DT_SINGLELINE | DT_VCENTER);


    SelectObject(hdc, GetStockObject(DC_PEN));
    SetDCPenColor(hdc, RGB(200, 200, 200));
    MoveToEx(hdc, 25, origin, NULL);
    LineTo(hdc, 640, origin);
    SetDCPenColor(hdc, RGB(0, 20, 250));
    MoveToEx(hdc, 40, 250, NULL);
    for (int i = 0; i < 100; i++) {
    LineTo(hdc, PointArray[i].TimeValue, origin - (PointArray[i].SampleValue / span) * 300);
    }
    SetDCPenColor(hdc, RGB(250, 0, 20));
    MoveToEx(hdc, 40, 100, NULL);
    for (int i = 0; i < 100; i++) {
    LineTo(hdc, PointArray2[i].TimeValue, 100 + 300 / span - (PointArray2[i].SampleValue / span) * 300);
    }






    TextWindow.left = 150; TextWindow.right = 250; TextWindow.top = 410; TextWindow.bottom = 440; //restore to default area selection
    DeleteObject(hdc);
                EndPaint(hWnd, &ps);
            }
            break;
        case WM_DESTROY:
            PostQuitMessage(0);
            break;
        default:
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
        return 0;
    }

    Monday, December 3, 2018 9:22 PM
  • When you post code please use the "Insert Code Block" button.

    I think the problem is that this code -

    while (GetMessage(&msg, nullptr, 0, 0))
    //this loop is the main program loop and runs indepentently of event driven items thrown from OS
    {
    checkForRedraw();
    calculateSine(1);
    if (refreshWindowTimer) {
    
    //ValidateRect(hWnd, NULL);
    //InvalidateRect(hWnd, NULL, TRUE);
    //UpdateWindow(hWnd);
    
    RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE | RDW_UPDATENOW);
    
    }
    
    if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
            {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
            }
    }
    is trying to draw within the message loop.  GetMessage blocks when there are no messages in the queue.  That is the reason why drawing doesn't happen until you jiggle the mouse or do something that causes GetMessage to return.

    • Marked as answer by JigglyBit Tuesday, December 11, 2018 10:00 PM
    Tuesday, December 4, 2018 12:00 AM
  • I tried inserting an || with BOOL TRUE in that while().  Same issue.  Also tried moving the RedrawWindow() into WndProc().  Nothing.  Maybe I am going about this the wrong way.  I get the event driven nature of TranslateMessage() & DispatchMessage().  While loop above just ignores everything until there is something in &msg and then the WndProc() is called to handle it.  But what if I want to flag my own message event to trigger this redraw?  Since the while loop is effectively the main program in the default construct, it would seem I need to create some sort of parallel thread and throw messages at this loop to handle?  I guess a better question would be does anyone have an example of how this could/should be handled?
    Tuesday, December 4, 2018 8:17 PM
  • LIke I said, remove the drawing code from the message loop.  Or use PeekMessage with PM_REMOVE which doesn't block.

    Also, take a look at the code for a game loop.  See https://stackoverflow.com/questions/1800250/is-there-a-better-way-to-create-this-game-loop-c-windows

    • Edited by RLWA32 Tuesday, December 4, 2018 9:01 PM added link
    Tuesday, December 4, 2018 8:28 PM
  • Thanks.  I marked the original as the answer.  Was able to get it to work.  I did notice something concerning with this approach though.  When I close the application window out as a result of a WM_QUIT being posted presumably, it doesn't seem to terminate the application but rather just close the window.  I observe that it is still running in the debugging environment.  Can you take a look at this and let me know what I may be doing wrong?

    	HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT1));
    
    	MSG msg;
    	
    	//Main message loop
    	while (TRUE)
    	{
    		while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE))
    		{
    			if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
    			{
    				TranslateMessage(&msg);
    				DispatchMessage(&msg);
    			}
    		}
    		if (msg.message == WM_QUIT)
    			break;
    
    		//rest of non-message stuff	
    		checkForRedraw();
    		calculateSine(1);
    		if (refreshWindowTimer) {
    			//ValidateRect(hWnd, NULL);
    			//InvalidateRect(hWnd, NULL, TRUE);
    			RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE  RDW_UPDATENOW);
    			refreshWindowTimer = FALSE;
    		}
    	
    	}
    	return (int) msg.wParam;
    }


    Tuesday, December 11, 2018 10:09 PM
  • Thanks.  I marked the original as the answer.  Was able to get it to work.  I did notice something concerning with this approach though.  When I close the application window out as a result of a WM_QUIT being posted presumably, it doesn't seem to terminate the application but rather just close the window.  I observe that it is still running in the debugging environment.  Can you take a look at this and let me know what I may be doing wrong?

    	HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_WINDOWSPROJECT1));
    
    	MSG msg;
    	
    	//Main message loop
    	while (TRUE)
    	{
    		while (PeekMessageA(&msg, NULL, 0, 0, PM_REMOVE))
    		{
    			if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
    			{
    				TranslateMessage(&msg);
    				DispatchMessage(&msg);
    			}
    		}
    		if (msg.message == WM_QUIT)
    			break;
    
    		//rest of non-message stuff	
    		checkForRedraw();
    		calculateSine(1);
    		if (refreshWindowTimer) {
    			//ValidateRect(hWnd, NULL);
    			//InvalidateRect(hWnd, NULL, TRUE);
    			RedrawWindow(hWnd, NULL, NULL, RDW_INVALIDATE  RDW_UPDATENOW);
    			refreshWindowTimer = FALSE;
    		}
    	
    	}
    	return (int) msg.wParam;
    }


    Move the test for WM_QUIT to right after the call to PeekMessage, inside the inner while loop.

    Make whatever other changes are needed to break out of the loops and end the program when WM_QUIT is received.

    Tuesday, December 11, 2018 11:10 PM
  • Perfect.  Thanks!
    Wednesday, December 12, 2018 7:23 PM