I don't understand this. Please help.

Pages: 12
Feb 12, 2012 at 4:48pm
I'm new to Win32 GUI programming. My question is:

Why does the WriteText function work fine in case WM_PAINT but not in case WM_TIMER.
Messagebox comes out fine every 5 seconds.

class WriteText
{
public:
HDC hdc;
HWND hwnd;
PAINTSTRUCT ppaint;

WriteText(int iXpos, int iYpos, char *csString)
{
hdc = BeginPaint(hwnd, &ppaint);
TextOut(hdc, iXpos, iYpos, csString, strlen(csString));
EndPaint(hwnd, &ppaint);
}
};

LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) {

LPSTR szMessage = "Write a text string!";

switch(Message) {
case WM_PAINT:
WriteText(70,50, szMessage);
break;
case WM_TIMER:
WriteText(70,20, szMessage);
MessageBox( NULL , "5 Second Alert !" , "Timer Alert" , MB_OK);
break ;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, Message, wParam, lParam);
}
return 0;
}
Feb 12, 2012 at 5:33pm
BeginPaint() can only be used during the WM_PAINT message. It cannot be used elsewhere. Since WM_TIMER is NOT WM_PAINT, it will fail miserably. :-(

What you need to do is invalidate the client area so a WM_PAINT message is generated right after WM_TIMER, achieving your goal of updating the text. Note, however, that a message box will just freeze the thread and you'll mess things up badly. Seriously consider removing it.
Feb 12, 2012 at 7:47pm
Thanks for your help. I only put the messagebox function in there to see of I was actually there. It will get out because as you said it will freeze the thread. How can I generate a WM_PAInT message in the WM_TIMER case and do I need to update the window too?
Feb 12, 2012 at 7:51pm
Feb 12, 2012 at 8:41pm
That's should work. What about using updatewindow or redrawwindow? As I said, I'm new to this GUI programming and it's a whole different world from the regular C I come from. I'm trying to get an understanding on how it works.
Feb 12, 2012 at 9:24pm
Can't understand it. In below code the bFlag flips between TRUE and FALSE every 5 seconds and do go into WM_PAINT message 1 and 2 ever 5 seconds but only message 1 shows up. I ran the code in debug mode to make sure it actually happen and it does. It should be notice that my compiler is Borland C++ ver 5.03.
I don't know why the code don't indent when I submit it. It does when I copy it. It's tough to read without.

LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) {

LPSTR szMessage_1 = "Text string #1!";
LPSTR szMessage_2 = "Text string #2!";

switch(Message) {
case WM_PAINT:
if (bFlag == TRUE) {
//First string
WriteText(70,50, szMessage_1);

} else {
// Second string after 5 seconds
WriteText(70,50, szMessage_2);
}
break;
case WM_TIMER:
//MessageBox( NULL , "5 Second Alert !" , "Timer Alert" , MB_OK);

// Flip between TRUE AN FALSE every 5 soconds
if (bFlag == TRUE)
{
bFlag = FALSE;
} else {
bFlag = TRUE;
}
// Call WM_PAINT when free
InvalidateRect(hwnd, NULL, NULL);
break;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, Message, wParam, lParam);
}
return 0;


Last edited on Feb 12, 2012 at 9:27pm
Feb 12, 2012 at 9:44pm
I said to call InvalidateRect() when receiving a WM_TIMER, not when receiving WM_PAINT. The point of invalidating is to generate WM_PAINT.
Feb 12, 2012 at 10:15pm
I understand. The code does call InvalidateRect() in the WM_TIMER case to generate a WM_PAINT message every 5 seconds. And the WM_PAINT case is handle every 5 seconds by switching between the 2 messages.
Feb 12, 2012 at 11:08pm
closed account (zwA4jE8b)
Would it work to just generate a WM_PAINT message?

i.e. using the 'Send_Message' function
Last edited on Feb 12, 2012 at 11:31pm
Feb 13, 2012 at 12:37am
I can try but but the InvalidateRect() call already do that. I can verify that in the debugger and see that message 1 and 2 is actually been called in WM_PAINT. It should be pretty straight forward. I tried with UpdateWindow() call too with same result. Weird.

Below is the whole code. Very simple. Just want to change the text in a window every 5 seconds.


#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

static char gszClassName[] = "darkblue";
static HINSTANCE ghInstance = NULL;

#define ID_5SECONDS 101
UINT TimerID = 0;
BOOL bFlag = TRUE;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
WNDCLASSEX WndClass;
HWND hwnd;
MSG Msg;

ghInstance = hInstance;

WndClass.cbSize = sizeof(WNDCLASSEX);
WndClass.style = NULL;
WndClass.lpfnWndProc = WndProc;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hInstance = ghInstance;
WndClass.hIcon = LoadIcon(NULL, IDI_APPLICATION);
WndClass.hCursor = LoadCursor(NULL, IDC_ARROW);
WndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
WndClass.lpszMenuName = NULL;
WndClass.lpszClassName = gszClassName;
WndClass.hIconSm = LoadIcon(NULL, IDI_APPLICATION);

if(!RegisterClassEx(&WndClass)) {
MessageBox(0, "Window Registration Failed!", "Error!", MB_ICONSTOP | MB_OK);
return 0;
}

hwnd = CreateWindowEx(
WS_EX_STATICEDGE,
gszClassName,
" Test Program",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, CW_USEDEFAULT,
320, 240,
NULL, NULL,
ghInstance,
NULL);

if(hwnd == NULL) {
MessageBox(0, "Window Creation Failed!", "Error!", MB_ICONSTOP | MB_OK);
return 0;
}

ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);

// set up our timer
TimerID = SetTimer( hwnd , ID_5SECONDS , 5 *1000 , NULL);

while(GetMessage(&Msg, NULL, 0, 0)) {
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
return Msg.wParam;
}
class WriteText
{
public:
HDC hdc;
HWND hwnd;
PAINTSTRUCT ppaint;

WriteText(int iXpos, int iYpos, char *csString)
{
hdc = BeginPaint(hwnd, &ppaint);
TextOut(hdc, iXpos, iYpos, csString, strlen(csString));
EndPaint(hwnd, &ppaint);
}
};

LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) {

LPSTR szMessage_1 = "Text string #1!";
LPSTR szMessage_2 = "Text string #2!";

switch(Message) {
case WM_PAINT:
if (bFlag == TRUE) {
//First string
WriteText(70,50, szMessage_1);

} else {
// Second string after 5 seconds
WriteText(70,50, szMessage_2);
}
break;
case WM_TIMER:
//MessageBox( NULL , "5 Second Alert !" , "Timer Alert" , MB_OK);

// Flip between TRUE AN FALSE every 5 soconds
if (bFlag == TRUE)
{
bFlag = FALSE;
} else {
bFlag = TRUE;
}
// Call WM_PAINT when free
InvalidateRect(hwnd, NULL, NULL);
break;
case WM_CLOSE:
DestroyWindow(hwnd);
break;
case WM_DESTROY:
PostQuitMessage(0);
break;
default:
return DefWindowProc(hwnd, Message, wParam, lParam);
}
return 0;
}
Feb 13, 2012 at 12:43am
closed account (zwA4jE8b)
like WebJose said, begin paint only works when a paint message is sent, hence, this must be in your message loop.

 
hdc = BeginPaint(hwnd, &ppaint);


an example from my old game

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
LRESULT CALLBACK MessageProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch(message)
	{
		case WM_DESTROY:
		{
			PostQuitMessage(0);
			return 0;
		} break;
		case WM_KEYDOWN:
		{
			switch (wParam)
			{
			.........
				break;
			}
			return 0;
		} break;
     //HERE IS WHERE THE PAINT MESSAGE IS PROCESSED IN WINDOWS
		case WM_PAINT:
		{
			PAINTSTRUCT ps;
			BeginPaint(hWnd,&ps);
			_game.game_blit(ps.hdc);
			EndPaint(hWnd,&ps);
		} break;
     }
     return DefWindowProcA(hWnd, message, wParam, lParam);
 }


so you could instead of _game.game_blit(ps.hdc) call your WriteText function and pass as an argument 'ps.hdc'
Last edited on Feb 13, 2012 at 12:44am
Feb 13, 2012 at 12:51am
If you look at the code I paste and copy the WriteText call is performed in the WM_PAINT case. The WM_TIMER call the InvalidateRect() function to set up the message for the WM_PAINT case and it does happen. Like I said I checked it with break points in the debugger and it switch between message 1 and 2 in the WM_PAINT case. I just don't get it. It print message 1. Thanks for the input.

Anyhow... Another subject. When I copy and past my code in here, all the tabs and spaces dissapear. Is there another way to do it? I see yours came out fine.
Last edited on Feb 13, 2012 at 12:53am
Feb 13, 2012 at 1:00am
I finally figure it out to set the right tabs and spaces. Here is the code in a readable format.

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
     LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) {

        LPSTR szMessage_1 = "Text string #1!";
        LPSTR szMessage_2 = "Text string #2!";

        switch(Message) {
                case WM_PAINT:
                        if (bFlag == TRUE) {
                           //First string
                            WriteText(70,50, szMessage_1);

                        } else {
                           // Second string after 5 seconds
                            WriteText(70,50, szMessage_2);
                        }
                        break;
        		case WM_TIMER:
		                //MessageBox( NULL , "5 Second Alert !" , "Timer Alert"  , MB_OK);

                       // Flip between TRUE AN FALSE every 5 soconds
                        if (bFlag == TRUE)
                        {
                            bFlag = FALSE;
                        } else {
                            bFlag = TRUE;
                        }
                       // Call WM_PAINT when free
                        InvalidateRect(hwnd, NULL, NULL);
            			break;
                case WM_CLOSE:
                        DestroyWindow(hwnd);
                        break;
                case WM_DESTROY:
                        PostQuitMessage(0);
                        break;
                default:
                        return DefWindowProc(hwnd, Message, wParam, lParam);
        }
        return 0;
[code]
[/code]
Feb 13, 2012 at 1:04am
The whole 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
#include <windows.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

static char gszClassName[]  = "darkblue";
static HINSTANCE ghInstance = NULL;

#define ID_5SECONDS 101
UINT TimerID = 0;
BOOL bFlag   = TRUE;

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) {
        WNDCLASSEX WndClass;
        HWND hwnd;
        MSG Msg;

        ghInstance = hInstance;

        WndClass.cbSize        = sizeof(WNDCLASSEX);
        WndClass.style         = NULL;
        WndClass.lpfnWndProc   = WndProc;
        WndClass.cbClsExtra    = 0;
        WndClass.cbWndExtra    = 0;
        WndClass.hInstance     = ghInstance;
        WndClass.hIcon         = LoadIcon(NULL, IDI_APPLICATION);
        WndClass.hCursor       = LoadCursor(NULL, IDC_ARROW);
        WndClass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
        WndClass.lpszMenuName  = NULL;
        WndClass.lpszClassName = gszClassName;
        WndClass.hIconSm       = LoadIcon(NULL, IDI_APPLICATION);

        if(!RegisterClassEx(&WndClass)) {
                MessageBox(0, "Window Registration Failed!", "Error!", MB_ICONSTOP | MB_OK);
                return 0;
        }

        hwnd = CreateWindowEx(
                WS_EX_STATICEDGE,
                gszClassName,
                " Test Program",
                WS_OVERLAPPEDWINDOW,
                CW_USEDEFAULT, CW_USEDEFAULT,
                320, 240,
                NULL, NULL,
                ghInstance,
                NULL);

        if(hwnd == NULL) {
                MessageBox(0, "Window Creation Failed!", "Error!", MB_ICONSTOP | MB_OK);
                return 0;
        }

        ShowWindow(hwnd, nCmdShow);
        UpdateWindow(hwnd);

    	// set up our timer
        TimerID = SetTimer( hwnd , ID_5SECONDS , 5 *1000 , NULL);

        while(GetMessage(&Msg, NULL, 0, 0)) {
                TranslateMessage(&Msg);
                DispatchMessage(&Msg);
        }
        return Msg.wParam;
}
class WriteText
{
public:
    HDC hdc;
    HWND hwnd;
    PAINTSTRUCT ppaint;

    WriteText(int iXpos, int iYpos, char *csString)
    {
        hdc = BeginPaint(hwnd, &ppaint);
        TextOut(hdc, iXpos, iYpos, csString, strlen(csString));
        EndPaint(hwnd, &ppaint);
    }
};

        LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) {

        LPSTR szMessage_1 = "Text string #1!";
        LPSTR szMessage_2 = "Text string #2!";

        switch(Message) {
                case WM_PAINT:
                        if (bFlag == TRUE) {
                           //First string
                            WriteText(70,50, szMessage_1);

                        } else {
                           // Second string after 5 seconds
                            WriteText(70,50, szMessage_2);
                        }
                        break;
        		case WM_TIMER:
		                //MessageBox( NULL , "5 Second Alert !" , "Timer Alert"  , MB_OK);

                       // Flip between TRUE AN FALSE every 5 soconds
                        if (bFlag == TRUE)
                        {
                            bFlag = FALSE;
                        } else {
                            bFlag = TRUE;
                        }
                       // Call WM_PAINT when free
                        InvalidateRect(hwnd, NULL, NULL);
            			break;
                case WM_CLOSE:
                        DestroyWindow(hwnd);
                        break;
                case WM_DESTROY:
                        PostQuitMessage(0);
                        break;
                default:
                        return DefWindowProc(hwnd, Message, wParam, lParam);
        }
        return 0;
}
Feb 13, 2012 at 1:05am
closed account (zwA4jE8b)
First, your program will exit as soon as there are no more messages in the queue because there is no loop encapsulating the message loop

1
2
3
4
5
6
while(GetMessage(&Msg, NULL, 0, 0)) {
 TranslateMessage(&Msg);
 DispatchMessage(&Msg);
 }
return Msg.wParam;
 }



try something like
1
2
3
4
5
6
case WM_PAINT:
 if (bFlag == TRUE) {
 //First string
PAINTSTRUCT ppaint;
hdc = BeginPaint(hwnd, &ppaint);
 WriteText(70,50, szMessage_1, hdc); ///pass hdc here. Do not get it and beginpaint in your class. do it here 
Feb 13, 2012 at 1:30am
The damm thing works. Thanks. Can you explain why I can't call an public function in WM_PAINT to do the same thing?


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
        LRESULT CALLBACK WndProc(HWND hwnd, UINT Message, WPARAM wParam, LPARAM lParam) {

        HDC hdc;
        PAINTSTRUCT ppaint;

        LPSTR szMessage_1 = "Text string #1!";
        LPSTR szMessage_2 = "Text string #2!";

        switch(Message) {
                case WM_PAINT:
                        if (bFlag == TRUE) {
                           //First string
                           // WriteText(70,50, szMessage_1);
                            hdc = BeginPaint(hwnd, &ppaint);
                            TextOut(hdc, 70, 50, szMessage_1, strlen(szMessage_1));
                            EndPaint(hwnd, &ppaint);
                        } else {
                           // Second string after 5 seconds
                            //WriteText(70,50, szMessage_2);
                            hdc = BeginPaint(hwnd, &ppaint);
                            TextOut(hdc, 70, 50, szMessage_2, strlen(szMessage_2));
                            EndPaint(hwnd, &ppaint);
                        }
                        break;
        		case WM_TIMER:
		                //MessageBox( NULL , "5 Second Alert !" , "Timer Alert"  , MB_OK);

                       // Flip between TRUE AN FALSE every 5 soconds
                        if (bFlag == TRUE)
                        {
                            bFlag = FALSE;
                        } else {
                            bFlag = TRUE;
                        }
                       // Call WM_PAINT when free
                        InvalidateRect(hwnd, NULL, NULL);
            			break;
                case WM_CLOSE:
                        DestroyWindow(hwnd);
                        break;
                case WM_DESTROY:
                        PostQuitMessage(0);
                        break;
                default:
                        return DefWindowProc(hwnd, Message, wParam, lParam);
        }
        return 0;
}
Last edited on Feb 13, 2012 at 1:36am
Feb 13, 2012 at 1:45am
closed account (zwA4jE8b)
I cannot explain it, I don't know enough about the inner workings. I just know that is the proper syntax. Maybe disch knows, he is the one who showed me how to properly use WM_PAINT

Also you should do this

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
case WM_PAINT:
                        if (bFlag == TRUE) {
                           //First string
                           // WriteText(70,50, szMessage_1);
                            hdc = BeginPaint(hwnd, &ppaint);
                            TextOut(hdc, 70, 50, szMessage_1, strlen(szMessage_1));
                            EndPaint(hwnd, &ppaint);
                            ReleaseDC(hdc);
                        } else {
                           // Second string after 5 seconds
                            //WriteText(70,50, szMessage_2);
                            hdc = BeginPaint(hwnd, &ppaint);
                            TextOut(hdc, 70, 50, szMessage_2, strlen(szMessage_2));
                            EndPaint(hwnd, &ppaint);
                            ReleaseDC(hdc);
                        }
                        break;


Again, I dont know exactly why, but you should release the dc as soon as possible so other windows can use it.
Last edited on Feb 13, 2012 at 1:48am
Feb 13, 2012 at 2:03am
Like I said. I'm new to this Win32 GUI programming and don't understand all the stuff yet. Still thinking in good old C programming. When you said pass hdc to WriteText then pass it as what? Or is there a way to make hdc public? You have been a great help thanks. Have to get rid of thinking in C programming.
Feb 13, 2012 at 2:07am
closed account (zwA4jE8b)
This would be optimal
1
2
3
4
5
6
7
void WriteText(HWND hwnd, int iXpos, int iYpos, char *csString)
    {
		HDC hdc = GetDC(hwnd)
		TextOut(hdc, iXpos, iYpos, csString, strlen(csString));
		ReleaseDC(hWnd, hdc);
    }
};

this way the DC is called for the function and released asap.

Sorry for editing so many times.

But if you just have the one function, a class is not necessary. Just declare the function before it is called
Last edited on Feb 13, 2012 at 2:20am
Feb 13, 2012 at 2:32am
WM_PAINT cannot be forged using SendMessage(). It is a special window message handled at the kernel of the operating system. It has special features. For instance, you will never ever have two WM_PAINT messages in the message queue for the same window. Guaranteed. Another trait is that WM_PAINT is only retrieved by GetMessage() if there aren't other more pressing messages to process, like mouse or keyboard messages.

So summarizing, if you want a WM_PAINT, you invalidate using InvalidateRect().

Now, because of this special treatment this message receives, there are also special functions to deal with it. Those are BeginPaint() and EndPaint(). BeginPaint() gets you a device context (HDC) special for the occasion: It is already clipped according to the area invalidated and according to the window styles WS_CLIPSIBLINGS and WS_CLIPCHILDREN. EndPaint() on the other hand tells the operating system that the painting operation is completed and that it is OK now to mark as Valid all previously Invalid(ated) regions of the window.

BeginPaint() and EndPaint() are therefore only available during the processing of WM_PAINT. But you should be able to move these calls outside of the window procedure. I see no technical reason as long as the calls are made within the context of WM_PAINT.
Pages: 12