Problem Description
Here is a demonstration of how to process two
Delphi mouse messages that are normally ignored - MouseEnter and
MouseLeave.
Background & Techniques
A recent version of Oscilloscope
adds a bar chart of the frequencies contained in the input sample. I
wanted to display the current frequency and amplitude values as the user
moved the mouse cursor over the chart (a Timage control). I
decided to use a TLabel and show it along side the mouse cursor,
updated each time the cursor moved. If the cursor was not over the
image, the label should disappear, but how to know if the cursor left the
image? I decided to use the MouseEnter and MouseLeave
messages generated by the Forms control when the mouse enters
or leaves a control. The message triggers code to set the
"visible" property of the information label.
Here's a demo program that illustrates the technique. It also
illustrates how to make an temporary component that has all the advantages
of a visual designed without the hassle of making a true
component.
Temporary Components
Special purpose components are easy to generate but a nuisance to
install and maintain. They reduce the portability of programs since
any other system must have installed all of the components the program
uses. Here's an alternative. Drop the root visual component
on the form and resize, move, color, set exits - whatever features
will apply to the descendent component. Then define the descendent
component, create it in the FormsCreate procedure and make (or
override) an Assign procedure to assign to the new component all of
the ancestor control properties we have modified. (Two special
considerations: The windowed parent control of our visual model is
automatically assigned by Delphi, so we must assign the parent property to
our new control even though we may not even be aware of its existent to
the ancestor. Also, names of controls must be unique, so if you need
to generate a unique name for your control. (For example:
name:='My'+prototype.name;)
The Message Mechanics
The VCL unit Controls.Pas defines sixty-some "VCL
Control Message" numbers all prefixed with CM_,
including CM_MouseEnter and CM_MouseLeave. Unit Forms.pas
checks during it's idle time processing loop. Whenever the previous
mouse owner changes, it sends a CM_MouseLeave message to the old
owner and a CM_MouseEnter message to the new
owner. The ancestor for all VCL components, TControl,
has default processing routines, CMMouseEnter and CMMouseLeave,
for these messages which you may override in any descendent control.
If not overridden, the default routines each check to see if the
parent processor has routines and if so, calls them. So your enter/leave mouse processing will receive messages only when the
mouse "enters from" or "leaves to" a non-child
control.
The Demo
In our current case, I wanted to intercept the mouse messages in a
couple of TShape controls. The definition for the
descendent class looks like this:
TMyShape=class(TShape)
procedure CMMouseEnter(var Msg: TMessage); message
CM_MouseEnter;
procedure CMMouseLeave(var Msg: TMessage); message CM_MouseLeave;
procedure assign(proto:TShape);
end;
We have overridden the default handling procedures for these
messages. In the public section of the form, I defined the two
shapes:
Square:TMyShape;
Circle:TMyShape;
In the FormCreate procedure we'll create the shapes
Square:=TMyShape.Create(self);
Square.Assign(Shape1);
Circle:=TMyShape.Create(self);
Circle.Assign(Shape2);
You can click the "Browse Source Extract" button below to see
the Assign procedure and the rest of the code.
At mouse enter time, a label caption is set to "Cursor is in
..." + the shape name. At mouse exit time then caption is
change to "Cursor is not in any shape" and it is moved to the
bottom left corner of the Panel that contains the
shapes. The shape color is also brightened as the mouse
enters and dimmed when the mouse leaves each shape. An OnMouseMove
event exit moves the label to follow the cursor as moves across the
shape.
Considerations
A couple of items worth mentioning.
The program moves a label to the mouse cursor location each time the
mouse moves either of the shapes. My original location for the label
was so close to the mouse cursor location that many, many
CM_MouseLeave & CM_MouseEnter messages were generated as the mouse
moved over the label anf then the label moved so the mouse again entered
the shape. Adding an extra 5 pixel buffer between the cursor
and the label solved the problem.
I also wanted to have the effect of a light coming on when the mouse
entered the shape by brightening it up. I accomplished this by
defining a "dark blue" for shape1 by setting the blue byte value
of the brush.color field to 127. For the other shape, I defined
"dark red" with a red byte value of 127. With
these definitions I can multiply the color value by 2 when mouse
enter is received brighten it and divide the color by 2 when mouse
leave is received to darken it again. Just be aware that this
technique will not work in general. For that we would have to split
out the RGB color bytes, adjust them individually, and then reconstruct
the color word from the bytes.
Running/Exploring the Program