Repainting the Client area

I'm new to Windows Programming as I've been doing embedded for many years. I am using Petzold's book as my learning source. I can't find a good example of this scenario:

Under my WM_PAINT case statement, I am painting 'Test 123' at the top of the client area. I also have a menu that allows Opening and Selecting a File, with a IDM_OPEN case statement. Access to the menu isn't allowed until WM_PAINT is finished running the first time. If I open a file and select a file or exit the dialog box, how do I display 'Test 456' in the middle of the client area after the dialog box goes away? The 'Test 123' is repainted after the dialog box goes away, but how do I add text to the client area? Where do I put that code?

Sutton
You need send the windows INVALIDATE_RECT with the region coordinates (as rectangle) to be repainted.
I tried that. It still only repaints 'Test 123' when the dialog box disappears. It still won't paint 'Test 456'. Where do I place the code TextOut(), so that 'Test 456' gets displayed only after the dialog box disappears? You can't put the Test 456 code in WM_PAINT, otherwise it paints both lines of text initially. If the code is put the code after the File Open code is done, it doesn't display the Test 456.
Again, I don't do Windows, but in general you need to keep the entire text you want to display in a buffer and repaint that text in the paint event. For instance, you will need to redraw the text if the window is covered and uncovered, or minimized and shown again.

Something like:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// global
vector<string> text {"Test 123"};

int main(...) {
    set_up_code...
    event loop:
        ...
}

void paint_event(...) {
    int x = 20, y = 20;
    for (const auto& line: text) {
        TextOut(hwnd, x, y, line.c_str(), line.size());
        y += 20;
    }
}

void menu_event(...) {
    handle menu
    text.push_back("Test 456")
    InvalidateRect(...)
}

y += 20 is just a guess. There should be a way to determine how high the font actually is so the line spacing can be more precise.
Last edited on
how do I display 'Test 456' in the middle of the client area after the dialog box goes away?

You are calling some form of a dialog box function in your IDM_OPEN case, right?. Put the code for drawing the text after calling your dialog box function within your IDM_OPEN case statement. So the text is drawn after the dialog box returns control back to the main app window.

If you are using Petzold's 5th edition of "Programming Windows" the source code has some problems since the book was released. MS modified the Windows Desktop API in areas so some changes to the code needs to be done.

A lot of the work has already been done for you. See
https://github.com/recombinant/petzold-pw5e

I don't know what Windows version you have, nor what your compiler is. Telling us that would be helpful for giving advice and suggestions how to proceed.

An old text drawing code on demand sample I have, and updated to work with 32- and 64-bit Windows is as follows. It uses a custom menu to show a block of text when selecting a menu item. Using Visual Studio 2019:

ids.h
1
2
3
4
5
6
7
8
9
10
11
12
13
#ifndef __IDS_H__
#define __IDS_H__

#define IDR_MENU     10001
#define IDR_ACCEL    10002

#define IDM_EXIT     11001
#define IDM_HELP     11002

#define IDM_SHOW     11101
#define IDM_RESET    11102

#endif 

TextOutput.rc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <windows.h>
#include "ids.h"

IDR_MENU MENU
{
   POPUP "&Text"
   {
      MENUITEM "&Show\tF2", IDM_SHOW
      MENUITEM "&Reset\tF3", IDM_RESET
      MENUITEM SEPARATOR
      MENUITEM "E&xit", IDM_EXIT
   }
   MENUITEM "&Help", IDM_HELP
}

IDR_ACCEL ACCELERATORS
{
   VK_F1, IDM_HELP, VIRTKEY

   VK_F2, IDM_SHOW, VIRTKEY
   VK_F3, IDM_RESET, VIRTKEY
}

DrawText.c
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
149
150
151
#include	<windows.h>
#include "ids.h"

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

int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
                    _In_ PWSTR szCmdLine, _In_ int nWinMode)
{
   const WCHAR szWinName[] = L"DisplayText";
   WNDCLASSW   wc;

   wc.hInstance     = hInstance;
   wc.lpszClassName = szWinName;
   wc.lpfnWndProc   = WndProc;
   wc.style         = 0;
   wc.hIcon         = (HICON)   LoadImageW(NULL, IDI_APPLICATION, IMAGE_ICON, 0, 0, LR_DEFAULTCOLOR);
   wc.hCursor       = (HCURSOR) LoadImageW(NULL, IDC_ARROW, IMAGE_CURSOR, 0, 0, LR_SHARED);
   wc.lpszMenuName  = MAKEINTRESOURCE(IDR_MENU);
   wc.cbClsExtra    = 0;
   wc.cbWndExtra    = 0;
   wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1);

   if (RegisterClassW(&wc) == 0)
   {
      MessageBoxW(NULL, L"Couldn't Register the Window Class!", L"ERROR", MB_OK | MB_ICONERROR);
      return E_FAIL;
   }

   const WCHAR szAppTitle[] = L"Demonstrating Text Output";

   HWND hwnd = CreateWindowW(szWinName, szAppTitle,
                             WS_OVERLAPPEDWINDOW,
                             CW_USEDEFAULT, CW_USEDEFAULT,
                             CW_USEDEFAULT, CW_USEDEFAULT,
                             NULL, NULL, hInstance, NULL);

   if (hwnd == NULL)
   {
      MessageBoxW(NULL, L"Couldn't Create the Main Window!", L"ERROR", MB_OK | MB_ICONERROR);
      return E_FAIL;
   }

   ShowWindow(hwnd, nWinMode);
   UpdateWindow(hwnd);

   HACCEL hAccel = LoadAcceleratorsW(hInstance, MAKEINTRESOURCE(IDR_ACCEL));

   MSG msg;

   while (GetMessageW(&msg, NULL, 0, 0))
   {
      if (TranslateAcceleratorW(hwnd, hAccel, &msg) == 0)
      {
         TranslateMessage(&msg);
         DispatchMessageW(&msg);
      }
   }

   return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
   static WCHAR str[255];
   static int   X = 0;   // current output location
   static int   Y = 0;   // current output location
   static int   maxX;    // screen dimensions
   static int   maxY;    // screen dimensions
   SIZE         size;
   TEXTMETRIC   tm;
   UINT         response;
   HDC          hdc;

   switch (message)
   {
   case WM_CREATE:
      // get the screen dimensions
      maxX = GetSystemMetrics(SM_CXSCREEN);
      maxY = GetSystemMetrics(SM_CYSCREEN);
      return 0;

   case WM_COMMAND:
      switch (LOWORD(wParam))
      {
      case IDM_EXIT:
         response = MessageBoxW(hwnd, L"Quit the Program?", L"Exit", MB_YESNO);

         if (response == IDYES)
         {
            PostQuitMessage(S_OK);
         }
         return S_OK;

      case IDM_HELP:
         MessageBoxW(hwnd, L"F2: Display\nF3: Reset", L"Help", MB_OK);
         return S_OK;

      case IDM_SHOW:
         hdc = GetDC(hwnd);

         // set the text color to black
         SetTextColor(hdc, RGB(0, 0, 0));

         // set the background color to turquoise
         SetBkColor(hdc, RGB(0, 255, 255));

         // get the text metrics
         GetTextMetricsW(hdc, &tm);

         wsprintfW(str, L"The font is %ld pixels high.", tm.tmHeight);
         TextOutW(hdc, X, Y, str, lstrlenW(str));

         Y += tm.tmHeight + tm.tmExternalLeading;

         lstrcpyW(str, L"This is on the next line. ");
         TextOutW(hdc, X, Y, str, lstrlenW(str));

         // compute the length of a string
         GetTextExtentPoint32W(hdc, str, lstrlenW(str), &size);
         wsprintfW(str, L"Previous string is %ld units long.", size.cx);

         X = size.cx;

         TextOutW(hdc, X, Y, str, lstrlenW(str));

         X  = 0;
         Y += tm.tmHeight + tm.tmExternalLeading;

         wsprintfW(str, L"Screen dimensions: %d x %d", maxX, maxY);
         TextOutW(hdc, X, Y, str, lstrlenW(str));

         Y += tm.tmHeight + tm.tmExternalLeading;

         ReleaseDC(hwnd, hdc);
         return S_OK;

      case IDM_RESET:
         X = Y = 0;

         InvalidateRect(hwnd, NULL, TRUE);
         return S_OK;
      }
      break;

   case WM_DESTROY:
      PostQuitMessage(S_OK);
      return S_OK;
   }

   return DefWindowProcW(hwnd, message, wParam, lParam);
}

I target for Unicode explicitly instead of relying on macros. Win 95/98/Me are dead. No need for mixed 16/32 bit encodings.

Since Vista Windows changed how pixels are drawn to the screen. Be aware of this change.
https://docs.microsoft.com/en-us/windows/win32/dwm/dwm-overview

Win 7 (and later) turned off the ability to enable/disable DWM. It is always on.
I'm using Win7 and Win 10. I think the text buffer seems the way to go. The 2nd line of text will not write after the File Dialog box is closed in the IDM_OPEN statement. The WM_PAINT message has a message of 'Test 123', which is static. Even if you do a TextOut in the IDM_OPEN section after the File Dialog closes, once the WM_PAINT message immediately process again, you lose that line, because the only TextOut in WM_PAINT is the 'Test123'. I think if you create a buffer and write the buffer in the WM_PAINT section, that should work.
I'm still having a few issues with my code. Don't heckle me too bad, I'm still in early learning mode. Still using Petzold's book as my main resource, with online help for more help.

In this code, for some reason, the About Box crashes, if I have a return(0) in the WM_PAINT section. If I change to a break;, it works. return(0) is the way all code is written, so I'm not sure why.

Also, I display 'Test123' upon the initial window creation. Then, when I click About and then close the About Box, I want to display 'Test456' in the window. How do I do that? For now, I appended 'Test456' to my string, but it doesn't display - it clears everything after the About Box is closed and nothing is displayed. Why? Just as an exercise, I would really like to put Test123 at 0,0 like it does initially and then Test456 at 20,0 in the window after the About Box is closed. How do I do that?

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
#include "windows.h"
#include "commdlg.h"

#define IDM_ABOUT   1
#define IDM_EXIT    2
#define IDM_HELP    3
#define IDM_OPEN    4

WCHAR AppName[] = L"Test";
WCHAR AboutTitle[] = L"About Test";
WCHAR iconfilename[] = L"analysis.ico";
WCHAR wintext[80] = L"Test 123";
unsigned char xcoord, ycoord;
HDC hdc;

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


int WINAPI WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PSTR szCmdLine,int iCmdShow)
{
    WNDCLASS wndclass;
    HMENU hMenu, hMenuPopup;
    MSG msg;
    HWND hWnd;

    wndclass.style = CS_HREDRAW | CS_VREDRAW;
    wndclass.lpfnWndProc = WndProc;
    wndclass.cbClsExtra = 0;
    wndclass.cbWndExtra = 0;
    wndclass.hInstance = hInstance;
    wndclass.hIcon = (HICON)LoadImage(NULL,iconfilename,IMAGE_ICON,0,0,LR_LOADFROMFILE|LR_DEFAULTSIZE|LR_SHARED);
    wndclass.hCursor = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
    wndclass.lpszMenuName = AppName;
    wndclass.lpszClassName = AppName;

    if (!RegisterClass(&wndclass))
    {
        MessageBox(NULL,L"Call to RegisterClass failed!",AppName,NULL);
        return(1);
    }

    hWnd = CreateWindow(AppName, AppName, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, CW_USEDEFAULT, 700, 400, NULL, NULL, hInstance, NULL);
    if (!hWnd)
    {
        MessageBox(NULL, L"Call to CreateWindow failed!", AppName, NULL);
        return(1);
    }
    hdc = GetDC(hWnd);

    hMenu = CreateMenu();
    hMenuPopup = CreateMenu();
    AppendMenu(hMenuPopup, MF_STRING, IDM_OPEN, L"Open");
    AppendMenu(hMenuPopup, MF_STRING, IDM_EXIT, L"Exit");
    AppendMenu(hMenu, MF_POPUP, UINT(hMenuPopup), L"File");

    hMenuPopup = CreateMenu();
    AppendMenu(hMenuPopup, MF_STRING, IDM_ABOUT, L"About Test");
    AppendMenu(hMenu, MF_POPUP, UINT(hMenuPopup), L"Help");

    SetMenu(hWnd, hMenu);

    ShowWindow(hWnd, iCmdShow);
    UpdateWindow(hWnd);

    // Main message loop:
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return msg.wParam;
}


//
//  FUNCTION: WndProc(HWND, UINT, WPARAM, LPARAM)
//
//  PURPOSE: Processes messages for the main window.
//
//  WM_COMMAND  - process the application menu
//  WM_PAINT    - Paint the main window
//  WM_DESTROY  - post a quit message and return
//
//
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    WCHAR text[80];

    switch (message)
    {
       case WM_COMMAND:
           // Parse the menu selections:

           switch (LOWORD(wParam))
           {
               case IDM_OPEN:
                   MessageBeep(0);
                   Sleep(500);
                   MessageBeep(0);
                   Sleep(500);
                   MessageBeep(0);
                   return(0);

               case IDM_EXIT:
                   SendMessage(hwnd, WM_CLOSE, 0, 0);
                   return(0);

               case IDM_ABOUT:
                   wsprintfW(text, L"%s\n\u00a92021 Lookout Portable Security\nVersion 1.0", AppName);
                   MessageBoxW(hwnd,text,AboutTitle,MB_ICONINFORMATION|MB_OK);
                   lstrcatW(wintext, L"Test 456");
                   InvalidateRect(hwnd, NULL, TRUE);
                   return(0);
            }
            break;

        case WM_PAINT:
            xcoord = 0;
            ycoord = 0;
            TextOutW(hdc, xcoord, ycoord, wintext, lstrlen(wintext));
            return(0);

        case WM_DESTROY:
            PostQuitMessage(0);
            return(0);
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}
Is there a way to create a virtual Window with my Test123 text positioned at 0,0 and then add Test456 at position 20,0? Then issue some type of show message to show my virtual window in my client area? That way I can manipulate text data any way I want to. That might be another approach, but haven't figured out how to do that yet or even if that is doable or a recommended way.
Last edited on
In this code, for some reason, the About Box crashes, if I have a return(0) in the WM_PAINT section. If I change to a break;, it works. return(0) is the way all code is written, so I'm not sure why.


The WM_PAINT code doesn't call BeginPaint() and EndPaint(). When you use a break, it calls DefWindowProc() which does call BeginPaint() and EndPaint() - even if there's nothing to paint.

The simplest WM_PAINT code is:

1
2
3
4
5
6
7
8
case WM_PAINT:
    {
        PAINTSTRUCT ps {};

        BeginPaint(hwnd, &ps);
        EndPaint(hwnd, &ps);
    }
    return 0;

Last edited on
This should do what you want.
1
2
3
4
5
6
7
8
9
10
11
12
case WM_PAINT:
	{
		PAINTSTRUCT ps = { 0 };
		BeginPaint(hwnd, &ps);
		xcoord = 0;
		ycoord = 0;
		TextOutW(hdc, xcoord, ycoord, wintext, lstrlen(wintext));
		TextOutW(hdc, 60, 0, L"Test456", 7);

		EndPaint(hwnd, &ps);
		break;
	}


Could you show the code in the future with the original post. It makes it much easier.
That doesn't do what I want it to do as per my post. I want to print Test123 and then only after the user clicks the About Box and then OK it do I want to print Test 456 in the client window down a few lines. I don't want the Test456 right away.

Thanks for the WM_PAINT explanation.
So you didn't even bother to try my suggestion?
This code actually does print the Test456 after the About Box is exited, but how do you put the text at a certain x,y coordinate? If I put \n\n\n before the Test456, that doesn't work either. Do I need another BeginPaint/EndPaint in the About 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
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    WCHAR text[80];
    PAINTSTRUCT ps;

    switch (message)
    {
       case WM_COMMAND:
           // Parse the menu selections:

           switch (LOWORD(wParam))
           {
               case IDM_OPEN:
                   MessageBeep(0);
                   Sleep(500);
                   MessageBeep(0);
                   Sleep(500);
                   MessageBeep(0);
                   return(0);

               case IDM_EXIT:
                   SendMessage(hwnd, WM_CLOSE, 0, 0);
                   return(0);

               case IDM_ABOUT:
                   wsprintfW(text, L"%s\n\u00a92021 Lookout Portable Security\nVersion 1.0", AppName);
                   MessageBoxW(hwnd,text,AboutTitle,MB_ICONINFORMATION|MB_OK);
                   lstrcatW(wintext, L"Test 456");
                   InvalidateRect(hwnd, NULL, TRUE);
                   return(0);
            }
            break;

        case WM_PAINT:
            ps = { 0 };
            BeginPaint(hwnd, &ps);
            xcoord = 0;
            ycoord = 0;
            TextOutW(hdc, xcoord, ycoord, wintext, lstrlen(wintext));
            EndPaint(hwnd, &ps);
            return(0);

        case WM_DESTROY:
            PostQuitMessage(0);
            return(0);
    }
    return DefWindowProc(hwnd, message, wParam, lParam);
}
OK. Got it. I need another BeginPaint/TextOut (0,60)/EndPaint. It doesn't mess up the initial text at 0,0. That's what I wanted.
\n doesn't work in GUI TextOut or DrawText.
You have to do it manually
Last edited on
\n doesn't work in a GUI

Are you sure about that?

1
2
3
4
5
6
7
8
9
#include <windows.h>

int WINAPI wWinMain(_In_ HINSTANCE hInstance, _In_opt_ HINSTANCE hPrevInstance,
                    _In_ PWSTR     szCmdLine, _In_     int       iCmdShow)
{
   MessageBoxW(NULL, L"Hello World!\n\nAnother Line.", L"Hello Message", MB_OK);

   return 0;
}

I get a message box, text reads "Hello World!" with one blank line before "Another Line."
Are you sure about that?

Looks like you found an exception, seems to work in other controls as well.
I was thinking about TextOut and DrawText. I have updated the previous post.
Last edited on
Fair 'nuf.

How Windows draws text to the client area with DrawText/TextOut is decidedly byzantine, but it works within certain constraints.
I was sloppy, and lazy. My example is to be ignored.
Last edited on
Topic archived. No new replies allowed.