Recently, a user asked me if there was a way to make a list box track the mouse (have the currently selected item be the one under the pointer) as it moves over the items much like a combo box does when you drop down its list. I responded that if he really needed a quick solution for his project, he could drop a TListBox component onto his form and write an event handler for the OnMouseMove event. The code would be fairly simplejust a couple of lines like this:
procedure TForm1.ListBox1MouseMove(Sender: TObject; Shift: TShiftState;
X, Y: Integer);
var
P : TPoint;
begin
{Grab the position of the mouse and put it into a TPoint}
P.X := X;
P.Y := Y;
{Set the ItemIndex of the list box to the Item under the current mouse
coordinates}
ListBox1.ItemIndex := ListBox1.ItemAtPos(P, True);
end;
The ItemAtPos method of TListBox makes all of this work. As the mouse passes over the list box, the event handler picks up the coordinates. ItemAtPos then passes back the index of the list under the current mouse position. Doing the assignment to the ItemIndex property changes the current selection in the list boxpretty neat.
Well, that was the quick and dirty solution, but there's more to it. It is possible that you could use this functionality in more than one project. In that case, you'd have to create a new component. Now while the solution is simple, and in fact, uses pretty much the same code as before, finding the right method to override in the ancestral tree isn't as readily apparent as you might think. This is because the handler code for a mouse move isn't defined at the TListBox level. It's actually defined about four parent levels up at the TControl level. So visibility is a bit of an issue.
Furthermore, for those of you who have written event handlers for components, the natural tendency (and usually the correct one) would be to write a Windows message handler for WM_MOUSEMOVE. Well, you can, but in order to get the coordinates, you need to get the high- and low-order bits from the lParam parameter of the message record to resolve the coordinate values. It's just a bit more coding.
As I mentioned earlier, you can use code similar to the previous listing for the new descendant class. And you can do this with TControl's dynamic method MouseMove. MouseMove's declaration is almost exactly like the OnMouseMove event handler. It's just missing the Sender parameter. The other parameters Shift, X, and Y are all there. How convenient! So with MouseMove, you've found the method to override.
Before I give you the code listing, you might wonder why I chose to descend from TCustomListBox, and not TListBox. The reason for this is simple: TListBox surfaces a property called MultiSelect that allows a user to select multiple items. Within the scope of this solution, that property is meaningless. Why? Because as the mouse moves over the list, the ItemIndex property constantly changes. You just can't select multiple items in this case. With that in mind, descending from TCustomListBox makes more sense because you can leave out the MultiSelect property yet retain all the other functionality of TListBox. Here's the code listing:
unit EnhListBox;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TEnhListBox = class(TCustomListBox)
private
{ Private declarations }
protected
procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
public
{ Public declarations }
published
property Align;
property Anchors;
property BiDiMode;
property BorderStyle;
property Color;
property Columns;
property Constraints;
property Ctl3D;
property DragCursor;
property DragKind;
property DragMode;
property Enabled;
property ExtendedSelect;
property Font;
property ImeMode;
property ImeName;
property IntegralHeight;
property ItemHeight;
property Items;
property ParentBiDiMode;
property ParentColor;
property ParentCtl3D;
property ParentFont;
property ParentShowHint;
property PopupMenu;
property ShowHint;
property Sorted;
property Style;
property TabOrder;
property TabStop;
property TabWidth;
property Visible;
property OnClick;
property OnDblClick;
property OnDragDrop;
property OnDragOver;
property OnDrawItem;
property OnEndDock;
property OnEndDrag;
property OnEnter;
property OnExit;
property OnKeyDown;
property OnKeyPress;
property OnKeyUp;
property OnMeasureItem;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
property OnStartDock;
property OnStartDrag;
end;
procedure Register;
implementation
{ TEnhListBox }
procedure TEnhListBox.MouseMove(Shift: TShiftState; X, Y: Integer);
var
P : TPoint;
begin
//Make sure to call the inherited method first!
inherited MouseMove(Shift, X, Y);
//Grab the current mouse coordinates
P.X := X;
P.Y := Y;
//Set the index
Self.ItemIndex := Self.ItemAtPos(P, True);
end;
procedure Register;
begin
RegisterComponents('BD', [TEnhListBox]);
end;
end.