views:

907

answers:

4

I use a number of scrolling controls: TTreeViews, TListViews, DevExpress grids and treelists, etc. When the mouse wheel is spun, the control with focus receives the input no matter what control the mouse cursor is over.

How do you direct the mouse wheel input to whatever control the mouse cursor is over? The Delphi IDE works very nicely in this regard.

+2  A: 

You might find this article useful: send a scroll down message to listbox using mousewheel, but listbox doesn't have focus [1], it is written in C#, but converting to Delphi shouldn't be too big a problem. It uses hooks to accomplish the wanted effect.

To find out which component the mouse is currently over, you can use the FindVCLWindow function, an example of this can be found in this article: Get the Control Under the Mouse in a Delphi application [2].

[1] http://social.msdn.microsoft.com/forums/en-US/winforms/thread/ec1fbfa2-137e-49f6-b444-b634e4f44f21/
[2] http://delphi.about.com/od/delphitips2008/qt/find-vcl-window.htm

TommyA
+4  A: 

Try overriding your form's MouseWheelHandler method like this (I have not tested this thoroughly):

procedure TMyForm.MouseWheelHandler(var Message: TMessage);
var
  Control: TControl;
begin
  Control := ControlAtPos(ScreenToClient(SmallPointToPoint(TWMMouseWheel(Message).Pos)), False, True, True);
  if Assigned(Control) and (Control <> ActiveControl) then
  begin
    Message.Result := Control.Perform(CM_MOUSEWHEEL, Message.WParam, Message.LParam);
    if Message.Result = 0 then
      Control.DefaultHandler(Message);
  end
  else
    inherited MouseWheelHandler(Message);

end;
TOndrej
Almost working. ControlAtPos() gets the immediate child, so if the control is in panel, it returns the panel. FindVCLWindow(Mouse.CursorPos) returns the correct control.Just the DevExpress TcxTreeList scrolls too much - seems to do 3x the scroll.
avenmore
FindVCLWindow only works with TWinControl descendants.
TOndrej
This turned out to be the solution that worked for me. The solution to the excessive scrolling was setting Message.Result := 1. Will watch out for limitations of FindVCLWindow. Thanks for the help.
avenmore
I've submitted QC report 82143 about what seems to be a bug in ControlAtPos implementation which fails to find nested child controls:http://qc.embarcadero.com/wc/qcmain.aspx?d=82143
TOndrej
+2  A: 

Override the TApplication.OnMessage event (or create a TApplicationEvents component) and redirect the WM_MOUSEWHEEL message in the event handler:

procedure TMyForm.AppEventsMessage(var Msg: tagMSG;
  var Handled: Boolean);
var
  Pt: TPoint;
  C: TWinControl;
begin
  if Msg.message = WM_MOUSEWHEEL then begin
    Pt.X := Word(Msg.lParam);
    Pt.Y := HiWord(Msg.lParam);
    C := FindVCLWindow(Pt);
    if C = nil then 
      Handled := True
    else if C.Handle <> Msg.hwnd then begin
      Handled := True;
      SendMessage(C.Handle, WM_MOUSEWHEEL, Msg.wParam, Msg.lParam);
    end;
   end;
end;

It works fine here, though you may want to add some protection to keep it from recursing if something unexpected happens.

Craig Peterson
I think this is the best answer. The problem is that a focused DevExpress control still intercepts this message. If I call C.Perform() instead of SendMessage(), then the DevExpress controls work but the common controls don't. Have to do some digging in the DevExpress source to disable this hook.
avenmore
I ended up abandoning this solution as it seems that the focused TControl (nothing to to do with DevExpress) always intercepts the message.
avenmore
A: 

In the OnMouseEnter event for each scrollable control add a respective call to SetFocus

So for ListBox1:

procedure TForm1.ListBox1MouseEnter(Sender: TObject);  
begin  
    ListBox1.SetFocus;  
end;  

Does this achieve the desired effect?

No, that would be bad behaviour for a program.
avenmore
This will change the user experience serverly. Not everybody has wrked with the X windows manager where you move the mouse to give focus to different windows..
Ritsaert Hornstra