· 5 years ago · May 04, 2020, 07:38 PM
1/*
2 ProgEx38a -- Form2 - Window Procedure And Windows Messages. C++ Win32 Sdk Api Program Shows
3 Various Windows WM_XXXXXX Messages And Standard WndProc Construction.
4
5
6 Form1 only processed WM_CREATE, WM_LBUTTONDOWN, WM_PAINT, and WM_DESTROY Windows messages in
7 its Window Procedure. This program - Form2 - will process all these messages more fully; but
8 in addition, will process WM_SIZE, WM_CHAR, and WM_MOUSEMOVE. Our goals here are rather lofty.
9 What we want to do is create a window as we did in Form1, but when we move the mouse cursor
10 over the window, we want displayed right on the Window's surface client area the x and y pixel
11 location of the cursor. The program must display a running tally of this. Next, if we resize
12 the Window by grabbing the borders with the mouse, we also want a continuous update of the
13 width and height of the Window's client area. Further, if we click anywhere in the client area
14 of the window with the left mouse button, we want displayed near the point of the click the x and
15 y pixel location of the click. Next, we want to be able to type characters into the Window with
16 the keyboard and see them as they are displayed kind of like a simple text editor. Finally,
17 we've tasked ourselves to do all this without one single global or static variable in the
18 program. Sounds like a lot, huh?
19
20 Actually, I suppose it is, because there are a number of conceptual hurdles to overcome, but if
21 you were able to follow what I did in ProgEx37 in the CBox3 and CBox4 examples in terms of the
22 use of SetWindowLong() and GetWindowLong() to store 'instance data' in the form of pointers, you
23 will be a long way to understanding what I am going to do here. Let's briefly review that
24 example, i.e., CBox3 of ProgEx37. In that example we created a CBox object in the program's
25 WM_CREATE message handler, and we output the specifications of the CBox object, i.e., its length,
26 width, height, and volume in another message handling routine, that is, a WM_PAINT handler. Now
27 granted, the message handling code there was all within one procedure, that is, fnWndProc(), but
28 its important to keep in mind that when Windows sent WM_PAINT or WM_CREATE messages to the
29 Window Procedure, these were actually separate function calls, and any local automatic stack
30 based variables within the Window Procedure came into and went out of scope in rapid succession
31 and in no case preserved any data across function calls or invocations of the Window Procedure.
32 The reason we were able to set values to the members of the CBox instance in WM_CREATE and
33 retrieve those same values for display in WM_PAINT was that we allocated a CBox object on the
34 heap dynamically and stored a pointer to that object in the window's instance data through the
35 Window Extra Bytes of the Window structure.
36
37 Now, I could have made things much easier on you by not doing this. I could have declared my
38 variables at global program scope. Or, I could have specified the variables as statics in the
39 Window Procedure. Truth be told, this latter would be an acceptable alternative, because if
40 a variable is declared as a static in a procedure at least it won't be accessible from every
41 procedure in a program module, but only within those to which it is passed through function
42 parameters. But in this program example I'm not going to do that; we'll subject ourselves to
43 the rigorous ascetic of doing things the hard, pure way. We'll not allow ourselves the easy
44 road of global variables. Once you learn to do it this way you'll likely never turn back. So
45 take it like medicine and just grin and bear it is my advice. Real Windows programmers don't
46 need global variables!
47
48 Another point I'd like to clear up in terms of my use of language before we proceed is my
49 reference to 'message handlers'. You've repeatedly seen me make reference to the term 'message
50 handler' or 'message handling routine', for example, the 'WM_CREATE message handler'. In CBox3
51 and in our ProgEx37 template program we parsed the incoming Msg parameter of the Window
52 Procedure with switch logic to route function execution to the correct case of the switch
53 logic construct. In major Windows programs involving tens of thousands of lines of code the
54 Window Procedure can grow to gargantuan proportions. From a code organization standpoint it
55 can become difficult to maintain. Many coders - me included, break the Window Procedure up
56 into separate message handling functions. Here is a simple example of what it could look like...
57
58 long fnWndProc_OnCreate(HWND hWnd, WPARAM wParam, LPARAM lParam)
59 {
60 HINSTANCE hIns;
61 hIns=((LPCREATESTRUCT)Wea->lParam)->hInstance;
62 return 0;
63 }
64
65
66 long fnWndProc_OnClose(HWND hWnd, WPARAM wParam, LPARAM lParam)
67 {
68 if(MessageBox(hWnd,"Do You Wish To Exit This App?","Exit Check?",MB_YESNO)==IDYES)
69 {
70 DestroyWindow(hWnd);
71 PostQuitMessage(WM_QUIT);
72 }
73
74 return 0;
75 }
76
77
78 LRESULT CALLBACK fnWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
79 {
80 switch(msg)
81 {
82 case WM_CREATE:
83 return fnWndProc_OnCreate(hwnd, wParam, lParam);
84 case WM_CLOSE:
85 return fnWndProc_OnClose(hwnd, wParam, lParam);
86 }
87
88 return DefWindowProc(hwnd, msg,wParam, lParam);
89 }
90
91 In the above code the switch logic in fnWndProc() routes execution to fnWndProc_OnCreate() when
92 a WM_CREATE message is received, and to fnWndProc_OnClose() when a WM_CLOSE message is received.
93 All other messages are sent to DefWindowProc(). I'll not be doing that in this 1st instance of
94 ProgEx38 because the code bulk is hardly unmanageable, but we'll take up this topic soon afterwards
95 because I do think it's important to understand.
96
97 OK, let's begin. We'll start with the mouse move message. First, I'd recommend you build
98 yourself a project and compile and run this example to see what it does. You should see a basic
99 white window that displays mouse coordinates in the upper left hand corner of the window, as well
100 as the width and height of the client area on the 2nd line. On the 3rd line you should see whatever
101 you type with your keyboard. Also, if you left button click with your mouse anywhere within the
102 client area of the window you should see displayed there the x/y coordinate pixel location of
103 where you clicked. The mouse move coordinates should be continuously updated as you move the mouse,
104 as should the window width and height on the 2nd line as you resize the window. Now we'll get to
105 the 'magic' that makes this all work.
106
107 The continuous display of the mouse cursor data is caused by the interplay of two specific messages,
108 and they are WM_MOUSEMOVE and WM_PAINT. This is what MSDN has to say about WM_MOUSEMOVE...
109
110 WM_MOUSEMOVE
111
112 The WM_MOUSEMOVE message is posted to a window when the cursor moves. If the mouse is not
113 captured, the message is posted to the window that contains the cursor. Otherwise, the message
114 is posted to the window that has captured the mouse.
115
116 WM_MOUSEMOVE
117
118 fwKeys = wParam; // key flags
119 xPos = LOWORD(lParam); // horizontal position of cursor
120 yPos = HIWORD(lParam); // vertical position of cursor
121
122 Parameters
123
124 fwKeys Value of wParam. Indicates whether various virtual keys are down. This parameter can be any
125 combination of the following values: Value Description
126
127 MK_CONTROL Set if the ctrl key is down.
128 MK_LBUTTON Set if the left mouse button is down.
129 MK_MBUTTON Set if the middle mouse button is down.
130 MK_RBUTTON Set if the right mouse button is down.
131 MK_SHIFT Set if the shift key is down.
132
133 xPos Value of the low-order word of lParam. Specifies the x-coordinate of the cursor. The coordinate
134 is relative to the upper-left corner of the client area.
135
136 yPos Value of the high-order word of lParam. Specifies the y-coordinate of the cursor. The coordinate
137 is relative to the upper-left corner of the client area.
138
139 --End Microsoft--
140
141 So what happens when you move the mouse over this program's window is that Windows posts a WM_MOUSEMOVE
142 message to the program's message queue. When this mouse move message arrives in the Window Procedure
143 fnWndProc the msg parameter will equal WM_MOUSEMOVE (whatever that number is), the HWND parameter will
144 contain the handle of the window to which the message applies, and the LPARAM parameter will have stuck
145 in its low and high 16 bit words the xPos and yPos in pixels of the mouse. These locations can be
146 extracted with the LOWORD() and HIWORD() macros.
147
148 What we're going to have to do, however, once we receive this data, is find some way to persist it across
149 invocations of the Window Procedure so that the values can be output to the client window during another
150 message - the WM_PAINT message. Its in the nature of Windows Programming that all drawing to a window
151 should be done during the WM_PAINT message - and that's a separate invocation of the Window Procedure.
152 If we store the mouse coordinates in local variables during a WM_MOUSEMOVE message, and attempt to output
153 these values during a WM_PAINT message - they'll be gone. All you'll get on the screen are zeros or
154 junk data. It is possible to take the 'low road' and draw directly to the screen in a WM_MOUSEMOVE
155 message by calling GetDC() to get a handle to a device context, but I'd strongly recommend against that.
156 Its simply the improper way to do it - your results will be extremely poor. So what we are going to have
157 to do is persist the data someway across invocations of the Window Procedure like we did in CBox3 and CBox4
158 of ProgEx37. In that example we created a CBox class dynamically and stored a pointer to it in the
159 Window Class structure of the window. We'll do the same here, although we don't even need anything as
160 sophisticated as a C++ class to persist the data. We'll just allocate a simple struct of type ProgramData
161 as follows...
162
163 struct ProgramData
164 {
165 short int xMouse;
166 short int yMouse;
167 short int xSize;
168 short int ySize;
169 short int xButton;
170 short int yButton;
171 };
172
173 We'll allocate one of these in our WM_CREATE logic, and store a pointer to that at offset 4 in the
174 Window Class struct. At offset zero we'll store a pointer to a C++ Standard Library String object we'll
175 also dynamically allocate in WM_CREATE. Down in WinMain() note I set the .cbWndExtra bytes to 8 so
176 as to store these two pointers, i.e., one to a string object to contain typed characters, and one to
177 a ProgramData object to persist mouse coordinates, window size, and left button mouse clicks. Here is
178 what our WM_CREATE logic looks like...
179
180 ...
181 ...
182 {
183 ProgramData* pPD=NULL;
184
185 case WM_CREATE: // This message is only received once at program startup.
186 { // Here we'll dynamically allocate memory on the heap to
187 std::wstring* pStr; // store inputs the program will receive in terms of key-
188 pStr=new std::wstring; // presses, mouse movements, button clicks, etc. We'll
189 SetWindowLong(hwnd,0,(long)pStr); // store pointers to this memory in 8 bytes we've
190 pPD=(ProgramData*)GlobalAlloc(GPTR, sizeof(ProgramData));
191 SetWindowLong(hwnd,4,(long)pPD); // allocated in theWNDCLASSEX struct down in WinMain()
192 return 0;
193 }
194 ...
195 ...
196 }
197
198 So what will need to happen when the Window Procedure receives a WM_MOUSEMOVE message - and it will
199 receive hundreds of them per second as you move the mouse, is that the present coordinates of the
200 mouse will need to be extracted from the lParam Window Procedure parameter and stored in our
201 dynamically allocated ProgramData object. Then an InvalidateRect() Api call needs to be made to
202 force a WM_PAINT message from Windows, during which message the mouse coordinates will be extracted
203 from the ProgramData object, and a TextOut() Api calls made to draw the mouse coordinates on the
204 window. Its a little wacky, I know. But you'll get used to it and accept it after awhile. Trust
205 me. So our WM_MOUSEMOVE handler will look like this....
206
207 case WM_MOUSEMOVE: //Sent when user moves mouse and mouse cursor is over this
208 { //window. 1st retrieve ProgramData* from cbWndExtra bytes.
209 pPD=(ProgramData*)GetWindowLong(hwnd,4); //Then extract present mouse coord-
210 pPD->xMouse=LOWORD(lParam); //inates from lParam's Lo and Hi words.
211 pPD->yMouse=HIWORD(lParam); //Finally force repaint.
212 InvalidateRect(hwnd,NULL,TRUE);
213 return 0;
214 }
215
216 Note that InvalidateRect() call. That is what forces the WM_PAINT that actually displays the
217 rapidly changing mouse coordinate display you see in the window as you move the mouse. Here
218 is the pertinent WM_PAINT code that actually does the window painting. I've removed everything
219 except that code relating to the display of mouse movement coordinates...
220
221 case WM_PAINT: //The 1st thing to do in WM_PAINT is usually get your handle to a device
222 { //context with BeginPaint() - which requires a PAINTSTRUCT variable. Then
223 PAINTSTRUCT ps; //we'll extract our ProgramData* from .cbWndExtra bytes so we can get at
224 std::wstring s1; //the mouse coordinate data from the last WM_MOUSEMOVE message. Next we'll
225 std::wstring s2; //build ourselves a C++ Standard String Class object (a local one) to
226 TCHAR szBuffer[16]; //hold a nicely formatted string with our mouse coordinates. We'll
227 HDC hDC; //concatenate the thing together and need a character string buffer
228 hDC=BeginPaint(hwnd,&ps); //to convert the numeric data to character format. We'll use
229 pPD=(ProgramData*)GetWindowLong(hwnd,4); //sprintf - or its tchar counterpart _stprintf
230 s1=_T("xMouse="); //to do the dirty work. Then we'll finally
231 _stprintf(szBuffer,_T("%d"),pPD->xMouse); //string the whole mess together and output it
232 s2=szBuffer; //with TextOut() and release our devie context
233 s1+=s2+_T(" yMouse="); //back to Windows.
234 _stprintf(szBuffer,_T("%d"),pPD->yMouse);
235 s2=szBuffer;
236 s1+=s2;
237 TextOut(hDC,0,0,s1.c_str(),s1.length());
238 ...
239 ...
240 EndPaint(hwnd,&ps);
241 return 0;
242 }
243
244 That's really pretty much it, and the same basic drill or logic covers all the other
245 functionalities we wanted to have such as displaying typed characters, window size, and
246 left button down clicks. These others are just different messages such as WM_CHAR, WM_SIZE, and
247 WM_LBUTTONDOWN. You should look up these messages on MSDN to see how the data is transferred
248 to the Window Procedure through the WPARAM and LPARAM WNDPROC parameters. Right now it should
249 be beginning to dawn on you the true elegance of the intellectual edifice that constitutes
250 Windows - something which users of Windows as opposed to programmers cannot really conceive or
251 appreciate.
252
253 Finally, in our WM_DESTROY handler we call C++'s delete operator on our string object, call
254 GlobalFree() on our ProgramData pointer (that's a Win Api memory de-allocation function I just
255 threw in to show you there are various ways of acquiring/releasing memory), and PostQuiteMessage()
256 to let us drop out of the message pump.
257
258 If you don't have a couple years of fairly rigorous programming experience this program might be
259 overwhelming. It touches on several somewhat complex topics such as Windows Program structure and
260 dynamic memory allocation. If you can partly grasp it I'd recommend looking up and studying on the
261 various Api functions used here such as CreateWindow(), RegisterClassEx(), SetWindowLong(),
262 InvalidateRect(), BeginPaint(), and TextOut(). And if you are really into this sort of thing and
263 have decided to make Win32 Sdk style programming your Windows Programming model of choice, you need
264 to get yourself Charles Petzold's "Programming Windows" book. His Windows 95 book would even be
265 useful, and can be had for little more than the cost of postage. That book also has a good
266 section on Microsoft's OLE/COM (Object Linking and Embedding/Component Object Model) which his more
267 often recommended fifth edition lacks.
268
269 Using CodeBlocks and GNU MinGW I'm looking at about a 54 K executable with this program. That seems
270 a bit much to me. The problem is the C++ Standard Library String Class. In the next example let's
271 try my String Class and see if we can't bring the size down some!
272*/
273
274//Main.cpp
275#define UNICODE //Will cause 'W' versions of Api functions to be used.
276#define _UNICODE //Will cause wide character versions of C i/o to be used.
277#include <windows.h> //Master Windows include file
278#include <tchar.h> //Macros for ansi/wide char support
279#include <string> //C++ Standard String Class Support
280
281
282struct ProgramData //This object will be used to persist mouse coordinate,
283{ //client window dimensions, and left mouse click coordinates
284 short int xMouse; //across invocations of the Window Procedure so that the
285 short int yMouse; //data can be displayed on the window during WM_PAINT
286 short int xSize; //messages. An instance of this object will be allocated
287 short int ySize; //dynamically in WM_CREATE, and the pointer to it stored
288 short int xButton; //as instance data in the window's WNDCLASSEX::cbWndExtra
289 short int yButton; //bytes.
290};
291
292
293LRESULT CALLBACK fnWndProc(HWND hwnd, unsigned int msg, WPARAM wParam, LPARAM lParam)
294{
295 ProgramData* pPD=NULL;
296
297 switch(msg)
298 {
299 case WM_CREATE: //This message is only received once at program startup.
300 { //Here we'll dynamically allocate memory on the heap to
301 std::wstring* pStr; //store inputs the program will receive in terms of key-
302 pStr=new std::wstring; //presses, mouse movements, button clicks, etc. We'll
303 SetWindowLong(hwnd,0,(long)pStr);//store pointers to this memory in 8 bytes we've
304 pPD=(ProgramData*)GlobalAlloc(GPTR,sizeof(ProgramData));
305 if(pPD) //allocated in theWNDCLASSEX struct down in WinMain()
306 {
307 SetWindowLong(hwnd,4,(long)pPD);
308 return 0;
309 }
310 else
311 {
312 return -1;
313 }
314 }
315 case WM_SIZE: //Sent when the Window is first shown or resized. We need
316 { //to first retrieve the ProgramData* from the cbWndExtra
317 pPD=(ProgramData*)GetWindowLong(hwnd,4); //bytes. Then we can retrieve from
318 pPD->xSize=LOWORD(lParam); //lParam's lo and hi words the width
319 pPD->ySize=HIWORD(lParam); //and height of the window. Then
320 InvalidateRect(hwnd,NULL,TRUE); //force a WM_PAINT to redraw the
321 return 0; //newly acquired info.
322 }
323 case WM_CHAR: //Sent when this program has the focus and the user hits
324 { //a char on the keypad. Same drill as above; retrieve
325 std::wstring* pStr; //String pointer though; Then just add the wParam (which
326 pStr=(std::wstring*)GetWindowLong(hwnd,0); //holds the key code pressed) to the
327 *pStr+=+wParam; //String and force the repaint as
328 InvalidateRect(hwnd,NULL,FALSE); //above.
329 return 0;
330 }
331 case WM_MOUSEMOVE: //Sent when user moves mouse and mouse cursor is over this
332 { //window. 1st retrieve ProgramData* from cbWndExtra bytes.
333 pPD=(ProgramData*)GetWindowLong(hwnd,4); //Then extract present mouse coord-
334 pPD->xMouse=LOWORD(lParam); //inates from lParam's Lo and Hi words.
335 pPD->yMouse=HIWORD(lParam); //Finally force repaint.
336 InvalidateRect(hwnd,NULL,TRUE);
337 return 0;
338 }
339 case WM_LBUTTONDOWN: //Sent when user left button mouse clicks over window. In
340 { //terms of the rest, you should be getting the idea by now!
341 pPD=(ProgramData*)GetWindowLong(hwnd,4);
342 pPD->xButton=LOWORD(lParam);
343 pPD->yButton=HIWORD(lParam);
344 InvalidateRect(hwnd,NULL,FALSE);
345 return 0;
346 }
347 case WM_PAINT: //All drawing to a window should be done during a WM_PAINT message.
348 { //That is why in all of the above message handling code except that
349 PAINTSTRUCT ps; //for WM_CREATE there is an InvalidateRect() Api call. That call
350 std::wstring s1; //will cause Windows to invalidate the client area of the window,
351 std::wstring s2; //and when this occurs windows will post to the window's message
352 std::wstring* pStr; //queue a WM_PAINT message. In this code just left we are
353 TCHAR szBuffer[16]; //allocating a few standard string objects, and a TCHAR
354 HDC hDC; //buffer. These will support the text we wish to display
355 hDC=BeginPaint(hwnd,&ps); //using TextOut(). What we first do though is call
356 pPD=(ProgramData*)GetWindowLong(hwnd,4); //BeginPaint() to get a handle to a
357 s1=_T("xMouse="); //device context (which is necessary
358 _stprintf(szBuffer,_T("%d"),pPD->xMouse); //for GDI (Graphics Device Interface)
359 s2=szBuffer; //function calls, and then call
360 s1+=s2+_T(" yMouse="); //GetWindowLong() to retrieve our
361 _stprintf(szBuffer,_T("%d"),pPD->yMouse); //ProgramData pointer from the Window
362 s2=szBuffer; //Class struct. Having done that we can
363 s1+=s2; //chug through some string minipulation
364 TextOut(hDC,0,0,s1.c_str(),s1.length()); //code to compose the messages we want
365 if(pPD->xButton||pPD->yButton) //to display in our window, which
366 { //messages contain the various numeric
367 s1=_T("xButton="); //information we want to convey. Note
368 _stprintf(szBuffer,_T("%d"),pPD->xButton); //that _stprintf() is used to convert
369 s2=szBuffer; //the integral data to a string format
370 s1+=s2+_T(" yButton="); //so it can be output in a std::string.
371 _stprintf(szBuffer,_T("%d"),pPD->yButton); //If you know a better way to do it go
372 s2=szBuffer; //for it. I'm not really expert on the
373 s1+=s2; //std::string class, as I mostly use my
374 TextOut //own. Finally, after outputting all
375 ( //the data we want we need to call
376 hDC, //EndPaint() to release the device
377 pPD->xButton+12, //context. This is important because
378 pPD->yButton, //if you don't release memory objects
379 s1.c_str(), //and resources back to the operating
380 s1.length() //system you'll cause a memory leak, and
381 ); //your program will start to behave
382 pPD->xButton=0, pPD->yButton=0; //rather badly. Essentially, .NET
383 } //is Microsoft's answer to the problem
384 s1=_T("Width="); //that the average coder can't be trusted
385 _stprintf(szBuffer,_T("%d"),pPD->xSize); //to do things like this correctly, so
386 s2=szBuffer; //the framework takes care of it for
387 s1+=s2+_T(" Height="); //him/her. So you've got to ask yourself,
388 _stprintf(szBuffer,_T("%d"),pPD->ySize); //"Are you better than the average coder?"
389 s2=szBuffer; //
390 s1+=s2; //In WM_DESTROY below we'll release the
391 TextOut(hDC,0,20,s1.c_str(),s1.length()); //std::string pointer we acquired with
392 pStr=(std::wstring*)GetWindowLong(hwnd,0); //new by calling delete on it, and we'll
393 TextOut(hDC,0,40,pStr->c_str(),pStr->length()); //use GlobalFree() on the ProgramData
394 EndPaint(hwnd,&ps); //pointer we acquired with GlobalAlloc().
395 return 0;
396 }
397 case WM_DESTROY:
398 {
399 std::wstring* pStr;
400 pStr=(std::wstring*)GetWindowLong(hwnd,0);
401 delete pStr;
402 pPD=(ProgramData*)GetWindowLong(hwnd,4);
403 if(pPD)
404 GlobalFree(pPD); //send the WM_DESTROY. Anyway, what we need to do here is
405 PostQuitMessage(0);//release our heap allocations and post a Quit message.
406 return 0;
407 }
408 }
409
410 return (DefWindowProc(hwnd, msg, wParam, lParam));
411}
412
413
414int WINAPI WinMain(HINSTANCE hIns, HINSTANCE hPrevIns, LPSTR lpszArgument, int iShow)
415{
416 TCHAR szClassName[]=_T("Form2");
417 TCHAR szCaption[80];
418 WNDCLASSEX wc;
419 MSG messages;
420 HWND hWnd;
421
422 wc.lpszClassName=szClassName, wc.lpfnWndProc=fnWndProc;
423 wc.cbSize=sizeof (WNDCLASSEX), wc.style=CS_DBLCLKS;
424 wc.hIcon=LoadIcon(NULL,IDI_APPLICATION), wc.hInstance=hIns;
425 wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION), wc.hCursor=LoadCursor(NULL,IDC_ARROW);
426 wc.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH), wc.cbWndExtra=8;
427 wc.lpszMenuName=NULL, wc.cbClsExtra=0;
428 RegisterClassEx(&wc);
429 _tcscpy(szCaption,_T("Try Typing Text, Moving Mouse, Clicking Left Mouse Button, Or Sizing Window!"));
430 hWnd=CreateWindowEx(0,szClassName,szCaption,WS_OVERLAPPEDWINDOW,100,100,600,400,HWND_DESKTOP,0,hIns,0);
431 ShowWindow(hWnd,iShow);
432 while(GetMessage(&messages,NULL,0,0))
433 {
434 TranslateMessage(&messages);
435 DispatchMessage(&messages);
436 }
437
438 return messages.wParam;
439}