EWin Tutorial - Handling Messages

How do I make a more interesting program? [event-driven programming intro]

You will need to handle various events in order to make your program behave in more complex ways. Events. Let's talk about events for a second.

In your typical C program (for a non-Windows GUI application), you don't have events. When you want events, you ask for them. If you want the user's name, you write

 
  printf("What's your name?");
  gets(name);

And that's that. But in Windows, you can't do that. The user may decide to type in his name, or he may go do something else, maybe even in another program. There are lots and lots of things the user may do at any time, including: type something, move the mouse, minimize a window, move a window, choose something from a menu, etc.

So to handle all these possible things that might happen, Windows classifies each one as one or more Messages. It sends these Messages to your window, and your window queues them up internally. Then, when your program isn't doing anything else, it pulls one of these Messages off of the queue and processes it. EWin figures out what kind of Message it is, and then it calls an appropriate OnSomething() message. For instance, if the user moved the window, your TWin's OnMoving() gets called repeatedly while the user is moving the window around, and then OnMoved() gets called when the user is done. If the user closes the window, OnClose()gets called. If the user chooses something from a menu, this calls OnMenuCmd(). If there is mouse input, it calls OnMouseMsg(). If there is keyboard input, it calls OnKeyboardMsg(). And so on. Since these OnSuchAndSuch() functions are virtual, you can override them to do anything you want.


So if I wanted to do something special when my window was maximized?

Maximizing the window is a special way of sizing it. So you would overload OnSized(), and see if the second parameter is SIZE_MAXIMIZED. There are other things that OnSized() could get called for; you can get more details by reading the help for TWin::OnSized(). But for the case of maximizing the window:

 
  #include <common.h>
  #include <win.h>

  class TMyWin : public TWin
  {
    public:
    TMyWin(TWin *PParent, const TWinAttr &Changes)
      : TWin(PParent, Changes) { }

    virtual bool OnSized (TPoint WH, UINT SizeType)
      {
        if (SizeType == SIZE_MAXIMIZED)
          InfoBox("Hey, you maximized me!");
        return true;
      }
  };

This lovely new window will bring up a little box that says "Hey, you maximized me!" each time you maximize the window.


So if I wanted to stop the user from making the window larger than a certain amount, I would just...

You're thinking that you could just do this:

 
virtual bool TMyWin::OnSized (TPoint WH, UINT SizeType)
  {
    if (WH.w > 600) //hey, I dont want it to get bigger than 600 wide!
      Size(600, WH.y);
    return true;
  }

Like that. Well, yeah, that will work. But remember, OnSized() gets called AFTER the window has been resized. So the user will move it to where he wants it and let go of the mouse, and then your code will kick in and the window will jump to a different size. That will confuse the user. There is a better way. Instead of using OnSized(), overload QueryMinMaxInfo(). This is a function that Windows [indirectly] calls when it wants to know the minimum and maximum size of your window.

 
virtual bool TMyWin::QueryMinMaxInfo (MINMAXINFO &MinMaxInfo)
  {
    MinMaxInfo.ptMaxTrackSize.x = 600;
    return true;
  }
This will make it so that the user can't make the window bigger than 600 pixels wide. The operating system won't let them drag it any larger than that. You can change other parameters of MINMAXINFO to set the minimum size as well as specifying the size of the window when it is Maximized (by default it is the size of the whole screen when maximized, but you can change that).

Oh, hey, note that I am not going to keep giving you whole class definitions for each example from now on. I will just give you the relevant functions outside of the class definition. In other words, it is assumed that before the QueryMinMaxInfo code appears, this code is present:

 
#include <common.h>
#include <win.h>

class TMyWin : public TWin
{
  public:
  TMyWin(TWin *PParent, const TWinAttr &Changes)
    : TWin(PParent, Changes) { }
  virtual bool QueryMinMaxInfo (MINMAXINFO &MinMaxInfo);
};

See, the QueryMinMaxInfo() function is declared in the class definition, but the actual code for this function can be specified later. From here on I will assume that you can manage to get the class declaration working without my showing you how each time. I'll just provide the code that's interesting.


Why is that function called QueryMinMaxInfo() and not OnGetMinMaxInfo()?

Good question. For historical reasons, mostly. If I did everything over again, there wouldn't be any QueryXXXX messages. But they are there now. They have only one difference between OnXXXX messages: what their return-value means. See, all OnXXXX messages return void, or else they return a bool. If they return a bool, this indicates that they have processed the message. In other words, if you return true, this means "Hey, Windows, I processed this message, so you don't have to!" and if you return false, it means, "Hey, Windows, I didn't handle this message. Why don't you process it?" and then Windows will do some sort of default processing. For most messages there is no default processing, but for some, there is.

But QueryXXXX messages have a different meaning for their return type. The return value means a different thing for each QueryXXXX function. That was the reason I originally used the Query prefix: so that you wouldn't get confused by what the return-type means, and assume it meant what a normal OnXXXX function meant. But what I SHOULD have done is have them still return void or a bool, and have them take an extra parameter. So that

 
virtual MouseActivateMode QueryMouseActivate(
                            HitTest Location, 
                            MouseMsg Msg);

would be rewritten as

 
virtual bool OnQueryMouseActivate(HitTest Location, MouseMsg Msg, 
                                  MouseActivateMode &Mode);

and then all event-handlers would start with OnXXXX. But oh well. Eventually I will make this change, when I am next feeling mean and want to break everybody's code.


What's OnInit()?

OnInit()is an especially useful message handler. It gets called right after your window gets Create()'ed. You usually do most of your initialization work in OnInit() as opposed to doing it in your object's constructor. This is because in the constructor, the window doesn't really exist yet. To put it another way, Windows doesn't know the window exists yet. It is only a C++ class; the Windows side of it (the actual window) doesn't exist yet. And most configuration functions require that your window be valid before you can do them. If you're a list box, for instance, you can't put items into the list until the window has been created. In general, all window functions have to be done when the window is created, except for getting or setting the window's inherent attributes like its text or its style flags, because EWin keeps track of those and lets you do them ahead of time.

So do your window-setup code in OnInit(). That way you don't have to wonder if such-and-such a function requires that the window be created or not. The window will always be created when OnInit() is called, so you don't have to worry about that. However, the window isn't visible to the user yet, so you don't have to worry about the user seeing a non-initialized version of your window. (Normally, the window will become visible right after OnInit() finishes.)

Similarly, do your Windows clean up code in OnDone() instead of in your TWin object's destructor. OnDone() gets called right before the window gets destroyed. That way you will still be able to do things that require your window to be valid.

You are probably wondering what Create() is. You don't usually call it yourself; EWin calls it for you. We'll get to that in a while when you have more background knowledge. For now, let's go on to the next topic.

 

(go on to the next page in the tutorial...)