Changing the mouse wheel scroll behaviour of TListBox

While working on my dzMdbViewer I wondered why the mouse wheel didn’t seem to scroll the listbox containing the table names but did work in the dbtable. It turned out to be a misconception on my side:

The listbox contained only a few items so it didn’t have a vertical scroll bar. The default behaviour is to scroll only the view without changing the currently selected item. Of course this does not have any effect if all items are already visible.

But that wasn’t what I wanted the mouse wheel to do. I wanted it to have the same effect as on the dbtable, that is: Select the next/previous line.

Unfortunately a listbox does not have an OnMouseWheel event (a dbtable does have one), so I could not simply assign an event handler and be done with it. I resorted to a proven hack and created an interposer class that overrides TListBox.DoMouseWheel:

type
  TListBox = class(StdCtrls.TListBox)
    ///<summary>
    /// Overrides the default behaviour of the TListbox for mouse wheel scrolling:
    /// Rather than scroll the view without affecting the selected item, select the
    /// next/previous item as if the user pressed the up/down arrow key. </summary>
    function DoMouseWheel(_Shift: TShiftState; _WheelDelta: Integer; _MousePos: TPoint): Boolean; override;
  end;

type
  Tfr_AccessDb = class(TFrame)
    lb_Tables: TListBox;
  private
  public
  end;

implementation

{$R *.dfm}

uses
  Math,
  u_dzVclUtils;

{ TListBox }

function TListBox.DoMouseWheel(_Shift: TShiftState; _WheelDelta: Integer; _MousePos: TPoint): Boolean;
var
  Idx: Integer;
begin
  // calculate the index of the item to select
  Idx := ItemIndex - Sign(_WheelDelta);
  if Idx >= Items.Count then
    Idx := Items.Count
  else if Idx < 0 then
    Idx := 0;
  // select it
  ItemIndex := Idx;
  // and simulate a mouse click on it so the selected table gets displayed
  Self.Click;

  // tell the caller that the event has been handled
  Result := True;
end;

Simple, isn't it?

I found a more involved version on The Spirit of Delphi that overrides the WndProc method and changes the Message to a keyboard message. I like my solution better, it "feels" cleaner.

The other solution also creates a control rather than using an interposer class. That's cleaner than an interposer class but I'm not a great fan of writing my own components to solve shortcomings like this. It's just too much overhead.