Calling functions / Using andywestken's code to output text

Hi,

There's a post in the Windows Programming forum about selecting fonts: http://www.cplusplus.com/forum/windows/62180/.

The user andywestken's code allows me to select a font for output. I've incorporated his code into my own (Windows) program, but now have a general C++ question about how to use or implement it.

I want to output text to the screen using Andy's class and function, which allow me to change the font before output.

But I don't know how to call the WriteText function, or where in my code to place the function call. I've tried several methods:

1
2
WriteText (hdc, 50, 50, "Hi there");
TextWriter::WriteText (hdc, 50, 50, "Hi there");

The first method gives me the error, "Identifer 'WriteText' is undefined."
The second method gives, "A nonstatic member reference must be relative to a specific object."

I'm just learning how to work with functions in C++. I've read a lot about functions in theory, but haven't had much success implementing what I thought I knew in real life. An example of how to do this in the context of Andy's code might help make it click for me.

I'm not even sure WHERE in my overall program I could place a proper function call.

Thanks for your help.
Last edited on
Hi

WriteText is a non-static method of a class, in this case TextWriter, so you need an instance.

The easiest way is to just use a local instance in the WM_PAINT handler of a ""classic" Win32 program's WindowProc, or the equivalent place in MFC, etc.

1
2
3
4
5
6
7
8
9
10
11
	case WM_PAINT:
	{
		PAINTSTRUCT ps = {0};
		HDC hdc = BeginPaint(hWnd, &ps);

		TextWriter tw;
		tw.WriteText(hdc, 50, 100, "Hello, cplusplus.com");

		EndPaint(hWnd, &ps);
	}
	break;


But if you want to persist setting, you'll need to keep the TextWriter instance around somewhere: a member of a persistent class, a global variable, a (C-style) static variable etc.

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
// global variable
TextWriter g_tw;

// etc

	case WM_CREATE:
	{
		// etc

		g_tw.Initialize();

		// etc
	}
	break;

	case WM_DESTROY:
	{
		// etc

		g_tw.Finalize();

		// etc
	}
	break;

        case WM_COMMAND:
        {
		// etc

		// NOTE THIS BIT SHOULD BE TRIGGERED BY JUST ONE
		// MENU ITEM: CODE IS FOR ILLUSTRATIVE PURPOSES ONLY

		COLORREF cref = g_tw.GetTextColor();

		// display dialog to allow user to set font, size, color, ... 

		g_tw.SetTextColor(cref); // now a different value

		InvalidateRect(hWnd, NULL, TRUE);

		// etc
        }
        break;

	case WM_PAINT:
	{
		PAINTSTRUCT ps = {0};
		HDC hdc = BeginPaint(hWnd, &ps);

		g_tw.WriteText(hdc, 50, 100, "Hello, cplusplus.com");

		EndPaint(hWnd, &ps);
	}
	break;

// etc 


Notes

1. This class will only work in a Windows (GUI) program, not a (Windows) console program. The WriteText method should be called only in the WM_PAINT handler of your program's message loop.

2. The sample I posted previously uses fixed font, size, and color. But it should be easy enough to adapt it to make these things adjustable. The Initialize() and Finalize(), or equivalent, will also need to be implemented if you want the user to be able to change colors.

3. You could use a local instance and keep the font, size, color as globals (or whatever), reinitializing the TextWriter object each time. This would require the contructor to be modified accordingly.

Andy
Last edited on
This is more or less the minimum amount of code you need for a "classic" Win32 program which uses the TextWriter class.

You could lose some of the local variables, replace the Wizard generated functions (MyRegisterClass and InitInstance) with inlined code, and remove assorted spaces, you would still have to go through pretty much the same sequence of calls.

The contents of three files follows:
- TextWriter_Demo.cpp
- TextWriter_Demo.rc
- resource.h

The resource is just needed to provide a File/Exit menu item.

Also note that the code has been tweaked to use the tchar.h tyepdefs/macros (TCHAR for char, _tcscpy for strcpy, etc) so it builds Unicode and Ansi.

And finally, if winresrc.h is not found, just swap the line to windows.h. I'm not sure where this file lives in the different versions of Visual C++ and other compilers. It's in the Windows SDK folder on my machine, which should turn up with the newer Express editions of Visual C++.

Andy

Source file

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
152
153
154
155
156
157
158
// TextWriter_Demo.cpp

#define WIN32_LEAN_AND_MEAN
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <tchar.h>

#include "resource.h"

TCHAR szTitle      [] = _T("TextWriter Demo");       // The title bar text
TCHAR szWindowClass[] = _T("TextWriter_Demo_Class"); // the main window class name

// Forward declarations of functions included in this code module:
ATOM              MyRegisterClass(HINSTANCE hInstance);
BOOL              InitInstance(HINSTANCE, int);
LRESULT CALLBACK  WndProc(HWND, UINT, WPARAM, LPARAM);

const COLORREF colorRed = RGB(255, 0, 0);

class TextWriter
{
private:
    HFONT hfont;
    COLORREF textColor;

public:
    TextWriter() : hfont(NULL), textColor(colorRed)
    {
        // initialize font here (I prefer CreateFontIndirect to CreateFont,
        // as it's easier to deal with unused params using memset.)
        LOGFONT logFont;
        memset(&logFont, 0, sizeof(logFont));
        logFont.lfHeight = -48; // see PS
        logFont.lfWeight = FW_BOLD;
        _tcscpy(logFont.lfFaceName, _T("Broadway"));
        hfont = CreateFontIndirect(&logFont);
    }

    ~TextWriter()
    {
        DeleteObject(hfont);
    }

    void WriteText(HDC _hdc, int _Xpos, int _Ypos, const TCHAR* _szMessage)
    {
        // set text color and font
        COLORREF oldTextColor = SetTextColor(_hdc, textColor);
        HFONT oldHFont = (HFONT)SelectObject(_hdc, hfont);

        TextOut(_hdc, _Xpos, _Ypos, _szMessage, _tcslen(_szMessage));

        // restore text color and font
        SetTextColor(_hdc, oldTextColor);
        SelectObject(_hdc, oldHFont);
    }
};

int APIENTRY
_tWinMain(HINSTANCE hInstance,
          HINSTANCE hPrevInstance,
          LPTSTR    lpCmdLine,
          int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    MyRegisterClass(hInstance);

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

    MSG msg = {0};
    while(GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEX wcex;
    wcex.cbSize         = sizeof(WNDCLASSEX);
    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = NULL; // no icon provided
    wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName   = MAKEINTRESOURCE(IDR_MAIN);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = NULL; // no small icon provided

    return RegisterClassEx(&wcex);
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    HWND hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

    if(!hWnd)
    {
        return FALSE;
    }

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    return TRUE;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch(uMsg)
    {
        case WM_COMMAND:
        {
            int wmId    = LOWORD(wParam);
            int wmEvent = HIWORD(wParam);
            switch (wmId)
            {
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, uMsg, wParam, lParam);
            }
        }
        break;

        case WM_PAINT:
        {
            PAINTSTRUCT ps = {0};
            HDC hdc = BeginPaint(hWnd, &ps);

            TextWriter tw;
            tw.WriteText(hdc, 50, 100, _T("Hello, cplusplus.com"));

            EndPaint(hWnd, &ps);
        }
        break;

        case WM_DESTROY:
            PostQuitMessage(0);
        break;

        default:
            return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
    return 0;
}


Minimal resource file (you just need a menu with an Exit item)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// TextWriter_Demo.rc

#include "winresrc.h"
#include "resource.h"

// Menu

IDR_MAIN MENU 
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "E&xit", IDM_EXIT
    END
END


And resource header

1
2
3
4
5
// resource.h

#define IDR_MAIN                        100
#define IDM_EXIT                        101
#define IDC_STATIC                      -1 

Last edited on
This version uses a global variable so the user can set the text color.

For this demo, the common Color Chooser dialog is being used.

Note I would split out the TextWriter class out into its own header file, but for ease of cut and paste I stuffed eveything in one file.

Note also that the scoping operator is now being in used in the TextWriter (in the WriteText method). That's because I wanted to use SetTextColor as the name of a member function and then call the global Win32 SetTextColor function.

Andy

Source file

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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
// TextWriter_Demo.cpp

#define WIN32_LEAN_AND_MEAN
#define _CRT_SECURE_NO_WARNINGS
#include <windows.h>
#include <commdlg.h>
#include <tchar.h>

#include "resource.h"

TCHAR szTitle      [] = _T("TextWriter Demo");       // The title bar text
TCHAR szWindowClass[] = _T("TextWriter_Demo_Class"); // the main window class name

// Forward declarations of functions included in this code module:
ATOM              MyRegisterClass(HINSTANCE hInstance);
BOOL              InitInstance(HINSTANCE, int);
LRESULT CALLBACK  WndProc(HWND, UINT, WPARAM, LPARAM);

void OnTextColor(HWND hWnd);

const COLORREF colorRed = RGB(255, 0, 0);

class TextWriter
{
private:
    HFONT hfont;
    COLORREF textColor;

public:
    TextWriter() : hfont(NULL), textColor(colorRed)
    {
    }

    ~TextWriter()
    {
        Finalize();
    }

    void Initialize()
    {
        Finalize();

        // initialize font here (I prefer CreateFontIndirect to CreateFont,
        // as it's easier to deal with unused params using memset.)
        LOGFONT logFont;
        memset(&logFont, 0, sizeof(logFont));
        logFont.lfHeight = -48; // see PS
        logFont.lfWeight = FW_BOLD;
        _tcscpy(logFont.lfFaceName, _T("Broadway"));
        hfont = CreateFontIndirect(&logFont);
    }

    void Finalize()
    {
        if (hfont != NULL)
        {
            DeleteObject(hfont);
            hfont = NULL;
        }
    }

    COLORREF GetTextColor() const
    {
        return textColor;
    }

    void SetTextColor(COLORREF cref)
    {
        if(cref != textColor)
        {
            textColor = cref;

            Initialize(); // Initialize will Finalize first
        }
    }

    void WriteText(HDC _hdc, int _Xpos, int _Ypos, const TCHAR* _szMessage)
    {
        // set text color and font
        COLORREF oldTextColor = ::SetTextColor(_hdc, textColor);
        HFONT oldHFont = (HFONT)::SelectObject(_hdc, hfont);

        ::TextOut(_hdc, _Xpos, _Ypos, _szMessage, _tcslen(_szMessage));

        // restore text color and font
        ::SetTextColor(_hdc, oldTextColor);
        ::SelectObject(_hdc, oldHFont);
    }
};

TextWriter g_tw;

int APIENTRY
_tWinMain(HINSTANCE hInstance,
          HINSTANCE hPrevInstance,
          LPTSTR    lpCmdLine,
          int       nCmdShow)
{
    UNREFERENCED_PARAMETER(hPrevInstance);
    UNREFERENCED_PARAMETER(lpCmdLine);

    MyRegisterClass(hInstance);

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

    MSG msg = {0};
    while(GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }

    return (int)msg.wParam;
}

ATOM MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASSEX wcex;
    wcex.cbSize         = sizeof(WNDCLASSEX);
    wcex.style          = CS_HREDRAW | CS_VREDRAW;
    wcex.lpfnWndProc    = WndProc;
    wcex.cbClsExtra     = 0;
    wcex.cbWndExtra     = 0;
    wcex.hInstance      = hInstance;
    wcex.hIcon          = NULL; // no icon provided
    wcex.hCursor        = LoadCursor(NULL, IDC_ARROW);
    wcex.hbrBackground  = (HBRUSH)(COLOR_WINDOW + 1);
    wcex.lpszMenuName   = MAKEINTRESOURCE(IDR_MAIN);
    wcex.lpszClassName  = szWindowClass;
    wcex.hIconSm        = NULL; // no small icon provided

    return RegisterClassEx(&wcex);
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
    HWND hWnd = CreateWindow(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
        CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInstance, NULL);

    if(!hWnd)
    {
        return FALSE;
    }

    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);

    return TRUE;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch(uMsg)
    {
        case WM_CREATE:
        {
            g_tw.Initialize();

            return 0; // return 0 to indicate success
        }
        // no need for break

        case WM_DESTROY:
        {
            g_tw.Finalize();

            PostQuitMessage(0);
        }
        break;

        case WM_COMMAND:
        {
            int wmId    = LOWORD(wParam);
            int wmEvent = HIWORD(wParam);
            switch (wmId)
            {
            case IDM_EXIT:
                DestroyWindow(hWnd);
                break;
            case IDM_TEXT_COLOR:
                OnTextColor(hWnd);
                break;
            default:
                return DefWindowProc(hWnd, uMsg, wParam, lParam);
            }
        }
        break;

        case WM_PAINT:
        {
            PAINTSTRUCT ps = {0};
            HDC hdc = BeginPaint(hWnd, &ps);

            g_tw.WriteText(hdc, 50, 100, _T("Hello, cplusplus.com"));

            EndPaint(hWnd, &ps);
        }
        break;

        default:
            return DefWindowProc(hWnd, uMsg, wParam, lParam);
    }
    return 0;
}

// use standard color dialog for demo
void OnTextColor(HWND hWnd)
{
    COLORREF cref = g_tw.GetTextColor();

    static COLORREF acrCustClr[16]; // array of custom colors 

    CHOOSECOLOR cc = {0}; // initilize all members to zero
    cc.lStructSize  = sizeof(cc);
    cc.hwndOwner    = hWnd;
    cc.lpCustColors = acrCustClr; // cannot be NULL
    cc.Flags        = CC_RGBINIT;
    cc.rgbResult    = cref; // pre-select current color

    BOOL ret = ChooseColor(&cc);
    // note that it's up to the app to persist the custom colors
    // for the chooser to redisplay next time. Not done here!

    if(ret)
    {
        if(cc.rgbResult != cref)
        {
            g_tw.SetTextColor(cc.rgbResult);

            InvalidateRect(hWnd, NULL, TRUE);
        }
    }
}


Resource file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// TextWriter_Demo.rc

#include "winresrc.h"
#include "resource.h"

// Menu

IDR_MAIN MENU 
BEGIN
    POPUP "&File"
    BEGIN
        MENUITEM "E&xit", IDM_EXIT
    END
    POPUP "&View"
    BEGIN
        MENUITEM "Text &Color", IDM_TEXT_COLOR
    END
END


And resource header

1
2
3
4
5
6
// resource.h

#define IDR_MAIN                        100
#define IDM_EXIT                        101
#define IDM_TEXT_COLOR                  102
#define IDC_STATIC                      -1 

Last edited on
Andy, this is great... I created an instance of the TextWriter class and called it from inside WM_PAINT:

1
2
TextWriter tw;
tw.WriteText (hdc, 200, 50, "Andy rules");

... Then I moved the class to its own header file -- my first file that's separate from Main.cpp! So I'm getting the hang of it.

All works great in my Windows app, which performs some math operations on values read in from a text file.

However, I'm having trouble creating a menu... I've integrated your example code into my working Windows app (about 250 lines of code that reads strings from a text file, converts them to values, performs some math operations on the values, and shows the results in a window).

The .h and .rc files seem to be working fine, and in Main.cpp I've declared a handle to a menu (in the int APIENTRY WinMain procedure):

HMENU hMenu;

Then I loaded the menu from the resource:

hMenu = LoadMenu(hInstance, MAKEINTRESOURCE(IDR_MAIN));

And finally, I named the menu resource to be used in the window class:

1
2
ex.lpszMenuName = MAKEINTRESOURCE(hMenu);
ex.lpszMenuName = MAKEINTRESOURCE(IDR_MAIN);   // Also tried this here 

The project compiles and runs error-free but doesn't render a menu. I've gone through your code carefully and tried to integrate it with my own application, but must be missing something critical. Any ideas?

(Will create a new topic for this if necessary. Thanks again for the great code, Andy.)
Last edited on
You shouldn't need the LoadMenu or the HMENU for the main menu, unless you plan to switch it with another on the fly.

ex.lpszMenuName = MAKEINTRESOURCE(IDR_MAIN);

should be enough. So I have no idea what your problem is.

Have you tried simplifying your menu a bit?

(I take it the version of the menu I posted worked ok for you?)
Last edited on
I hate WINAPI. I know Winapi but i hate It.
Andy, with the menus, I think I was just having trouble linking the resource file in my compiler. Actually I never got that resolved -- but ended up generating menus using AppendMenu in the WM_CREATE section of my .cpp file.

So now I'm successfully using your TextWriter class in my program's main window. But I'd also like to use it to format text that I output to a menu-generated dialog box. For example, when the user selects "About" from the "Help" menu, the program displays a dialog box ("strMessage" is the contents of the dialog box):

MessageBoxW(hwnd, strMessage.c_str(), L"About", MB_OK);

Is it even possible to apply your TextWriter code to my About box? I realize that it works in the main window because TextWriter is taking the hdc handle, and thus paints to the screen once my main window has already been created. But I don't know how to make it compatible with a MessageBoxW type action -- as once the box is displayed, the program just sits there waiting for the user to close the box (i.e., it doesn't do anything else -- like call TextWriter).

How should I be thinking of this, or actually code it, if possible? Thank you again.
I am not aware of a way to customize MessageBox, aside from re-coding it!

Most people would use a dialog for the "about box". If you go that way, you can do your own drawing in a custom window. A common way is to add a static control to the dialog template where you want to put your custom window. Then in the WM_INITDIALOG handler you find the static control and replace it with your own window (which has its own WM_PAINT handler).

Note that to get this to work you need to give the static control a distinct ID, rather than the default IDC_STATIC.
Last edited on
Topic archived. No new replies allowed.