0

I have created a simple Windows console program in C++ that does some processing and a window with a progress bar which updates as processing advances. The problem is that the progress bar doesn't update.

Below there is the code of my sample application.

#include <iostream>
#include <stdlib.h>
#include "atlstr.h"
#include "pch.h"
#include <string.h>
#include "tchar.h"
#include <vector>
#define ISOLATION_AWARE_ENABLED 1
#include <windows.h>
#include <commctrl.h>

using namespace std;

#define WM_UPDATEPROGRESS WM_USER + 1

typedef struct {
    HWND    window;
    HWND    hwndPB;
} OPERATION_INFO;

void DoOperation(LPVOID lpThreadParams) {
    //reading ThreadParams
    OPERATION_INFO *pOperationInfo = (OPERATION_INFO *)lpThreadParams;
    //run a test loop that updates bar
    for (int i = 1; i <= 10; i += 1) {
        // do something
        Sleep(500); // dummily waiting some time
        // send message to update progress bar
        PostMessage(
            pOperationInfo->window, WM_UPDATEPROGRESS, 0, NULL);
    }
}

LONG_PTR CALLBACK WindowProcedure(
    HWND window, unsigned int msg, WPARAM wp, LPARAM lp)
{
    RECT rcClient;
    HWND hwndPB = NULL;
    OPERATION_INFO operationInfo;
    static bool flag_activate = FALSE;

    switch (msg)
    {
    case WM_CREATE:

        // create progress bar
        GetClientRect(window, &rcClient);
        hwndPB = CreateWindowEx(0, PROGRESS_CLASS,
            (LPCWSTR)NULL, (WS_CHILD | WS_VISIBLE),
            20,
            20,
            rcClient.right - rcClient.left - 40,
            20,
            window, (HMENU)0, GetModuleHandle(0), NULL);

        // progress bar settings
        SendMessage(hwndPB, PBM_SETRANGE, 0, MAKELPARAM(0, 100));
        SendMessage(hwndPB, PBM_SETPOS, (WPARAM)0, 0);
        SendMessage(hwndPB, PBM_SETSTEP, (WPARAM)10, 0);

    case WM_ACTIVATEAPP:
        // if message already fired then do nothing:
        // because in this message we have code to initialize thread 
        // that updates progress bar and the thread initialization, 
        // obviously, must be done once.
        if (flag_activate)
            break;
        else
        {
            // store message MW_ACTIVEAPP fired
            flag_activate = TRUE;

            // initialize thread
            OPERATION_INFO operationInfo = { window, hwndPB };
            HANDLE threadHandle;
            // make thread call DoOperation routine
            threadHandle = CreateThread(
                NULL, 0, 
                (LPTHREAD_START_ROUTINE)DoOperation, 
                (LPVOID)&operationInfo, 0, 0);
        }
        break;

    case WM_UPDATEPROGRESS:
        //get info with HWND of the progress bar to update
        //update the progress bar
        SendMessage(hwndPB, PBM_STEPIT, NULL, 0);
        //release memory
        break;

    case WM_DESTROY:
        PostQuitMessage(0);
        return 0L;

    case WM_LBUTTONDOWN:
        break;

    default:
        return DefWindowProc(window, msg, wp, lp);
    }
}

int main()
{
    // Window creation

    // structure initialization
    LPCWSTR myclass = L"myclass";
    WNDCLASSEX wndclass = { 
        sizeof(WNDCLASSEX), CS_DBLCLKS, 
        WindowProcedure, 0, 0, GetModuleHandle(0), 
        LoadIcon(0,IDI_APPLICATION), LoadCursor(0,IDC_ARROW), 
        HBRUSH(COLOR_WINDOW + 1), 0, myclass, 
        LoadIcon(0,IDI_APPLICATION) };
    // preliminar action
    if (RegisterClassEx(&wndclass))
    {
        // create window
        HWND window = CreateWindowEx(0, myclass, L"Processing",
            WS_OVERLAPPED | WS_SYSMENU,
            CW_USEDEFAULT, CW_USEDEFAULT,
            300, 150,
            0, 0, GetModuleHandle(0), 0);
        // if OK
        if (window)
        {
            // show window
            ShowWindow(window, SW_SHOWDEFAULT);
            MSG msg;
            while (GetMessage(&msg, 0, 0, 0)) 
                DispatchMessage(&msg);
        }
    }
}

I expect that the progress bar updates itself every second but this doesn't happen: the progress bar does nothing.

What's wrong?

drescherjm
  • 10,365
  • 5
  • 44
  • 64
guidros
  • 41
  • 3
  • 1
    Warning: If you are using Visual Studio, you will find the placement of `#include "pch.h"` to either be unnecessary or fatal. If precompiled headers are in use, `#include "pch.h"` [MUST be the first non-comment in the file](https://stackoverflow.com/questions/54121917/what-is-pch-h-and-why-is-it-needed-to-be-included-as-the-first-header-file). Everything before it is silently ignored. If precompiiled headers are not in use, there is no need to include pch.h. – user4581301 May 05 '19 at 16:41
  • I saw a similar problem years ago: no code was calling Invalidate, so it never called Paint, so it never updated anything. Is that STEPIT message doing everything necessary? – Zan Lynx May 05 '19 at 16:45
  • 2
    `hwndPB` is a local variable. You would have noticed this if you had set a breakpoint on your `WM_UPDATEPROGRESS` handler. – Raymond Chen May 05 '19 at 19:08
  • Other problems include the cast `(LPTHREAD_START_ROUTINE)DoOperation`. If `DoOperation` has the wrong signature, as the compiler told you, you must fix the signature. Your cast merely tells a lie to the compiler, namely you claim that `DoOperation` is `LPTHREAD_START_ROUTINE` when it is not. – David Heffernan May 06 '19 at 08:11

2 Answers2

2

Your code has two similar problems:

  1. In the WM_ACTIVATEAPP handler you are assigning CreateThread(...)'s lpParameter parameter, the variable to be passed to the thread, to a pointer to a local struct defined on the stack. As soon as the else-clause that call is in ends that struct is out of scope and the pointer is invalid.

  2. In the WM_UPDATEPROGRESS handler, you are SendMessage-ing to a window handle that is an undefined variable. That variable is only defined in your WM_CREATE handler.

In real code you'd want to handle these problems in more robust ways but for the purposes of getting this code to do what you expect, make your two state variables static:

static HWND hwndPB = NULL;
static OPERATION_INFO operationInfo;

and modify the WM_ACTIVATEAPP handler so that it passes a pointer to this static variable rather than to its own local OPERATION_INFO.

Basically you need to remember that WNDPROCs are normal C functions where normal scope rules apply. They are not any kind of class definition. If you want to maintain state associated with some window that is good across calls to its window procedure you must maintain such state explicitly.

jwezorek
  • 8,592
  • 1
  • 29
  • 46
  • Thanks jwezorek. I see what's wrong. Could you tell me more about the more robust ways to handle these problems? – guidros May 06 '19 at 16:36
  • 1
    Well the problem with making them static is that that solution is only good for one instance of the window. If you want to have some data that is associated with each instance of a Window class for which there are multiple instances, one way to do it is to dynamically allocate a struct and store a pointer to it "user data" via SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR) pMyData) – jwezorek May 06 '19 at 16:43
  • Another thing is you dont actually need to store a handle to the progress bar child control at all. If you create it with a child id, you can recover its HWND via GetDlgItem(...) – jwezorek May 06 '19 at 16:46
-2

I had the same problem, but when I was working in Delphi. The most likely cause is that the processing takes an important amount of time and, because the processing and the bar updating are happening on the same thread, the bar fills up to 100% after the processing is finished.

Solution: try updating the progress bar in a separate thread. The update function is called from the main thread, each second (or as often as you wish).

Bogdan Doicin
  • 2,342
  • 5
  • 25
  • 34
  • 4
    UI has to be run out of the main thread. Solution is to pump the message queue, or run the long calculation in a different thread. – David Heffernan May 05 '19 at 18:14