Learning Windows Programming
Part 2

Processing Messages

In WinMain, we defined a WindowClass and told Windows that all messages associated with windows of this class would be processed by a Procedure called "MainWndProc".  So now we will create this procedure.  This procedure will be called many times during the life of the program.  For example, whenever the mouse moves - this procedure is called and lParam will contain the X.Y coordinates of the mouse position.  If the Window is Minimised, Maximised, or made visible after being obscured by another window this procedure will be called.  If you have Buttons, Listboxes, etc in the Window and they have been clicked - this procedure will be called.  And because you can have several windows of the same class, the hWnd of the particular window that is being affected is passed.  The parameters of message and wParam tell you what caused the procedure to be called.



PROCEDURE [WINDOWS] MainWndProc*(hWnd: W.HWND;   (* window handle             *)
                                 message: W.WORD;                                         (* type of message           *)
                                 wParam: W.WORD;                         (* additional information    *)
                                 lParam: LONGINT                           (* additional information    *)
                                ): LONGINT;

  VAR
    rc: INTEGER;
    hdc:  W.HDC;
    ps:   W.PAINTSTRUCT;
    rect: W.RECT;
BEGIN
  CASE message OF
    W.WM_PAINT:
      hdc := W.BeginPaint(hWnd, SYSTEM.ADR(ps));
      W.GetClientRect(hWnd, SYSTEM.ADR(rect));
      rc := W.DrawText(hdc, SYSTEM.ADR(msg), -1, SYSTEM.ADR(rect),
              W.DT_SINGLELINE + W.DT_CENTER + W.DT_VCENTER);
      W.EndPaint(hWnd, SYSTEM.ADR(ps));
      RETURN 0;
  | W.WM_DESTROY:
    W.PostQuitMessage(0);
    RETURN 0;
  ELSE                                    (* Passes it on if unproccessed     *)
    RETURN W.DefWindowProc(hWnd, message, wParam, lParam)
  END;
END MainWndProc;

In this example program, we only test for 2 conditions - and we ignore all others.
Remember in WinMain we issued an UpdateWindow API call.  I said that this would place a WM_PAINT message on the queue.  This is where we process it.  So if the parameter "message" has been set to WM_PAINT we perform the following code. 


  CASE message OF
    W.WM_PAINT:
      hdc := W.BeginPaint(hWnd, SYSTEM.ADR(ps));
      W.GetClientRect(hWnd, SYSTEM.ADR(rect));
      rc := W.DrawText(hdc, SYSTEM.ADR(msg), -1, SYSTEM.ADR(rect),
              W.DT_SINGLELINE + W.DT_CENTER + W.DT_VCENTER);
      W.EndPaint(hWnd, SYSTEM.ADR(ps));
      RETURN 0; 


In this code we want to be able to paint our message on the window.  To do so, we must get a Device Context for the Window.  This will be explained on a later page - but for now - just accept that a DeviceContext (DC) is how Windows is able to provide device indepence from all the different types of display adapters that are on the market.  In fact it goes even further than that - to paint on a printer or plotter - you also get a DC and issue the same set of commands that you use to draw on your window.  So you can use the same procedures to draw lines, text, shapes, graphics, etc to your Display, printer or plotter - and Windows does all the hard work of correctly formatting it for you. 


There are two ways of getting a DC.  You can use the GetDC API call - and that will allow you to draw anywhere on your window or page.  The other method is to use BeginPaint - which is only used for Displays - and has some special capabilities.  I will mention some of those shortly.


Before we can draw any text, we first must get the dimensions of the client area - that is, the area inside the Window frame and below the TitleBar (and below the MenuBar if you have one).  


NOTE:  In all the API calls you will notice that some parameters are passed as SYSTEM.ADR(xxxx).  OK - whenever we need to pass a parameter to Windows that is a RECORD or an ARRAY (like a String) - we pust pass the ADDRESS in memory of  the variable - NOT the value of the variable itself.  This allows Windows to access the variable, and if necessary, update some of the values within it.


The DrawText API call is the one line of code that everything else has been building up to.  This is the call that will actually paint our message on the display.  The last parameter in the call is made up from 3 variables - stating that we want the text to be printed as a Single Line and that it should be Centered on the display - both Horizontally and Vertically.  I'll get back to that shortly.


After we have finished painting we must free the DC that we obtained.  If we had used a GetDC command - we would use a ReleaseDC to free it - but since we used BeginPaint - we must use EndPaint to free it. 


Then we RETURN control to windows - giving it a ReturnCode of 0 - this informs Windows that we did indeed process this message.

Now that seems like a lot of code to go through just to put a simple message on screen - lets face it - using QBasic under DOS we could have done that with:  Print "Hello World" and it would be done!!!!!
But this is where the magic of Windows comes in.  If you run this program you will see the Window that we created and our "Hello Windows" message printed in the center.   Now if you do any of the following things:

Any of these actions will cause a WM_PAINT message to be generated - which will call this Procedure and thus cause the contents to be redrawn.  Also, if we had resized the window - the text will be automatically redrawn to be in the center of the window with its new size.

I mention before that BeginPaint had some extra power over the GetDC command.  With BeginPaint - only the part of a window that has been destroyed (say, by an overlapping window) is redrawn. If our "Hello Windows" message was still intact - windows would ignore the DrawText Command.  Also the EndPaint informs Windows that the window is now fully restored.  (In Windows jargon - it is now VALIDATED).

Notes about your code for WM_PAINT


We now come to the other message type that our procedure will handle:


  | W.WM_DESTROY:
    W.PostQuitMessage(0);
    RETURN 0; 


WM_DESTROY message is passed when the user clicks on the System Close button (The 'X" in the top right corner of a Win95 window - or the Close menu item in a Win3.x window).  This is the part of the program where you see many applications issue the "Are you sure you want to quit?" or the "Changes have been made to ...... Save Y/N?" dialogs.  In our case - we inform Windows that we do indeed want to quit and request Windows to Post a Quit Message.  You may remember that the Message Loop requires a Quit message to drop out of the loop and terminate the program - this is the call that creates it.  We also must RETURN a return code of zero to Windows to say that we have processed this message.

At the start of this page I mentioned that you will get lots of other messages coming through this code.  Just moving the mouse will generate a message, if you resize the window - not only do you get a WM_PAINT but you also get a WM_RESIZE, etc.  For these messages we must inform Windows that we didn't handle them and that Windows should take the default actions for these messages.

 ELSE                                    (* Passes it on if unproccessed     *)
    RETURN W.DefWindowProc(hWnd, message, wParam, lParam)

If the RETURN statement here confuses you - it could also be written like this:



  ELSE
   returnCode := W.DefWindowProc(hWnd, message, wParam, lParam) ;
   RETURN returnCode;
 

 Now - I know that these last two pages have been fairly hard going if you are new to Windows programming.  DON'T be alarmed - everyone goes through this culture shock when they first start.  Print these pages out and read them several times.  Run the program and Resize, Minimise, Maximise, Move it, etc. to see that it does all the things I mentioned.  If you come to grips with these two Procedures - I promise you - its gets a little easier after this.
 

 

 
Home Prev Next
Email Brosco