My class for constructing application windows

Pages: 12
closed account (3pj6b7Xj)
I've been working on this little project for over 4 days and tonight I finally got it running. Normally when you create a simple windows application, you declare a windows procedure, you enter win main, fill the window class, register the window class, create the window and show the window...along with that mess of steps comes knowing what to put in the structure or function, delaying from what ever you need to do.

Well, I created my own class that automatically initializes a default windows application and lets you change any of the window class structure settings or window creation data before displaying the application window. I know most of you have already done this for the sake of ease of programming but for me, it is a mile store, here what is my code looks like for a simple application!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
#include "CreateApp.h"

using namespace nsCtApp;

int WINAPI WinMain
(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR szArgs, int WindowState)
{
    /* create application window object */
    CreateApp ClientWin(hInstance);

    /* present application window */
    HWND hAppHandle = ClientWin.fnPresent();

    while(ClientWin.fnGetMessageLoop())
    {
         // insert program code here...
    }

     return 0;
}


By default it uses a windows procedure that is defined in the namespace nsCtApp , but you can define your own and point the class to it.

1
2
3
LRESULT CALLBACK MyProc(HWND, UINT, WPARAM, LPARAM);

ClientWin.fnWindowProc(MyProc); // point to new wnd proc. 


If I wanted to specify dimensions for my window, I would do this before calling fnPresent();

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
    /* create application window object */
    CreateApp ClientWin(hInstance);

    ClientWin.fnWindowX(100); // horizontal position
    ClientWin.fnWindowY(100); // vertical position
    ClientWin.fnWindowWidth(800); // width of window
    ClientWin.fnWindowHeight(600); // height of window

    /* present application window */
    HWND hAppHandle = ClientWin.fnPresent();

    while(ClientWin.fnGetMessageLoop())
    {
         // insert program code here...
    }


There is also a message loop PeekMessage()!

1
2
3
4
5
6
    while(!ClientWin.fnQuitApp())
    {
         ClientWin.fnPeekMessageLoop(); /* proccess any messages */
         
         // insert program code here...
    }


And the application does not stay running if you click the X button when using the PeekMessage loop, like borat says..."GREAT SUCCESS!"

This just makes windows programming life easier and with time I can improve this class. I'm happy to have finished this little project, now I can write less code and concentrate on what I need to do instead of worrying about details of creating an application window.

I could use some feedback, thanks guys! :)
closed account (3pj6b7Xj)
I made a butt load of improvements to these thing this morning, it is way better now. :) Thanks to Disch for making it clear about the operation of messages when windows closes a program, now I can use peek message and detect when the X button or WHEN the application is being requested to close and I can present a dialog confirming yes or no if I wanted too.

Now I will implement this into my Squibbles game if you all want to see previews, just visit my youtube channel /faostube thanks to all for great help.
i have one of these too, but i only use it when i need my windows to be oo. mine uses the std::map to store window handles, and if you want to create your own window, you just create a new class, and inherit the CWindow class, then over ride the WndProc.
I don't care much for OOP GUI frameworks, but I occasionally play with them. I have one I wrote a few weeks ago that just displays a window with a few buttons on it. It takes about three times the amount of code as a straight SDK app. However, I'll post it if anyone is interested. mrfaosfx if you would post a complete compilable app that is just a bare framework that shows how you did it I'd like to look at it to see what you did. I copied your code but it doesn't look compiliable to me.
closed account (3pj6b7Xj)
I'll do that for you freddie1, what is that you want to look at? My implementation of CreateApp class or an actual application that is fully operational using CreateApp? You can copy my code from the main cpp but it wont run if you don't have my CreateApp class.
Your mentioning this mrfaosfx got me interested in it again. Its kind of a recurring topic with me. Anyway, today I modified what I have for a class framework and made a very simple app, with all the code in one file, instead of breaking it up into *.cpp and *.h files as is typical of 'de riguer' C++ drill. The app has two buttons and two text boxes. The buttons say "Love" and "Hate". When you click either button it outputs a MessageBox and writes a message to the respective text box. I created the very same app with my typical classless Sdk Api style, which, however, uses my somewhat sophisticated message cracker routinws where function pointers are used to route Windows messages to the correct message handling routine, somewhatr like MFC, but it is 'classless' and more or less straight C. I wanted to compare code complexity, lines of code, and compiled size. The first post is the Window class attrempt, followed by the Sdk attempt. Maybe you could develop your version of it. I might learn something; perhaps others too. I compiled with GNU Code::Blocks, optimized for small code size...


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
237
238
239
240
241
242
243
244
245
246
247
248
#include <windows.h>             //22016 bytes;  248 lines of code
#include <stdio.h>
#define BTN_LOVE  1500
#define BTN_HATE  1505
#define TXT_LOVE  1510
#define TXT_HATE  1515
long __stdcall FrameWndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam);


class CWindow                         //CWindow
{
 public:
 CWindow(HINSTANCE);
 virtual ~CWindow(void);
 HWND Window(void);

 protected:
 HINSTANCE   m_hInst;
 HWND        m_hWnd;
};


CWindow::CWindow(HINSTANCE hIns)
{
 this->m_hInst=hIns;
}


CWindow::~CWindow()
{
 //fprintf(fp,"  Entering CWindow Destructor!\n");
}


HWND CWindow::Window()
{
 return this->m_hWnd;
}


class CButton                         //CButton
{
 public:
 CButton(LPSTR lpCaption, int x, int y, int cx, int cy, HWND hParent, int iCtrlId, HINSTANCE hIns)  //CButton Constructor
 {
  this->m_hBtn=CreateWindowEx(0,"button",lpCaption,WS_CHILD|WS_VISIBLE,x,y,cx,cy,hParent,(HMENU)iCtrlId,hIns,0);
 };
 ~CButton(){};   //CButton Destructor

 private:
 HWND m_hBtn;
};


class CEdit                         //CEdit -- Text Box Class
{
 public:
 CEdit(LPSTR lpCaption, int x, int y, int cx, int cy, HWND hParent, int iCtrlId, HINSTANCE hIns)  //CButton Constructor
 {
  this->m_hEdit=CreateWindowEx(WS_EX_CLIENTEDGE,"edit",lpCaption,WS_CHILD|WS_VISIBLE,x,y,cx,cy,hParent,(HMENU)iCtrlId,hIns,0);
 };

 void SetText(char* lpszText)
 {
  SetWindowText(this->m_hEdit,lpszText);
 }

 ~CEdit(){};   //CEdit Destructor

 private:
 HWND m_hEdit;
};


class CFrame : public CWindow         //CFrame
{
 friend
 long __stdcall FrameWndProc(HWND, UINT, WPARAM, LPARAM);

 public:
 CFrame(HINSTANCE, LPSTR, int);
 virtual ~CFrame(void);
 virtual BOOL Initialize(void);
 BOOL Create(int);
 BOOL Create(int,int,int,int,int);
 WPARAM MessageLoop(void);
 long OnCreate(HWND,CFrame*,LPARAM);
 long OnCommand(HWND, WPARAM, LPARAM);
 long OnDestroy(void);

 protected:
 LPSTR           m_pszCmdLine;
 int             m_nCmdShow;

 private:
 CButton*        m_pbtnLove;
 CButton*        m_pbtnHate;
 CEdit*          m_ptxtLove;
 CEdit*          m_ptxtHate;
};


CFrame::CFrame(HINSTANCE hIns, LPSTR lpCmdLn, int iShow) : CWindow(hIns)
{
 this->m_pszCmdLine=lpCmdLn;
 this->m_nCmdShow=iShow;
}


CFrame::~CFrame()
{
 //fprintf(fp,"  Entering CFrame Destructor!\n");
}


BOOL CFrame::Initialize(void)
{
 char szClassName[]="Form1";
 WNDCLASSEX wc;

 wc.lpszClassName=szClassName;                wc.lpfnWndProc=FrameWndProc;
 wc.cbSize=sizeof (WNDCLASSEX);               wc.style=CS_HREDRAW | CS_VREDRAW;
 wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);     wc.hInstance=this->m_hInst;
 wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION);  wc.hCursor=LoadCursor(NULL,IDC_ARROW);
 wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW;    wc.cbWndExtra=4;
 wc.lpszMenuName=NULL;                        wc.cbClsExtra=0;
 if(!RegisterClassEx(&wc))
    return FALSE;

 return TRUE;
}


BOOL CFrame::Create(int iShow)   //overloaded constructor
{
 this->m_hWnd=
 CreateWindowEx
 (
  0,"Form1","Form1",WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,HWND_DESKTOP,0,this->m_hInst,this
 );
 SetWindowLong(this->m_hWnd,0,(long)this);
 ShowWindow(this->m_hWnd,iShow);

 return (int)this->m_hWnd;
}


BOOL CFrame::Create(int iShow, int x, int y, int iWidth, int iHeight)    //overloaded constructor
{
 this->m_hWnd=
 CreateWindowEx
 (
  0,"Form1","Form1",WS_OVERLAPPEDWINDOW,x,y,iWidth,iHeight,HWND_DESKTOP,0,this->m_hInst,this
 );
 SetWindowLong(this->m_hWnd,0,(long)this);
 ShowWindow(this->m_hWnd,iShow);

 return (int)this->m_hWnd;
}


long CFrame::OnCreate(HWND hWnd, CFrame* pFrameWnd, LPARAM lParam)
{
 CREATESTRUCT* pCreateStruct;

 pCreateStruct=(CREATESTRUCT*)lParam;
 pFrameWnd=(CFrame*)pCreateStruct->lpCreateParams;
 pFrameWnd->m_pbtnLove=new CButton((char*)"Love",125,15,80,30,hWnd,BTN_LOVE,pFrameWnd->m_hInst);
 pFrameWnd->m_ptxtLove=new CEdit((char*)"",25,60,285,22,hWnd,TXT_LOVE,pFrameWnd->m_hInst);
 pFrameWnd->m_pbtnHate=new CButton((char*)"Hate",125,105,80,30,hWnd,BTN_HATE,pFrameWnd->m_hInst);
 pFrameWnd->m_ptxtHate=new CEdit((char*)"",25,150,285,22,hWnd,TXT_LOVE,pFrameWnd->m_hInst);

 return 0L;
}


long CFrame::OnCommand(HWND hWnd, WPARAM wParam, LPARAM lParam)
{
 CFrame* pFrame=NULL;

 pFrame=(CFrame*)GetWindowLong(hWnd,0);
 switch(LOWORD(wParam))
 {
  case BTN_LOVE:
    pFrame->m_ptxtLove->SetText((char*)"The World Needs More Love!");
    MessageBox(hWnd,"....You Want More Love!","Here Is What You Want...",MB_OK);
    break;
  case BTN_HATE:
    pFrame->m_ptxtHate->SetText((char*)"The World Needs More Hate!");
    MessageBox(hWnd,"....You Want More Hate!","Here Is What You Want...",MB_OK);
    break;
 }

 return 0L;
}


long CFrame::OnDestroy()
{
 PostQuitMessage(0);
 return 0L;
}


long __stdcall FrameWndProc(HWND hWnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 CFrame* pFrameWnd=(CFrame*)GetWindowLong(hWnd,0);

 switch(msg)
 {
  case WM_CREATE:
    return pFrameWnd->OnCreate(hWnd,pFrameWnd,lParam);
  case WM_COMMAND:
    return pFrameWnd->OnCommand(hWnd,wParam,lParam);
  case WM_DESTROY:
    return pFrameWnd->OnDestroy();
 }

 return (DefWindowProc(hWnd, msg, wParam, lParam));
}


WPARAM CFrame::MessageLoop(void)
{
 MSG msg;

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

 return msg.wParam;
}


int __stdcall WinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
 WPARAM wRet;

 CFrame* pFrame=new CFrame(hIns,lpszArgument,iShow);
 pFrame->Initialize();
 pFrame->Create(iShow,150,150,350,250);
 wRet=pFrame->MessageLoop();
 delete pFrame;

 return wRet;
}



long CFrame::OnCreate(HWND hWnd, CFrame* pFrameWnd, LPARAM lParam) //is tricky, as I had to pass a pointer to the CFrame class in through the Creation Parameters.
Last edited on
Here is my 'simpler' version, which is only 116 lines of code and 8704 bytes; 2-1/2 times smaller; but same program, which is why I don't care much for classes wrapping Windows GUI functionality (I kinda hate code bloat, can't say why, I just do)...

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
//Form9.cpp -- compiler switches /O1 /Oi /Os /GL /D  8704 bytes; 116 lines
#include <windows.h>
#include <tchar.h>
#define IDC_LOVE  1500
#define IDC_HATE  1505
#define TXT_LOVE  1510
#define TXT_HATE  1515


typedef struct    WindowsEventArguments
{
 HWND             hWnd;
 WPARAM           wParam;
 LPARAM           lParam;
 HINSTANCE        hIns;
}WndEventArgs,    *lpWndEventArgs;


struct EVENTHANDLER
{
 unsigned int     Code;
 long             (*fnPtr)(lpWndEventArgs);
}EventHandler[3];


long fnWndProc_OnCreate(lpWndEventArgs Wea)
{
 HWND hCtl;

 Wea->hIns=((LPCREATESTRUCT)Wea->lParam)->hInstance;
 hCtl=CreateWindowEx(0,_T("button"),_T("Love"),WS_CHILD|WS_VISIBLE,125,15,80,30,Wea->hWnd,(HMENU)IDC_LOVE,Wea->hIns,0);
 hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,"edit",_T(""),WS_CHILD|WS_VISIBLE,25,60,285,22,Wea->hWnd,(HMENU)TXT_LOVE,Wea->hIns,0);
 hCtl=CreateWindowEx(0,_T("button"),_T("Hate"),WS_CHILD|WS_VISIBLE,125,105,80,30,Wea->hWnd,(HMENU)IDC_HATE,Wea->hIns,0);
 hCtl=CreateWindowEx(WS_EX_CLIENTEDGE,"edit",_T(""),WS_CHILD|WS_VISIBLE,25,150,285,22,Wea->hWnd,(HMENU)TXT_HATE,Wea->hIns,0);

 return 0;
}


long fnWndProc_OnCommand(lpWndEventArgs Wea)
{
 switch(LOWORD(Wea->wParam))
 {
   case IDC_LOVE:
     SetWindowText(GetDlgItem(Wea->hWnd,TXT_LOVE),_T("The World Needs More Love!"));
     MessageBox(Wea->hWnd,_T("...More Love!"),_T("This Is What You Want..."),MB_OK);
     break;
   case IDC_HATE:
     SetWindowText(GetDlgItem(Wea->hWnd,TXT_HATE),_T("The World Needs More Hate!"));
     MessageBox(Wea->hWnd,_T("...More Hate!"),_T("This Is What You Want..."),MB_OK);
     break;
 }

 return 0;
}


long fnWndProc_OnClose(lpWndEventArgs Wea)
{
 DestroyWindow(Wea->hWnd);
 PostQuitMessage(0);
 return 0;
}


void AttachEventHandlers(void)
{
 EventHandler[0].Code=WM_CREATE,    EventHandler[0].fnPtr=fnWndProc_OnCreate;
 EventHandler[1].Code=WM_COMMAND,   EventHandler[1].fnPtr=fnWndProc_OnCommand;
 EventHandler[2].Code=WM_CLOSE,     EventHandler[2].fnPtr=fnWndProc_OnClose;
}


long __stdcall fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 WndEventArgs Wea;
 
 for(unsigned i=0; i<3 ; i++)
 {
     if(EventHandler[i].Code==msg)
     {
        Wea.hWnd=hwnd,Wea.lParam=lParam,Wea.wParam=wParam;
        return (*EventHandler[i].fnPtr)(&Wea);
     }
 }

 return (DefWindowProc(hwnd,msg,wParam,lParam));
}


int __stdcall WinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
 TCHAR szClassName[]=_T("Form9");
 WNDCLASSEX wc;
 MSG messages;
 HWND hWnd;

 AttachEventHandlers();
 wc.lpszClassName=szClassName;                wc.lpfnWndProc=fnWndProc;
 wc.cbSize=sizeof (WNDCLASSEX);               wc.style=CS_DBLCLKS;
 wc.hIcon=LoadIcon(NULL,IDI_APPLICATION);     wc.hInstance=hIns;
 wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION);  wc.hCursor=LoadCursor(NULL,IDC_ARROW);
 wc.hbrBackground=(HBRUSH)COLOR_BTNSHADOW;    wc.cbWndExtra=0;
 wc.lpszMenuName=NULL;                        wc.cbClsExtra=0;
 RegisterClassEx(&wc);
 hWnd=CreateWindowEx(0,szClassName,_T("Form9"),WS_OVERLAPPEDWINDOW,200,100,340,240,HWND_DESKTOP,0,hIns,0);
 ShowWindow(hWnd,iShow);
 while(GetMessage(&messages,NULL,0,0))
 {
       TranslateMessage(&messages);
       DispatchMessage(&messages);
 }

 return messages.wParam;
}
Last edited on
closed account (3pj6b7Xj)
That is very clever, I will construct the same application using my class and post it. give me some time bro :)
Don't think I'm so smart mrfaosfx! For a long while I wondered how professional class frameworks were written. MFC and OWL were somewhat mysteries to me. Somewhere along the line in some thread in some forum I 'heard' it mentioned that the problematic WndProc was handled by making it a 'friend' of the Frame Wnd Proc. I never looked into it further then about a month or two ago when I was perusing an old COM book 'Inside COM' 1st edition by Kraig Brockschmidt of Microsoft (1993), and in that book he discussed building a simple class framework which he used for the examples in his book. I was real interested in that and studied it real close, and developed most of what I have posted above using his ideas. Today though I added the text boxes. The cool part is getting the pointer to the class instantiation out of the .cbWndExtra bytes.



closed account (3pj6b7Xj)
I've just started the windows programming trend only 3 months ago...all I brought with me was my knowledge of C++ ... the windows api itself still is mysterious to me......there are some things that I look at and ask myself......well why didn't they make it like that in the first place!? Such as the background property of a window class? If you specify a color for the background, windows uses that color as the background...you drag your box off the screen and notice half the stuff on the windows client is missing.....then you set the background to NULL and create your own background....do the same thing and notice that this no longer happens.....thats just weird functionality.

I'm still learning windows programming and about 2 days ago I visited Barns & Nobles and BORDERS in the search of a windows programming book, NOTHING...NOTHING AT ALL..I can't find a windows programming book anywhere!! I'm tired of reading off the screen, I want to lay on my bed and relax while I read....now i've realized I haven't checked amazon.com, ugh can't hurt.
You mean to tell me you are not aware of 'The Bible' of Windows Programming mrfaosfx???? If that's the case I'm going to turn you on to one of what might be a life changing experience for you, and that is Charles Petzold's "Programming Windows". Its how most folks learned Windows Programming during Windows first 15 years of existance, i.e., 1985 through 2000. I have both his Windows 95 book, which is out of print but still easily available used for little more than the cost of shipping, and his Windows 2000 book which is still in print and available from Amazon.com. Most hard core low level Windows coders swear by is books. They are straight C, which raises a whole interesting issue, which I'm now about to address in some detail.....
Last edited on
...and as a vehicle of discussion I'll address it with reference to the above code. Charles maintains that Windows Api Gui coding is a type of Object Oriented Programming (OOP); albiet in C. This is true and not all that unusual, although I think many C++ coders have difficulty with this concept. And as evidence for this I submit to you the frequent complaint we see in these C++ forums when some new C++ console mode programmer is pointed to the Forger's Win32 Api Tutorial. They lament that it is written in C, which they consider to be some kind of 2nd rate language which ought to just fade away, and they want to do Windows with C++.

Central to the idea of OOP is the intimate association of data with the code that minipulates that data. From C we got the idea of a struct, and we created external functions which only worked with data contained in particular structs. When C++ came along the formal articulation of functions within structs came about and functions were no longer external to the struct; we called these classes. Of course, in C you could still define function pointers within structs, but that is not as clean as the higher level C++ solution of classes.

In the case of the Windows Api the WNDCLASSEX struct is of key importance because it defines a class template for a Window Class complete with a function pointer member, i.e., the pointer to the Window Procedure. Also of critical importance are the methods Microsoft created to associate data with an instantiated window of a class, and these methods would be either the .cbWndExtra bytes in the WNDCLASSEX struct, and the GetProp()/SetProp() Api functions. These serve the same purposes as what in OOP jargon are known as class accessors/mutators. The idea is that an instantiated window created with a CreateWindow() call has some purpose in life, i.e., it has data with which it accomplishes some meaningful task, and in both OOP terms and in terms of proper programming practices this data shouldn't be globally accessable, but should be tightly associated and bound with the object, i.e., the window, which minipulates it. When one creates a Window Class then what one should do is create a struct or class containing the data the window is to minipulate, perform a memory allocation for the object, then store a pointer to it in extra space allocated within the Window Class struct. This data will only be accessable from a window of the class through the hWnd passed in through the Window Procedure. This concept is how OOP in the C based Windows Api works....

to be continued...
closed account (3pj6b7Xj)
I some how always knew this was possible, except that C doesn't do the data hiding like C++ but it can all still be done I guess. I will look for those two books! If I can't find them in print, I might just have to buy that Kindle, going to hit the pockets this week but hey, I believe I'll get my money's worth......lol this is beginning to sound like an advertising campaign.
His last edition is available new yet but is very expensive.

http://www.amazon.com/Programming-Windows-Microsoft-Charles-Petzold/dp/157231995X/ref=sr_1_1?ie=UTF8&qid=1293042039&sr=8-1

Due to the price I think I'd be leaning in favor of a used copy. Especially if you could get one with a cd, because the book is massive and has massive code listings (they all work, too). Perhaps the code is posted somewhere, however. Truth be told, his Windows 95 book would suffice. The concepts don't really change.
....getting back to my discussion of OOP without classes using the Win32 Api paradigm, below is what I think might be an interesting program to run and think about. The program creates not one but four main program windows using a for loop in WinMain. Each window displays the mouse x/y coordinates as the mouse passes over it, window dimensions when being resized, character strings as they are typed, and location information of left mouse button clicks. It uses TextOut() to do this on each respective window. This information is of course obtained in window message handlers. However, live updates to a window's display in Windows should only occur in response to a WM_PAINT message, and that is what is done here. This program, however, has NO global variables to persist WM_CHAR, WM_MOUSEMOVE, WM_LBUTTONDON, or WM_SIZE messages. So, how is this information then persisted across invocations of the Window Procedure to be output during a WM_PAINT message, which is a seperate invocation of the Window Procedure???

The answer is that a WM_CREATE message can be likened to a call to an object constructor in C++, and in each Window's WM_CREATE handler a memory allocation for a structure to retain window inputs is allocated and stored within each respective windows cbWndExtra bytes memory (allocated with RegisterClassEx()). This program uses my 'minimal' String class (also posted), which is more efficient than the C++ standard string class. With VC9 optimized for small code size this comes in around 11K for me...

//Typer
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
//Main.cpp
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include "Strings.h"

struct ProgramData
{
 short int xMouse;
 short int yMouse;
 short int xSize;
 short int ySize;
 short int xButton;
 short int yButton;
};

LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
{
 ProgramData* pPD=NULL;

 switch(msg)
 {
    case WM_CREATE:
    {
         long iCntr=0;
         String* pStr;
         pStr=new String;
         SetWindowLong(hwnd,0,(long)pStr);
         pPD=(ProgramData*)GlobalAlloc(GPTR,sizeof(ProgramData));
         SetWindowLong(hwnd,4,(long)pPD);
         iCntr=GetClassLong(hwnd,0);
         iCntr++;
         SetClassLong(hwnd,0,iCntr);
         return 0;
    }
    case WM_SIZE:
    {
         pPD=(ProgramData*)GetWindowLong(hwnd,4);
         pPD->xSize=LOWORD(lParam);
         pPD->ySize=HIWORD(lParam);
         InvalidateRect(hwnd,NULL,TRUE);
         return 0;
    }
    case WM_CHAR:
    {
         String* pStr;
         pStr=(String*)GetWindowLong(hwnd,0);
         *pStr=*pStr+wParam;
         InvalidateRect(hwnd,NULL,FALSE);
         return 0;
    }
    case WM_MOUSEMOVE:
    {
         pPD=(ProgramData*)GetWindowLong(hwnd,4);
         pPD->xMouse=LOWORD(lParam);
         pPD->yMouse=HIWORD(lParam);
         InvalidateRect(hwnd,NULL,TRUE);
         return 0;
    }
    case WM_LBUTTONDOWN:
    {
         pPD=(ProgramData*)GetWindowLong(hwnd,4);
         pPD->xButton=LOWORD(lParam);
         pPD->yButton=HIWORD(lParam);
         InvalidateRect(hwnd,NULL,FALSE);
         return 0;
    }
    case WM_PAINT:
    {
         PAINTSTRUCT ps;
         String s1,s2,s3;
         String* pStr;
         HDC hDC;
         hDC=BeginPaint(hwnd,&ps);
         pPD=(ProgramData*)GetWindowLong(hwnd,4);
         if(pPD)
         {
            s1=pPD->xMouse;
            s2=pPD->yMouse;
            s3=_T("xMouse=");
            s3=s3+s1+_T("  yMouse=")+s2+_T("  ");
            TextOut(hDC,0,0,s3.lpStr(),s3.LenStr());
            if(pPD->xButton||pPD->yButton)
            {
               s1=pPD->xButton;
               s2=pPD->yButton;
               s3=_T("xButton=");
               s3=s3+s1+_T("  yButton=")+s2+_T("  ");
               TextOut(hDC,pPD->xButton+12,pPD->yButton,s3.lpStr(),s3.LenStr());
               pPD->xButton=0, pPD->yButton=0;
            }
            s1=pPD->xSize;
            s2=pPD->ySize;
            s3=_T("Width=");
            s3=s3+s1+_T("  Height=")+s2+_T("  ");
            TextOut(hDC,0,20,s3.lpStr(),s3.LenStr());
            pStr=(String*)GetWindowLong(hwnd,0);
            if(pStr)
               TextOut(hDC,0,40,pStr->lpStr(),pStr->LenStr());
         }
         EndPaint(hwnd,&ps);
         return 0;
    }
    case WM_DESTROY:
    {
         long iCntr=0;
         String* pStr;
         pStr=(String*)GetWindowLong(hwnd,0);
         delete pStr;
         pPD=(ProgramData*)GetWindowLong(hwnd,4);
         GlobalFree(pPD);
         iCntr=GetClassLong(hwnd,0);
         iCntr--;
         if(iCntr==0)
         {
            MessageBox(hwnd,_T("No More Windows Left!"),_T("Will Now Close..."),MB_OK);
            PostQuitMessage(0);
         }
         else
            SetClassLong(hwnd,0,iCntr);
         return 0;
    }
 }

 return (DefWindowProc(hwnd, msg, wParam, lParam));
}


int WINAPI WinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
{
 TCHAR szClassName[]=_T("Form2");
 TCHAR szCaption[80];
 WNDCLASSEX wc;
 MSG messages;
 HWND hWnd;

 wc.lpszClassName=szClassName,                          wc.lpfnWndProc=fnWndProc;
 wc.cbSize=sizeof (WNDCLASSEX),                         wc.style=0;
 wc.hIcon=LoadIcon(NULL,IDI_APPLICATION),               wc.hInstance=hIns;
 wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION),            wc.hCursor=LoadCursor(NULL,IDC_ARROW);
 wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH),  wc.cbWndExtra=8;
 wc.lpszMenuName=NULL,                                  wc.cbClsExtra=4;
 RegisterClassEx(&wc);
 _tcscpy(szCaption,_T("Try Typing Text, Moving Mouse, Clicking Left Mouse Button, Or Sizing Window!"));
 for(unsigned i=0; i<4; i++)
 {
     hWnd=CreateWindowEx(0,szClassName,szCaption,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,600,275,0,0,hIns,0);
     ShowWindow(hWnd,iShow);
 }                                        //Lets take the unusual step of creating four main program windows.  There
 while(GetMessage(&messages,NULL,0,0))    //are no global variables in this program, and each window's data is stored
 {                                        //within each window's instantiated class structure.Every window is completely
     TranslateMessage(&messages);         //independent of every other window.
     DispatchMessage(&messages);
 }

 return messages.wParam;
}
Here is Strings.h referenced above...

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
//Strings.h
#if !defined(STRINGS_H)
#define STRINGS_H

class String
{
 public:
 String();                                            //Uninitialized Constructor - Does nothing                                           //done
 String& operator=(const TCHAR);                      //Assigns TCHAR To String                                                            //done
 String& operator=(const TCHAR* pStr);                //Assigns TCHAR* To String                                                           //done
 String& operator=(const String&);                    //Assigns one String to another (this one)                                           //done
 String& operator=(int);                              //Converts And Assigns An Integer to A String                                        //done
 String& operator=(unsigned int);                     //Converts And Assigns An Unsigned Integer to A String                               //done
 String& operator=(long);                             //Converts And Assigns A Long to A String                                            //done
 String& operator=(DWORD);                            //Converts And Assigns A DWORD to A String                                           //done
 String& operator=(double);                           //Converts And Assigns A double to A String                                          //done
 String& operator+(const TCHAR);                      //For adding TCHAR to String                                                         //done
 String& operator+(const TCHAR*);                     //For adding null terminated TCHAR array to String                                   //done
 String& operator+(String&);                          //For adding one String to Another                                                   //done
 bool operator==(const String);                       //For comparing Strings                                                              //done
 String Left(unsigned int);                           //Returns String of iNum Left Most TCHARs of this                                    //done
 String Right(unsigned int);                          //Returns String of iNum Right Most TCHARs of this                                   //done
 String Mid(unsigned int, unsigned int);              //Returns String consisting of number of TCHARs from some offset                     //done
 int InStr(const TCHAR);                              //Returns one based offset of a specific TCHAR in a String                           //done
 int InStr(const TCHAR*, bool);                       //Returns one based offset of a particular TCHAR pStr in a String                    //done
 int InStr(const String&, bool);                      //Returns one based offset of where a particular String is in another String         //done
 void LTrim();                                        //Returns String with leading spaces/tabs removed                                    //done
 void RTrim();                                        //Returns String with spaces/tabs removed from end                                   //done
 void Trim();                                         //Returns String with both leading and trailing whitespace removed                   //done
 unsigned int ParseCount(const TCHAR);                //Returns count of Strings delimited by a TCHAR passed as a parameter                //done
 void Parse(String*, TCHAR);                          //Returns array of Strings in first parameter as delimited by 2nd TCHAR delimiter    //done
 int iVal();                                          //Returns int value of a String                                                      //done
 int LenStr();                                        //Returns length of string                                                           //done
 TCHAR* lpStr();                                      //Returns address of pStrBuffer member variable                                      //done
 void Print(bool);                                    //Outputs String to Console with or without CrLf                                     //done
 ~String();                                           //Destructor                                                                         //done

 private:
 TCHAR* pStrBuffer;                                   //Pointer To String Buffer
};

String operator+(TCHAR* lhs, String& rhs);             //global function
#endif  //#if !defined(STRINGS_H)
I hope Strings.cpp fits!

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
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
//Strings.cpp
#include  <windows.h>
#include  <tchar.h>
#include  <stdlib.h>
#include  <stdio.h>
#include  <string.h>
#include  "Strings.h"


String operator+(TCHAR* lhs, String& rhs)          //global function
{
 String sr;

 sr=lhs;
 sr=sr+rhs;

 return sr;
}


String::String()                                   //Uninitialized Constructor - Does nothing
{
 pStrBuffer=new TCHAR[1];
 pStrBuffer[0]=_T('\0');
}


String& String::operator=(const TCHAR ch)          //Overloaded operator = for assigning a
{                                                  //character to a String
 if(this->pStrBuffer)
    delete [] this->pStrBuffer;
 pStrBuffer=new TCHAR[2];
 this->pStrBuffer[0]=ch;
 this->pStrBuffer[1]=TEXT('\0');

 return *this;
}


String& String::operator=(const TCHAR* pStr)
{
 if(this->pStrBuffer)
    delete [] pStrBuffer;
 pStrBuffer=new TCHAR[_tcslen(pStr)+1];
 _tcscpy(pStrBuffer,pStr);

 return *this;
}


String& String::operator=(const String& strRight)   //Overloaded operator = for assigning
{                                                   //another String to a String
 if(this==&strRight)
    return *this;
 delete [] this->pStrBuffer;
 pStrBuffer=new TCHAR[_tcslen(strRight.pStrBuffer)+1];
 _tcscpy(pStrBuffer,strRight.pStrBuffer);

 return *this;
}


String& String::operator=(int iNum)
{
 delete [] this->pStrBuffer;
 pStrBuffer=new TCHAR[16];
 _stprintf(this->pStrBuffer,_T("%d"),iNum);

 return *this;
}


String& String::operator=(unsigned int iNum)
{
 delete [] this->pStrBuffer;
 pStrBuffer=new TCHAR[16];
 _stprintf(this->pStrBuffer,_T("%d"),iNum);

 return *this;
}

String& String::operator=(long iNum)
{
 delete [] this->pStrBuffer;
 pStrBuffer=new TCHAR[16];
 _stprintf(this->pStrBuffer,_T("%ld"),iNum);

 return *this;
}


String& String::operator=(DWORD iNum)
{
 delete [] this->pStrBuffer;
 pStrBuffer=new TCHAR[16];
 _stprintf(this->pStrBuffer,_T("%u"),(unsigned)iNum);

 return *this;
}


String& String::operator=(double dblNum)
{
 delete [] this->pStrBuffer;
 pStrBuffer=new TCHAR[24];
 _stprintf(this->pStrBuffer,_T("%10.14f"),dblNum);

 return *this;
}


String& String::operator+(const TCHAR ch)     //Overloaded operator + (Puts char in String)
{
 unsigned int iLen;
 TCHAR* pNew;

 if(this->pStrBuffer)                         //1st term (pStrBuffer) isn't empty (NULL)
 {
    iLen=_tcslen(this->pStrBuffer);
    pNew=new TCHAR[iLen+2];
    _tcscpy(pNew,this->pStrBuffer);
    delete [] this->pStrBuffer;
    this->pStrBuffer=pNew;
    this->pStrBuffer[iLen]=ch;
    this->pStrBuffer[iLen+1]=TEXT('\0');
 }
 else                                         //1st string (this->pStrBuffer) is empty (NULL)
 {
    pStrBuffer=new TCHAR[2];
    this->pStrBuffer[0]=ch;
    this->pStrBuffer[1]=TEXT('\0');
 }

 return *this;
}


String& String::operator+(const TCHAR* pChar) //Overloaded operator + (Adds char literals)
{
 TCHAR* pNew;

 if(this->pStrBuffer)                   //1st term (pStrBuffer) isn't empty (NULL)
 {
    pNew=new TCHAR[_tcslen(this->pStrBuffer)+_tcslen(pChar)+1];
    _tcscpy(pNew,this->pStrBuffer);
    delete [] this->pStrBuffer;
    this->pStrBuffer=pNew;
    _tcscat(this->pStrBuffer,pChar);
 }
 else                                   //1st string (this->pStrBuffer) is empty (NULL)
 {
    pNew=new TCHAR[_tcslen(pChar)+1];
    this->pStrBuffer=pNew;
    _tcscpy(this->pStrBuffer,pChar);
 }

 return *this;
}


String& String::operator+(String& strRight)  //Overloaded operator + Adds Another String
{                                            //to the left operand
 TCHAR* pNew;

 if(this->pStrBuffer)
 {
    pNew=new TCHAR[_tcslen(this->pStrBuffer)+_tcslen(strRight.pStrBuffer)+1];
    _tcscpy(pNew,this->pStrBuffer);
    delete [] this->pStrBuffer;
    this->pStrBuffer=pNew;
    _tcscat(this->pStrBuffer,strRight.pStrBuffer);
 }
 else
 {
    this->pStrBuffer=new TCHAR[_tcslen(strRight.pStrBuffer)+1];
    _tcscpy(this->pStrBuffer,strRight.pStrBuffer);
 }

 return *this;
}


bool String::operator==(const String strCompare)
{
 if(_tcscmp(this->pStrBuffer,strCompare.pStrBuffer)==0)  //_tcscmp
    return true;
 else
    return false;
}


String String::Left(unsigned int iNum)
{
 unsigned int iLen,i;
 String sr;

 iLen=_tcslen(this->pStrBuffer);
 if(iNum<iLen)
 {
    sr.pStrBuffer=new TCHAR[iNum+1];
    for(i=0;i<iNum;i++)
        sr.pStrBuffer[i]=this->pStrBuffer[i];
    sr.pStrBuffer[iNum]=TEXT('\0');
    return sr;
 }
 else
 {
    sr=*this;
    return sr;
 }
}


String String::Right(unsigned int iNum)  //Returns Right$(strMain,iNum)
{
 unsigned int iLen,i,j;
 String sr;

 iLen=_tcslen(this->pStrBuffer);
 if(iNum<iLen)
 {
    j=0;
    sr.pStrBuffer=new TCHAR[iNum+1];
    for(i=iLen-iNum;i<=iLen;i++)
    {
        sr.pStrBuffer[j]=this->pStrBuffer[i];
        j++;
    }
    sr.pStrBuffer[iNum]=TEXT('\0');
    return sr;
 }
 else
 {
    sr=*this;
    return sr;
 }
}


String String::Mid(unsigned int iStart, unsigned int iCount)
{
 unsigned int iLen,i,j;
 String sr;

 iLen=_tcslen(this->pStrBuffer);
 if(iStart && iStart<=iLen)
 {
    if(iCount && iStart+iCount-1<=iLen)
    {
       j=0;
       sr.pStrBuffer=new TCHAR[iCount+1];
       for(i=iStart-1;i<iStart+iCount-1;i++)
       {
           sr.pStrBuffer[j]=this->pStrBuffer[i];
           j++;
       }
       sr.pStrBuffer[iCount]=TEXT('\0');
       return sr;
    }
    else
    {
       sr=*this;
       return sr;
    }
 }
 else
 {
    sr=*this;
    return sr;
 }
}


int String::InStr(const TCHAR ch)
{
 int iLen,i;

 iLen=_tcslen(this->pStrBuffer);
 for(i=0;i<iLen;i++)
 {
     if(this->pStrBuffer[i]==ch)
        return (i+1);
 }

 return 0;
}


int String::InStr(const TCHAR* pStr, bool blnCaseSensitive)
{
 int i,iParamLen,iRange;

 if(*pStr==0)
    return 0;
 iParamLen=_tcslen(pStr);
 iRange=_tcslen(pStrBuffer)-iParamLen;
 if(iRange>=0)
 {
    for(i=0;i<=iRange;i++)
    {
        if(blnCaseSensitive)
        {
           if(_tcsncmp(pStrBuffer+i,pStr,iParamLen)==0)   //_tcsncmp
              return i+1;
        }
        else
        {
           if(_tcsnicmp(pStrBuffer+i,pStr,iParamLen)==0)  //__tcsnicmp
              return i+1;
        }
    }
 }

 return 0;
}


int String::InStr(const String& s, bool blnCaseSensitive)
{
 int i,iParamLen,iRange,iLen;

 iLen=_tcslen(s.pStrBuffer);
 if(iLen==0)
    return 0;
 iParamLen=iLen;
 iRange=_tcslen(pStrBuffer)-iParamLen;
 if(iRange>=0)
 {
    for(i=0;i<=iRange;i++)
    {
        if(blnCaseSensitive)
        {
           if(_tcsncmp(pStrBuffer+i,s.pStrBuffer,iParamLen)==0)  //_tcsncmp
              return i+1;
        }
        else
        {
           if(_tcsnicmp(pStrBuffer+i,s.pStrBuffer,iParamLen)==0) //__tcsnicmp
              return i+1;
        }
    }
 }

 return 0;
}


void String::LTrim()
{
 unsigned int i,iCt=0,iLenStr;

 iLenStr=this->LenStr();
 for(i=0;i<iLenStr;i++)
 {
     if(pStrBuffer[i]==32||pStrBuffer[i]==9)
        iCt++;
     else
        break;
 }
 if(iCt)
 {
    for(i=iCt;i<=iLenStr;i++)
        pStrBuffer[i-iCt]=pStrBuffer[i];
 }
}
....here's the rest. It didn't fit...

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


void String::RTrim()
{
 unsigned int iCt=0, iLenStr;

 iLenStr=this->LenStr()-1;
 for(unsigned int i=iLenStr; i>0; i--)
 {
     if(this->pStrBuffer[i]==9||this->pStrBuffer[i]==10||this->pStrBuffer[i]==13||this->pStrBuffer[i]==32)
        iCt++;
     else
        break;
 }
 this->pStrBuffer[this->LenStr()-iCt]=0;
}


void String::Trim()
{
 this->LTrim();
 this->RTrim();
}


unsigned int String::ParseCount(const TCHAR c)  //returns one more than # of
{                                               //delimiters so it accurately
 unsigned int iCtr=0;                           //reflects # of strings delimited
 TCHAR* p;                                      //by delimiter.

 p=this->pStrBuffer;
 while(*p)
 {
  if(*p==c)
     iCtr++;
  p++;
 }

 return ++iCtr;
}


void String::Parse(String* pStr, TCHAR delimiter)
{
 unsigned int i=0;
 TCHAR* pBuffer=0;
 TCHAR* c;
 TCHAR* p;

 pBuffer=new TCHAR[this->LenStr()+1];
 if(pBuffer)
 {
    p=pBuffer;
    c=this->pStrBuffer;
    while(*c)
    {
     if(*c==delimiter)
     {
        pStr[i]=pBuffer;
        //printf("Assigned pStr[%u] In Parse()\n",i);
        //printf("pStr[%u]=%s\n\n",i,pStr[i].lpStr());
        p=pBuffer;
        i++;
     }
     else
     {
        *p=*c;
        p++;
        *p=0;
     }
     c++;
    }
    pStr[i]=pBuffer;
    delete [] pBuffer;
 }
}


int String::iVal()
{
 return _ttoi(this->pStrBuffer);
}


int String::LenStr(void)
{
 int iLen=_tcslen(this->pStrBuffer);
 if(iLen)
    return iLen;
 else
    return 0;
}


TCHAR* String::lpStr()
{
 return pStrBuffer;
}


void String::Print(bool blnCrLf)
{
 _tprintf(_T("%s"),pStrBuffer);
 if(blnCrLf)
    _tprintf(_T("\n"));
}


String::~String()   //String Destructor
{
 delete [] pStrBuffer;
 pStrBuffer=0;
}
Oh, by the way, the book where I got the idea for the class framework wasn't 'Inside COM' but 'Inside OLE' by Kraig Brockschmidt. 'Inside COM' was Dale Rogerson's book. 'Inside DCOM' Guy and Henry Eddon's.
closed account (3pj6b7Xj)
You just posted treasure for the C++ newbies lol ... I happen to be working on a string class myself but I keep asking myself if its even worth the bother since the STL already has a nice string class and works very well, there is always room for improvement.
Pages: 12