Working with timers in C++

Hello C++ experts!

I'm still new here and I programming in C++ only a few days ago, but I have some programming knowledge from other languages (JavaScript, FreeBasic).
In my first little C++ program I would like move a rectangle, and create and delete small rectangles, so this is a graphical program. I use Code::Blocks for this, without any external graphics libraries (SDL, graphics.h, etc.).
Everything is OK, except that the timer isn't work and without the timer the things happen too fast: the moving of the big rectangle and creating the small rectangles are too fast. But I don't want it; I want a smooth movement in every 5 seconds.
What's wrong with my code?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
  #if defined(UNICODE) && !defined(_UNICODE)
    #define _UNICODE
#elif defined(_UNICODE) && !defined(UNICODE)
    #define UNICODE
#endif

#include <tchar.h>
#include <windows.h>
#include <vector>

/*  Declare Windows procedure  */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);

/*  Make the class name into a global variable  */
TCHAR szClassName[ ] = _T("CodeBlocksWindowsApp");

RECT the_window;
int cubex=50,cubey=5;
const unsigned int the_timer=1;
bool can_do=true;

class SmallCube{
    public:
        SmallCube(int xp,int yp, HDC ghandler) {
            xplace = xp;
            yplace = yp;
            gh = ghandler;
        }
        void draw_out() {
            Rectangle(gh,xplace,yplace,xplace+10,yplace+10);
        }
    private:
        int xplace,yplace;
        HDC gh;
};

std::vector<SmallCube> smcubes;

int WINAPI WinMain (HINSTANCE hThisInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR lpszArgument,
                     int nCmdShow)
{
    HWND hwnd;               /* This is the handle for our window */
    MSG messages;            /* Here messages to the application are saved */
    WNDCLASSEX wincl;        /* Data structure for the windowclass */

    /* The Window structure */
    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = WindowProcedure;      /* This function is called by windows */
    wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
    wincl.cbSize = sizeof (WNDCLASSEX);

    /* Use default icon and mouse-pointer */
    wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;                 /* No menu */
    wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
    wincl.cbWndExtra = 0;                      /* structure or the window instance */
    /* Use Windows's default colour as the background of the window */
    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    /* Register the window class, and if it fails quit the program */
    if (!RegisterClassEx (&wincl))
        return 0;

    /* The class is registered, let's create the program*/
    hwnd = CreateWindowEx (
           0,                   /* Extended possibilites for variation */
           szClassName,         /* Classname */
           _T("Arrows: move, CTRL: create a small cube, SPACE: delete the 1st small cube."),       /* Title Text */
           WS_OVERLAPPEDWINDOW, /* default window */
           CW_USEDEFAULT,       /* Windows decides the position */
           CW_USEDEFAULT,       /* where the window ends up on the screen */
           640,                 /* The programs width */
           480,                 /* and height in pixels */
           HWND_DESKTOP,        /* The window is a child-window to desktop */
           NULL,                /* No menu */
           hThisInstance,       /* Program Instance handler */
           NULL                 /* No Window Creation data */
           );

    /* Make the window visible on the screen */
    ShowWindow (hwnd, nCmdShow);

    /* Run the message loop. It will run until GetMessage() returns 0 */
    while (GetMessage (&messages, NULL, 0, 0))
    {
        /* Translate virtual-key messages into character messages */
        TranslateMessage(&messages);
        /* Send message to WindowProcedure */
        DispatchMessage(&messages);
    }

    /* The program return-value is 0 - The value that PostQuitMessage() gave */
    return messages.wParam;
}

/*  This function is called by the Windows function DispatchMessage()  */

LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    HDC gfxhandler = GetDC(hwnd);

    switch (message)                  /* handle the messages */
    {
       case WM_CREATE:
            SetTimer(hwnd,the_timer,5000,NULL);
       break;
       case WM_PAINT:
            GetWindowRect(hwnd, &the_window);
            if (GetAsyncKeyState(VK_LEFT) && cubex-4>0 && can_do==true) {cubex-=4; InvalidateRect(hwnd, NULL, true);};
            if (GetAsyncKeyState(VK_RIGHT) && cubex+4+40<(the_window.right-the_window.left) && can_do==true) {cubex+=4; InvalidateRect(hwnd, NULL, true);};
            if (GetAsyncKeyState(VK_UP) && cubey-4>0 && can_do==true) {cubey-=4; InvalidateRect(hwnd, NULL, true);};
            if (GetAsyncKeyState(VK_DOWN) && cubey+4+40<(the_window.bottom-the_window.top) && can_do==true) {cubey+=4; InvalidateRect(hwnd, NULL, true);};
            if (GetAsyncKeyState(VK_CONTROL) && can_do==true) {smcubes.push_back(SmallCube(cubex,cubey,gfxhandler));};
            if (GetAsyncKeyState(VK_SPACE) && smcubes.size()>0 && can_do==true) {smcubes.erase(smcubes.begin()); InvalidateRect(hwnd, NULL, true);};
            for (int i=0; i<smcubes.size(); i++) {
                smcubes[i].draw_out();
            };
            Rectangle(gfxhandler,cubex,cubey,cubex+40,cubey+40);
        break;
        case WM_TIMER:
            switch (wParam)
            {
                case the_timer: if (can_do==true) {
                        can_do = false;
                    }
                    else {
                        can_do = true;
                    };
                break;
            };
        break;
        case WM_DESTROY:
            KillTimer(hwnd,the_timer);
            ReleaseDC(hwnd,gfxhandler);
            PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
        break;

        default:                      /* for messages that we don't deal with */
            return DefWindowProc (hwnd, message, wParam, lParam);
    }

    return 0;
}
One problem I see you are calling InvalidateRect function inside WM_PAINT which is not the time to do it.

If you want to resize your rectangles (conditionally move them) before painting them you should do it inside WM_SIZE

WM_SIZE is called right before WM_PAINT.

And then do the drawing in WM_PAINT.

Then your WM_PAINT should be simplified to just this:

1
2
3
4
5
6
7
8
9
case WM_PAINT:
    for (int i=0; i<smcubes.size(); i++)
    {
         smcubes[i].draw_out();
    };

    Rectangle(gfxhandler,cubex,cubey,cubex+40,cubey+40);

    return 0; // don't call defaults 


Your WM_SIZE handling should prepare the drawings and do the rest, which should also be simplified by checking can_do just once!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
case WM_SIZE:
      // check if can be done only once here!
      if (can_do)
      {
           GetWindowRect(hwnd, &the_window);

           if (GetAsyncKeyState(VK_LEFT) && cubex-4 > 0)
           {
                  cubex-=4;
           };

           if (GetAsyncKeyState(VK_RIGHT) && cubex+4+40 < (the_window.right-the_window.left))
           {
                 cubex+=4;
           };
      }

// Etc...

      break; // call default procedure 



As you can see, you don't call InvalidateRect at all.

Next problem is you want to put your default handling outside switch statement:


1
2
3
4
5
6
        default:      /* for messages that we don't deal with */
            break;    // calls default bellow
    }

    // this will be called by cases where you break too!
    return DefWindowProc (hwnd, message, wParam, lParam);


ofc. you must update the rest too to not call defaults:

1
2
3
4
5
6
7
8
9
10
case WM_TIMER:
        switch (wParam)
        {
            case the_timer:
               if (can_do==true)
                    can_do = false;

              else can_do = true;
        };
    return 0;   // don't call defaults 


Last edited on
Hello Malibor!

No, I don't want resize the rectangles, I only want a smooth movement of big rectangle with arrow keys.
I tought, I solve this with a timer, when its can_do variable is true. But when this variable is false, the user can't move the rectangle, and the true-false values are change in every 5 second to make slower the movement.
But the timer don't want work: it seems the WM_TIMER doesn't execute and I don't know, why?
No, I don't want resize the rectangles I only want a smooth movement of big rectangle with arrow keys.

Movement IS resizing, saying you don't want to resize them is saying you don't want to move them.

But when this variable is false, the user can't move the rectangle

So this is the sign that the timer indeed works.

You said:
I want a smooth movement in every 5 seconds.

Does that mean the rectangles are moved by user or by the program?

How do you handle mouse input? you may need to update can_do variable each time mouse is down and let user move at speed he wants.


EDIT:
I think the timer is not the right thing for this as you have figured out
Last edited on
I thought, resizing is when you set the size of the window, but I would like repaint the contents of it when key press happen.
I have tried your advice with WM_SIZE, but in this case I couldn't move the rectangle and the program didn't react to keypress.
So, I would like the user can move the rectangle when can_do is true and after 5 seconds elapsed (can_do is false during this time), he or she can move it again.
Then, timer isn't the right thing for this?
And can you tell me, how can I write texts about variables out the window outside WM_PAINT event? Because my program doesn't write e.g. in this case:
1
2
3
4
5
case WM_TIMER:
char some_text[1];
itoa(wParam,some_text,10);
TextOut(gfxhandler, 50, 10, some_text,1);
InvalidateRect(hwnd, NULL, true);
Last edited on
I'm sorry for not reading your question hard enough and for wrong suggestion, I should have told you that keyboard is handled inside WM_KEYDOWN,
where you can read keyboard state into some variables for use in other messages.

There is a good example on MSDN on how to move shapes inside window with mouse, you could easily apply it to keyboard states, see here:
https://docs.microsoft.com/en-us/windows/win32/learnwin32/simple-drawing-sample

And can you tell me, how can I write texts about variables out the window outside WM_PAINT event?


Drawing text is done inside WM_PAINT, otherwise painting done inside WM_PAINT will overwrite your text and you'll see no text.

Please stop calling InvalidateRect inside window procedure if really not needed, you should instead do the drawing (most of the the visuals) in WM_PAINT, resizing inside WM_SIZE, keyboard inside WM_KEYDOWN, mouse inside WM_MOUSE etc etc..
You know all Win32 functions return status code, you should check those when ever possible, it's the only way to know if function succedeed or not.

IIRC calling invalidaterect is sometimes used as a simple way to animate. If you call it in the onpaint, it forces it to call onpaint again, and you get animation/updates if your drawing code does something different each frame. This is a dim memory from the late 90s, and may be totally unacceptable now? It gets kind of clunky to do that way while buffering to avoid redraw-flashing. It was a common trick for a quick and dirty simple program
Last edited on
Normally you would call InvalidateRect in the WM_Timer code once you have updated the things that should be drawn.
However WM_PAINT has quite low priority so in games or animations it's common to use GetDC to draw without InvalidateRect to speed things up.
I try to avoid using of WM_KEYDOWN, because in this case if I press at least two buttons simultaneously, e.g. right arrow and control, the rectangle can't move, and I would like move it.
I didn't know that MSDN example, but it seems great; thanks for it!
As for InvalidateRect(): if I not call it, the rectangle draws itself on its previous coordinates too, and I don't want it.
Besides that, I would like write out some values on the screen in order to see values of certain variables. For example I would like see the value wParam of WM_TIMER - but how can I reach it from the WM_PAINT with the TextOut()? Is it possible?
The WM_TIMER is not change the values of can_do between true and false in every 5 seconds, and I don't know why?
To store the WPARAM from WM_TIMER just create a variable in WindowProcedure, set it in WM_TIMER and read it in WM_PAINT.

I think you want to do too many things at once.
Why don't you go back a few steps and start simple.
* Create only a rectangle, move and draw it.
* When this works implement the the keyboard functionality, move it
* When this works add more rectangles

A better way to handle the keyboard is to do it in WM_TIMER.
If you want to do the drawing there(WM_TIMER) you can get rid of WM_PAINT
I have replaced the keyboard handling into WM_TIMER, but happening nothing. As if WM_TIMER never execute itself. This is the main problem with my code: I have created the timer here and so:
1
2
3
case WM_CREATE:
            SetTimer(hwnd,the_timer,5000,NULL);
       break;

but it's never execute itself.
And this is my piece of code with keyboard command in WM_TIMER event, but something is wrong in it, because the program not react to keypress:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    HDC gfxhandler = GetDC(hwnd);

    switch (message)                  /* handle the messages */
    {
       case WM_CREATE:
            SetTimer(hwnd,the_timer,5000,NULL);
       break;
       case WM_PAINT:
            for (int i=0; i<smcubes.size(); i++) {
                smcubes[i].draw_out();
            };
            Rectangle(gfxhandler,cubex,cubey,cubex+40,cubey+40);
            char some_text[1];
            itoa(can_do,some_text,10);
            TextOut(gfxhandler, 50, 10, some_text, 1);
        break;
        case WM_TIMER:
            switch (wParam)
            {
                case the_timer: if (can_do==true) {
                        can_do = false;
                    }
                    else {
                        can_do = true;
                    };
                    GetWindowRect(hwnd, &the_window);
                    if (GetAsyncKeyState(VK_LEFT) && cubex-4>0 && can_do==true) {cubex-=4; InvalidateRect(hwnd, NULL, true);};
                    if (GetAsyncKeyState(VK_RIGHT) && cubex+4+40<(the_window.right-the_window.left) && can_do==true) {cubex+=4; InvalidateRect(hwnd, NULL, true);};
                    if (GetAsyncKeyState(VK_UP) && cubey-4>0 && can_do==true) {cubey-=4; InvalidateRect(hwnd, NULL, true);};
                    if (GetAsyncKeyState(VK_DOWN) && cubey+4+40<(the_window.bottom-the_window.top) && can_do==true) {cubey+=4; InvalidateRect(hwnd, NULL, true);};
                    if (GetAsyncKeyState(VK_CONTROL) && can_do==true) {smcubes.push_back(SmallCube(cubex,cubey,gfxhandler));};
                    if (GetAsyncKeyState(VK_SPACE) && smcubes.size()>0 && can_do==true) {smcubes.erase(smcubes.begin()); InvalidateRect(hwnd, NULL, true);};
                break;
            };
            return 0;
        break;
        case WM_DESTROY:
            KillTimer(hwnd,the_timer);
            ReleaseDC(hwnd,gfxhandler);
            PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
        break;

        default:                      /* for messages that we don't deal with */
            return DefWindowProc (hwnd, message, wParam, lParam);
    }

    return 0;
}


But I have written a smaller code focusing onto my timer problem. It always shows "CAN_DO: TRUE" instead of change this text in every 5 seconds. I don't know, why...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#if defined(UNICODE) && !defined(_UNICODE)
    #define _UNICODE
#elif defined(_UNICODE) && !defined(UNICODE)
    #define UNICODE
#endif

#include <tchar.h>
#include <windows.h>

/*  Declare Windows procedure  */
LRESULT CALLBACK WindowProcedure (HWND, UINT, WPARAM, LPARAM);

/*  Make the class name into a global variable  */
TCHAR szClassName[ ] = _T("CodeBlocksWindowsApp");

const unsigned int timer1=1;
bool can_do=true;

int WINAPI WinMain (HINSTANCE hThisInstance,
                     HINSTANCE hPrevInstance,
                     LPSTR lpszArgument,
                     int nCmdShow)
{
    HWND hwnd;               /* This is the handle for our window */
    MSG messages;            /* Here messages to the application are saved */
    WNDCLASSEX wincl;        /* Data structure for the windowclass */

    /* The Window structure */
    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = WindowProcedure;      /* This function is called by windows */
    wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
    wincl.cbSize = sizeof (WNDCLASSEX);

    /* Use default icon and mouse-pointer */
    wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;                 /* No menu */
    wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
    wincl.cbWndExtra = 0;                      /* structure or the window instance */
    /* Use Windows's default colour as the background of the window */
    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    /* Register the window class, and if it fails quit the program */
    if (!RegisterClassEx (&wincl))
        return 0;

    /* The class is registered, let's create the program*/
    hwnd = CreateWindowEx (
           0,                   /* Extended possibilites for variation */
           szClassName,         /* Classname */
           _T("Code::Blocks Template Windows App"),       /* Title Text */
           WS_OVERLAPPEDWINDOW, /* default window */
           CW_USEDEFAULT,       /* Windows decides the position */
           CW_USEDEFAULT,       /* where the window ends up on the screen */
           544,                 /* The programs width */
           375,                 /* and height in pixels */
           HWND_DESKTOP,        /* The window is a child-window to desktop */
           NULL,                /* No menu */
           hThisInstance,       /* Program Instance handler */
           NULL                 /* No Window Creation data */
           );

    /* Make the window visible on the screen */
    ShowWindow (hwnd, nCmdShow);

    /* Run the message loop. It will run until GetMessage() returns 0 */
    while (GetMessage (&messages, NULL, 0, 0))
    {
        /* Translate virtual-key messages into character messages */
        TranslateMessage(&messages);
        /* Send message to WindowProcedure */
        DispatchMessage(&messages);
    }

    /* The program return-value is 0 - The value that PostQuitMessage() gave */
    return messages.wParam;
}


/*  This function is called by the Windows function DispatchMessage()  */

LRESULT CALLBACK WindowProcedure (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    HDC gfxhandler = GetDC(hwnd);
    switch (message)                  /* handle the messages */
    {
        case WM_CREATE:
            SetTimer(hwnd,timer1,5000,NULL);
        break;
        case WM_TIMER:
            switch (wParam)
            {
                case timer1: if (can_do==true) {
                        can_do = false;
                    }
                    else {
                        can_do = true;
                    };
                break;
            };
        break;
        case WM_PAINT:
            if (can_do==true) {
                TextOut(gfxhandler, 50, 10, "CAN_DO: TRUE", 12);
            }
            else {
                TextOut(gfxhandler, 50, 10, "CAN_DO: FALSE", 13);
            }
        break;
        case WM_DESTROY:
            KillTimer(hwnd,timer1);
            ReleaseDC(hwnd,gfxhandler);
            PostQuitMessage (0);       /* send a WM_QUIT to the message queue */
            break;
        default:                      /* for messages that we don't deal with */
            return DefWindowProc (hwnd, message, wParam, lParam);
    }

    return 0;
}
Last edited on
Why don't you set a breakpoint in the WM_TIMER code and see what happens.
Also check the return value of SetTimer and check GetLastError() if necessary.
I don't know how can I use debugging... :-(
Okay, I put a breakpoint in the line with mouse click before the command, but after? Should I choose "Edit breakpoint" after right mouse click?
I don't know much about code::blocks.
Once you created the break point there should be commands like step into or step over somewhere in the menu. Also there should be an option like inspect variables or similar.
Topic archived. No new replies allowed.