Handling Mouse Messages
Many applications need to know where the cursor is (xy coordinates), what window (dialog/control) it is over, if a mouse event has occurred (click, etc.), and details about the mouse event.

There are two general methods of getting the information - repetitive use of API to check mouse/cursor status (such as within a message loop or timer) or responding to Windows messages when they are sent to notify an application that a mouse events has occurred.

As we will see, either method can be used in a PowerBASIC application, but the use of Windows messages often provides a more convenient method of getting to the needed information.

This page covers methods to detect and use the various mouse events (mouse down, mouse up, etc.).

However, it is worth mentioning that all of the common controls provide their own messages on mouse events in areas of interest in the control. Most often, the messages supplied by the common controls are all that a programmer needs to create an application.

But since common control messages do not cover all possible mouse actions, the additional of use of Windows API often proves useful in many applications.

Using Only API to Monitor Mouse Status
Information about the cursor/mouse can be retrieved at any time, regardless of whether a mouse event has taken place. In particular, the cursor location (x,y coordinates) within a dialog, the mouse buttons pressed (left/right/middle), the keys pressed (Ctrl/Alt/Shift), and the handle/control ID of the child window beneath the cursor can be retrieved using API calls.

This code gets the location, control handle, and control ID.

    Local hTemp As Dword, ID as Integer, pt As Point, iResult&

    'get xy coordinates
    GetCursorPos pt               'pt has xy screen coordinates
    ScreenToClient hDlg, pt       'pt now has client coordinates

    'get child handle and ID
    hTemp = ChildWindowFromPoint(hDlg, pt.x, pt.y) 'child control handle
    ID = GetDlgCtrlID (hTemp)     'returns control ID from control handle

The next code shows how to use API to get the up/down status of mouse buttons and of the ctrl/alt/shift keys (commonly used with mouse actions). In particular, the GetKeyState() and GetAsyncKeyState() API can access button/key states.

   'mouse buttons (left/right/middle)
   iResult& = GetAsyncKeyState(%vk_lbutton)
   iResult& = GetAsyncKeyState(%vk_rbutton)
   iResult& = GetAsyncKeyState(%vk_mbutton)

   'keyboard keys (Ctrl/Alt/Shift)
   iResult& = GetAsyncKeyState(%vk_shift)
   iResult& = GetAsyncKeyState(%vk_control)
   iResult& = GetAsyncKeyState(%vk_menu)

   'alternate way to get keyboard keys status
   iResult& = GetKeyState(%vk_shift)
   iResult& = GetKeyState(%vk_control)
   iResult& = GetKeyState(%vk_menu)       

There are complexities with using these two API, making them not as popular with programmers as the seemingly straight-forward code might suggest. See my tutorial section on key monitoring for more information.

An inconvenience of the all-API approach is that the program must include code to repeatedly test for the mouse information, usually as part of a message loop or by using a timer to trigger periodic use of the code to get the mouse information.

It is generally more convenient to let the Windows operating system send a mouse event message, which contains much of the needed information, to the application for a program response.

Sample Application - Monitoring Mouse Without Messages
Here is a complete sample application using the code above. Note that in this example, a PowerBASIC Dialog DoEvents loop is used to repeatedly call the monitoring code. When the sub MouseInfo is run, the results are placed in a pair of labels.

   #Compile Exe
   #Include "win32api.inc"
   Global hDlg As Dword
   Function PBMain() As Long
      Dialog New Pixels, 0, "Button Test",300,300,200,200, )
                             %WS_OverlappedWindow, 0 To hDlg
      Control Add Label, hDlg, 100,"", 50,50,100,20, %WS_Border
      Control Add Label, hDlg, 101,"", 50,80,100,20, %WS_Border
      Dialog Show Modeless hDlg
      Do
          Dialog DoEvents 0 To Count&
          MouseInfo
      Loop While Count&
   End Function
   
   Sub MouseInfo
       Local hTemp As Dword, Id As Integer, pt As PointAPI
       GetCursorPos pt             'pt has screen position of cursor
       ScreenToClient hDlg, pt     'pt now has hDlg client coordinates
       hTemp = ChildWindowFromPoint(hDlg, pt.x, pt.y)  'handle of child
       Id = GetDlgCtrlID (hTemp)     'returns control ID
       hTemp = GetDlgItem(hDlg, Id)  'returns control handle
       Control Set Text hDlg, 100, Str$(htemp)
       Control Set Text hDlg, 101, Str$(Id)
       Control ReDraw hdlg, 100
       Control ReDraw hdlg, 101
   End Sub

As was noted above, this example displays the dialog as modeless and uses a message loop to continually monitor mouse information.

An alternative to the DDT DoEvents loop would be to create a timer, allowing the MouseInfo subroutine to run from within the recurring %wm_timer event messages.

More often, however, the cursor information is only needed when a mouse event (click, etc.) takes place. That's where Mouse messages come into play.

API vs DDT Comparison: Retrieving Control ID and Control Handle
This might be as good a time as any to mention some code that works both inside and outside mouse events. Given either the ID or the handle of a child control, PowerBASIC and the Windows API offers functions which allow you to get the other value.

    ID = GetDlgCtrlID (hTemp)         'API: use handle to get ID
    hTemp = GetDlgItem(hDlg, ID)      'API: use ID to get handle 

    Window Get ID hTemp to ID         'DDT: use handle to get ID
    Control Handle hDlg, ID TO hTemp  'DDT: use ID to get handle

The API mentioned here were used in the first example on this page. The DDT code is provided by PowerBASIC as an alternative to the API solutions. The choice of which code to use is a personal selection, however more experienced programmers tend to use API solutions which are considered more portable between languages.

Now, on to discussing mouse messages.

Mouse Messages: Overview
Whenever a mouse event (move, up, down, doubleclick, scroll and hover) takes place, Windows sends one or more messages to the PowerBASIC application. The messages are sent to the callback function of the control in which the mouse event took place. If no control callback function is available, the mouse message is sent to the dialog callback function of the control's parent dialog.

PowerBASIC programmers commonly use a dialog callback function to handle all messages, avoiding the use of creating a callback function for each control.

The following table lists the available mouse messages.

    Client Area            Non-Client Area          Miscellaneous
    ----------------	   -----------------        -------------------
    WM_LBUTTONDBLCLK       WM_NCLBUTTONDBLCLK       WM_MOUSEMOVE
    WM_LBUTTONDOWN	   WM_NCLBUTTONDOWN	    WM_NCMOUSEMOVE
    WM_LBUTTONUP	   WM_NCLBUTTONUP	    WM_NCHITTEST
    WM_MBUTTONDBLCLK       WM_NCMBUTTONDBLCLK	    WM_MOUSEACTIVATE
    WM_MBUTTONDOWN         WM_NCMBUTTONDOWN         WM_CAPUTRECHANGED
    WM_MBUTTONUP           WM_NCMBUTTONUP           WM_MOUSEHOVER
    WM_RBUTTONDBLCLK       WM_NCRBUTTONDBLCLK	    WM_NCMOUSEHOVER
    WM_RBUTTONDOWN         WM_NCRBUTTONDOWN	    WM_MOUSELEAVE
    WM_RBUTTONUP           WM_NCRBUTTONUP           WM_NCMOUSELEAVE
    WM_XBUTTONDBLCLK       WM_NCXBUTTONDBLCLK       WM_MOUSEWHEEL
    WM_XBUTTONDOWN	   WM_NCXBUTTONDOWN         WM_MOUSEHWHEEL
    WM_XBUTTONUP	   WM_NCXBUTTONUP

Note that there are equivalent messages for client and non-client area mouse events (up/down/doubleclick) in the first two columns. The messages in the third column cover a wide range of events.

The purpose of many of these messages is self-explanatory, but additional discussion is provided below - particularly for the messages in the "Miscellaneous" column.

Mouse Terminology - Background
But before discussing how to use the mouse messages from the table above, some mouse/cursor background information and definitions are needed.

Mouse Message Content
Like any Windows message, the mouse messages listed above contain 4 numbers, all Long data types.

The Msg value is a numerical named_constant defined by Windows. Msg values are documented on MSDN and are included in the PowerBASIC win32api.inc include file.

Within a PowerBASIC application, the actual numerical value or text description of a Msg is not used. Only the named constants (WM_MOUSEMOVE, etc.) are used in the code.

To know what a named constants means, you'll have to visit MSDN. There is no Windows API or utility which can return a text string description of a mouse event named constant. However, a DLL (winmsg.dll) is distributed with the PowerBASIC sample applications which offers a lookup capability.

There are even a few web pages - here, here, and here - which provide lists.

Mouse Messages: wParam and lParam Data Extraction
As noted above, wParam and lParam contain values which vary from message to message. To maximize the information that these values can send to an application, it is common to place two numerical values into these Long (4 bytes) variables - using the lower two bytes (low word) to store one integer value and the upper two bytes (high word) to store a second integer value. Many, but not all, messages use this technique.

Getting the Lo/Hi word from a Long or DWord (also 4 bytes) variable can be done in several ways, using API, mathematical operations, or with DDT commands.

Here's example code that uses the PowerBASIC Lo/Hi functions to extract lo/hi values from a wParam value. There's also a line of code which will place integer values in the low and high words of a Long/DWord variable.

    LoValue% = Lo(Word, wParam)
    HiValue% = Hi(Word, wParam)
    Result = Mak(DWORD, LoValue%, HiValue%)

In a PowerBASIC program, the wParam value is normally retrieved within a callback function using the CB function, so wParam would be normally be replaced with CB.wParam, as would the lParam message value be retrieved with CB.lParam.

Mouse Messages: Up, Down, DblClick
Finally - we're getting to actual mouse messages! Here's the listing of the 24 mouse messages corresponding to the up/down/dblclick (remember, Windows has no "click" mouse event) messages. There are corresponding messages for the client area and non-client area mouse messages.

    Client Area            Non-Client Area   
    ----------------	   ----------------- 
    WM_LBUTTONDBLCLK       WM_NCLBUTTONDBLCLK
    WM_LBUTTONDOWN	   WM_NCLBUTTONDOWN
    WM_LBUTTONUP	   WM_NCLBUTTONUP
    WM_MBUTTONDBLCLK       WM_NCMBUTTONDBLCLK
    WM_MBUTTONDOWN         WM_NCMBUTTONDOWN 
    WM_MBUTTONUP           WM_NCMBUTTONUP   
    WM_RBUTTONDBLCLK       WM_NCRBUTTONDBLCLK
    WM_RBUTTONDOWN         WM_NCRBUTTONDOWN
    WM_RBUTTONUP           WM_NCRBUTTONUP  
    WM_XBUTTONDBLCLK       WM_NCXBUTTONDBLCLK
    WM_XBUTTONDOWN	   WM_NCXBUTTONDOWN 
    WM_XBUTTONUP	   WM_NCXBUTTONUP

The great thing about these messages is that the message values all have the same content.

Here's more information about the content of wParam and lParam, and how to extract the information. Remember, this only applies to the 24 mouse events listed above. The Miscellaneous column of mouse messages have different wParam/lParam message value content.

It's worth mentioning again that almost all of the common controls provide their own events to indicate when a mouse clicks on areas of the control. Most often, those events are all that a programmer needs to create a user interface. However the dialog does not provide such events, nor do the control events cover all possible mouse actions, so the information just presented can be useful.

As helpful as these events might seem, monitoring 24 different mouse events can be pretty cumbersome. In the following discussion of the Miscellaneous column of mouse messages, we'll see that other mouse messages can simplify the task of monitoring for mouse events.

Miscellaneous Mouse Messages
Here is a description of the remaining mouse messages. Though fewer in number, you'll find several of these to be generally more useful than the messages already discussed.

    WM_MOUSEMOVE	- moves within the client area
    WM_NCMOUSEMOVE	- moves within the non-client area

    WM_NCHITTEST	- determines if event was in non-client area

    WM_MOUSEACTIVATE	- set top-level window as active
    WM_CAPTURECHANGED	- sent to window losing mouse capture

    WM_MOUSEHOVER	- hovers for a certain time period over client area
    WM_NCMOUSEHOVER	- hovers for a certain time period over non-client area
    WM_MOUSELEAVE	- leaves the client area
    WM_NCMOUSELEAVE	- leaves the non-client area

    WM_MOUSEHWHEEL	- detects movement of mouse wheel
    WM_MOUSEWHEEL	- detects movement of mouse wheel

Each of these are discussed below, including the content of wParam/lParam and how it might be used.

WM_PARENTNOTIFY
Surprise! This message was not discussed earlier, because it is not a mouse message, but rather a dialog message. This message is sent to the dialog when the user clicks a mouse on a child window. This is one of the most common mouse clicks. So if the child control does not provide an event for the click, then the WM_PARENTNOTIFY event is the first place to go, even before the dedicated mouse event messages!

Which mouse button was pressed and the xy coordinates of the cursor (client coordinates) are provided by WM_PARENTNOTIFY.

WM_SETCURSOR
Surprise, again! This message was not discussed earlier, because it is not a mouse message, but rather a cursor message (the only cursor message Windows provides).

Nonetheless, it provides information useful to a PowerBASIC programmer in working with a mouse. Whereas the WM_MOUSEMOVE and WM_NCMOUSEMOVE work only on either the client and non-client areas, respectively, the WM_SETCURSOR message is sent when mouse movement occurs anywhere within a window.

The dialog/control where the mouse was located, the message identifier, and the section of the window beneath the cursor are provided by WM_SETCURSOR.

WM_MOUSEMOVE, WM_NCMOUSEMOVE
These messages are sent when a mouse is moved in the client or non-client areas, respectively.

WM_NCHITTEST
This message is sent to a window when the cursor moves, or when a mouse button is pressed or released - basically the entire waterfront of events.

The only information provided is the xy coordinates of the cursor (screen coordinates). But, once the user sends this message on to the DefWindowProc, a return value will be received which is one of the hit-test position indicators (see values in WM_SETCURSOR discussion above).

To send the message on the DefWindowProc, use the following code:

    iResult& = DefWindowProc(CB.hWnd,CB.Msg,CB.wParam,CB.lParam)

And here's the information provided by wParam and lParam.

WM_MOUSEACTIVATE
This message is sent when the cursor is over an inactive window and the user presses a mouse button.

WM_CAPTURECHANGED
At any one time, only one window can receive mouse inputs. That window is said to have gained the mouse capture.

Normally, clicking on a window gives it focus and captures the mouse. However, a window can use SetCapture to capture the mouse. A window that has captured the mouse receives all mouse input, regardless of the position of the cursor, except when a mouse button is clicked while the cursor is in the window of another thread. The ReleaseCapture functions releases mouse capture from a window and restores normal mouse input processing.

When a window loses mouse capture, it is sent the message WM_CAPTURECHANGED.

WM_MOUSEHOVER
Hovering is only tracked when a TrackMouseEvent function is called, specifying a window to track and the time interval which constitutes "hovering". Once TrackMouseEvent is called, the WM_MOUSEHOVER, WM_NCMOUSEHOVER, WM_MOUSELEAVE, and WM_NCMOUSELEAVE messages will be generated.

WM_MOUSEHOVER is posted when the cursor hovers over the non-client area for a specified time.

WM_NCMOUSEHOVER
Hovering is only tracked when a TrackMouseEvent function is called, specifying a window to track and the time interval which constitutes "hovering". Once TrackMouseEvent is called, the WM_MOUSEHOVER, WM_NCMOUSEHOVER, WM_MOUSELEAVE, and WM_NCMOUSELEAVE messages will be generated.

WM_NCMOUSEHOVER is posted when the cursor hovers over the non-client area for a specified time.

WM_MOUSELEAVE, WM_NCMOUSELEAVE
These messages work with WM_MOUSEHOVER and WM_NCMOUSEHOVER, and are posted when the cursor leaves the client (WM_MOUSELEAVE) or the non-client (WM_NCMOUSELEAVE) area of the window specified in a prior call to TrackMouseEvent.

Tracking requested by TrackMouseEvent is canceled when this message is generated.

WM_MOUSEWHEEL, WM_MOUSEHWHEEL
The wheel on a mouse can be pressed, or rotated.

When pressed, it is treated as a middle mouse button, whose events were covered further up this page.

When the wheel is rotated, the WM_MOUSEWHEEL message is sent to the focus window.

The WM_MOUSEWHEEL message return the amount of rotation, which keys are down, and the xy screen coordinates of the pointer.

MousePTR DDT Function
Slightly outside the topic of mouse messages, is the PowerBASIC function MousePTR, which can set the mouse pointer to a new shape. MousePTR is often used as part of the response to mouse events.

    MOUSEPTR iStyle& TO iResult&    'TO is optional

iResult is set to zero if the operation fails, or to a handle for the old cursor if it succeeds. The handle may be used to restore the cursor by using it in place of iStyle&

iStyle options are:

    0 -  Hide                        7 - Pointer: Vertical
    1 -  Arrow                       8 - Pointer: NW-SE diagonal
    2 -  Cross                       9 - Pointer: Horizontal
    3 -  I-Beam                     10 - Up arrow
    4 -  Arrow                      11 - Hourglass
    5 -  Pointer: all directions    12 - No mouse pointer
    6 -  Pointer: NE-SW diagonal    13 - Arrow w/hourglass

If you have any suggestions or corrections, please let me know.