Messages and Message Loops |
Top Previous Next |
Introduction In the previous section we discussed how to create a window and introduced the concept of messages. Any action taken by a user that effects a window or dialog is reported to your program. This report is sent as a message that contains three main pieces of information. The message class or ID, the message code also known as the WPARAM, and the message qualifier also known as the LPARAM. Windows sends the report to your program by calling the subroutine you specified when the window or dialog was created. This subroutine is known as the "handler" subroutine. In other languages it may be referred to as the "windows procedure". Before your handler subroutine is called, Windows sets the system variables @MESSAGE, @WPARAM and @LPARAM so your program can tell what has happened. The message queue and loop Since Windows is a multitasking system, and there may be literally hundreds of programs running on your computer at once, it would be very inefficient for Windows to wait for your program to finish handling a message. So all messages sent to your program are placed in a special buffer known as the message queue. The messages are placed in the queue in the order received to prevent your program from losing track of what has been happening to the window. The queue also prevents losing messages that may have been sent while your program was busy doing something else. Because there may be many other programs running at the same time it is also not system friendly to "busy wait" for messages. When there are no messages left to process on the queue your program should sleep, waiting for the next message to be reported to the window, and placed in the message queue. In EBASIC the message queue, looping and sleeping are handled by using either the WAIT or WAITUNTIL statements. The WAITUNTIL statement. As discussed in the section on creating windows the WAITUNTIL statement will be used almost exclusively in your programs. To fully understand what is happening when you use the WAITUNTIL statement we will use some EBASIC statements to break it down. This code is just an example of what WAITUNTIL does and is not an actual program: SUB WAITUNTIL(condition) DO SLEEP(): REM Wait until some messages are available FOR x = 0 TO GetNumMessages() : REM get the number of messages in the queue msg = GETMESSAGE(x) @MESSAGE = msg.messageID @WPARAM = msg.wparam @LPARAM = msg.lparam IF @MESSAGE = @IDCONTROL @CONTROLID = msg.control_id ENDIF IF @MESSAGE = @MENUPICK @MENUNUM = msg.menu_id ENDIF GOSUB msg.window.handler : REM call the handler subroutine for this window or dialog NEXT x UNTIL condition RETURN ENDSUB As you can see by the pseudo code above there actually is a loop comprised of the DO and FOR statements. The WAIT statement The WAIT statement is very similar to WAITUNTIL with the exception that it only executes once. It is up to your program to determine how long messages should be processed and what condition(s) to check to exit the loop. WAIT should only be used in special circumstances where you need more control of when messages are processed. The WAIT statement has an optional parameter to specify not to sleep and to immediately check for messages and return. Using pseudo EBASIC statements again the code for WAIT would look something like this: SUB WAIT(nosleep) IF nosleep <> 1 SLEEP(): REM Wait until some messages are available ENDIF FOR x = 0 TO GetNumMessages() : REM get the number of messages in the queue msg = GETMESSAGE(x) @MESSAGE = msg.messageID @WPARAM = msg.wparam @LPARAM = msg.lparam IF @MESSAGE = @IDCONTROL @CONTROLID = msg.control_id ENDIF IF @MESSAGE = @MENUPICK @MENUNUM = msg.menu_id ENDIF GOSUB msg.window.handler : REM call the handler subroutine for this window or dialog NEXT x RETURN The purpose of having a nosleep parameter is to allow your program to remain responsive to user input while it is very busy doing something else. For example: FOR x = 1 TO 10000000 This assumes that there is some menu option, or button that sets the variable cancel equal to 1. If you did not use WAIT 1 in this case the program would appear to be 'locked' and would not respond to menus or buttons until the long loop was finished. You should limit your use of WAIT 1 since this creates a "busy wait" loop. Since your program is never allowed to sleep it consumes the majority of the processor time on the system and this will reduce overall system performance. WAIT can also be used to create a custom WAITUNTIL loop: SUB MYWAITUNTIL Using WAIT in this manner allows using the time between messages for custom processing. Message ID's and the handler Now that you have a general idea of what is happening with the message queue and loop we can explore some of the message ID's your handler will receive. To review the handler is just a subroutine that is called by the system, through either the WAITUNTIL or WAIT statements. EBASIC predefines many of the common message ID's that your program will use. Windows defines many hundreds more that can be used with your EBASIC programs. A good source of information on Windows messages is the Windows SDK available from Microsoft, the windows header files, or from the Microsoft developers website Mouse messages The mouse generates an input events whenever the user moves the mouse, or presses or releases a mouse button. Windows converts mouse input events into messages and posts them to the appropriate programs message queue. When mouse messages are posted faster than a program can process them, Windows discards all but the most recent mouse message. A window receives a mouse message when a mouse event occurs while the cursor is within the borders of the window, or when the window has captured the mouse. Mouse messages are divided into two groups: client area messages and nonclient area messages. Typically, an application processes client area messages and ignores nonclient area messages. When a mouse message is received and your handler is called the system variables @MOUSEX and @MOUSEY will contain the position of the pointers hot spot at the time the message was generated. The following mouse message ID's are predefined in EBASIC
Additional information: @WPARAM contains the status of the other mouse buttons and the CTRL/SHIFT keys when the message was received. 0x0001 = left mouse button is down 0x0002 = right mouse button is down 0x0004 = SHIFT is being held down 0x0008 = CTRL is being held down 0x0010 = middle mouse button is down The values should be test by using a bit wise AND (&). For example: IF (@WPARAM & 0x0004) = 0x0004 : REM shift key is held down
Keyboard messages Messages from the keyboard are generated whenever a key is pressed while your window has focus. Keyboard messages are sent in two different forms, keystroke messages and character messages. Keystroke messages are sent as raw keyboard scan codes, before the system translates them. Character messages are sent when the system translates the raw key code, taking into account the SHIFT, ALT and CTRL keys. The following keyboard messages ID's are predefined by EBASIC:
Additional information: @WPARAM will contain the raw virtual keycode for @IDKEYDOWN and @IDKEYUP messages or the ASCII character for @IDCHAR messages. See Appendix C for a list of virtual key codes.
Window and system messages Messages for your window are generated whenever an action that would effect the window happens. These include sizing messages, creation messages, drawing messages, close events, system wide messages, etc. A few of the predefined system messages:
See Also: System Variables and Constants for a list of predefined message ID's. You can of course handle any message that Windows sends to your handler. The compiler predefines the above constants as a convenience. |