Как направить ввод колеса мыши для управления под курсором вместо фокусировки? - PullRequest
37 голосов
/ 12 февраля 2010

Я использую несколько элементов прокрутки: TTreeViews, TListViews, DevExpress cxGrids и cxTreeLists и т. Д. Когда колесо мыши вращается, элемент управления с фокусом получает ввод независимо от того, над каким элементом управления курсор мыши находится над.

Как вы направите вход колеса мыши на какой-либо элемент управления курсором мыши? В этом отношении Delphi IDE работает очень хорошо.

Ответы [ 8 ]

22 голосов
/ 12 февраля 2010

Попробуйте переопределить метод MouseWheelHandler вашей формы следующим образом (я не проверял это полностью):

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;
18 голосов
/ 21 декабря 2015

Прокрутка происхождения

Действие с колесом мыши приводит к отправке WM_MOUSEWHEEL сообщения :

Отправляется в окно фокусировки при вращении колесика мыши. Функция DefWindowProc передает сообщение родителю окна. Не должно быть внутренней пересылки сообщения, поскольку DefWindowProc распространяет его по родительской цепочке, пока не найдет окно, которое его обрабатывает.

Одиссея колесика мыши 1)

  1. Пользователь прокручивает колесо мыши.
  2. Система помещает сообщение WM_MOUSEWHEEL в очередь сообщений потока переднего окна.
  3. Цикл сообщений потока извлекает сообщение из очереди (Application.ProcessMessage). Это сообщение типа TMsg, в котором есть hwnd элемент, обозначающий дескриптор окна, для которого предназначено сообщение.
  4. Событие Application.OnMessage происходит.
    1. Установка параметра Handled True останавливает дальнейшую обработку сообщения (кроме следующих шагов).
  5. Вызывается метод Application.IsPreProcessMessage.
    1. Если элемент управления не захватил мышь, вызывается метод PreProcessMessage для сфокусированного элемента управления, который по умолчанию ничего не делает. Ни один элемент управления в VCL не переопределил этот метод.
  6. Вызывается метод Application.IsHintMsg.
    1. Активное окно подсказки обрабатывает сообщение переопределенным методом IsHintMsg. Запретить дальнейшую обработку сообщения невозможно.
  7. DispatchMessage называется.
  8. Метод сфокусированного окна TWinControl.WndProc получает сообщение. Это сообщение типа TMessage, в котором отсутствует окно (потому что это тот экземпляр, к которому вызывается этот метод).
  9. Метод TWinControl.IsControlMouseMsg вызывается для проверки того, должно ли сообщение мыши быть направлено на один из его неоконных дочерних элементов управления.
    1. Если есть дочерний элемент управления, который захватил мышь или находится в текущей позиции мыши 2) , то сообщение отправляется методу WndProc дочернего элемента управления, см. Шаг 10. ( 2) Это никогда не произойдет, поскольку WM_MOUSEWHEEL содержит свою позицию мыши в экранных координатах, а IsControlMouseMsg предполагает положение мыши в клиентских координатах (XE2).)
  10. Унаследованный метод TControl.WndProc получает сообщение.
    1. Если система изначально не поддерживает колесико мыши (CM_MOUSEWHEEL и отправляется на TControl.MouseWheelHandler, см. Шаг 13.
    2. В противном случае сообщение отправляется соответствующему обработчику сообщений.
  11. Метод TControl.WMMouseWheel получает сообщение.
  12. WM_MOUSEWHEEL w indow m essage (имеет значение для системы и часто для VCL) преобразуется в CM_MOUSEWHEEL c контроль m essage (имеет смысл только для VCL), который предоставляет удобную информацию VCL ShiftState вместо данных ключей системы.
  13. Вызывается метод MouseWheelHandler элемента управления.
    1. Если элемент управления TCustomForm, то вызывается метод TCustomForm.MouseWheelHandler.
      1. Если на нем есть сфокусированный элемент управления, то CM_MOUSEWHEEL отправляется на фокусированный элемент управления, см. Шаг 14.
      2. В противном случае вызывается унаследованный метод, см. Шаг 13.2.
    2. В противном случае вызывается метод TControl.MouseWheelHandler.
      1. Если существует элемент управления, который захватил мышь и не имеет родителя 3) , то сообщение отправляется этому элементу управления, см. Шаг 8 или 10, в зависимости от типа элемента управления. ( 3) Это никогда не произойдет, потому что Capture получается с GetCaptureControl, который проверяет Parent <> nil (XE2).)
      2. Если элемент управления находится в форме, то вызывается форма элемента управления MouseWheelHandler, см. Шаг 13.1.
      3. В противном случае, или если элемент управления является формой, тогда элемент управления CM_MOUSEWHEEL отправляется в элемент управления, см. Шаг 14.
  14. Метод TControl.CMMouseWheel получает сообщение.
    1. Вызывается метод TControl.DoMouseWheel.
      1. Событие OnMouseWheel сработало.
      2. IЕсли f не обрабатывается, то вызывается TControl.DoMouseWheelDown или TControl.DoMouseWheelUp, в зависимости от направления прокрутки.
      3. Событие OnMouseWheelDown или OnMouseWheelUp происходит.
    2. Если не обработано, то CM_MOUSEWHEEL отправляется родительскому элементу управления, см. Шаг 14. (Я считаю, что это противоречит совету, данному MSDN в приведенной выше цитате, но это, несомненно, продуманное решение, принятое разработчиками. Возможно, потому что это положило бы начало всей этой цепочке.)

Замечания, замечания и соображения

Почти на каждом этапе в этой цепочке обработки сообщение можно игнорировать, ничего не делая, изменяя его, изменяя параметры сообщения, обрабатывая его и отменяя настройкой Handled := True или установкой Message.Result в ненулевое значение .

Только когда какой-то элемент управления имеет фокус, это сообщение принимается приложением. Но даже когда Screen.ActiveCustomForm.ActiveControl принудительно установлен на nil, VCL обеспечивает сфокусированное управление с TCustomForm.SetWindowFocus, которое по умолчанию имеет ранее активную форму. (С Windows.SetFocus(0), действительно, сообщение никогда не отправляется.)

Из-за ошибки в IsControlMouseMsg 2) , TControl может получить сообщение WM_MOUSEWHEEL, только если оно захватило мышь. Это можно сделать вручную , установив Control.MouseCapture := True, но вы должны позаботиться о скорейшем освобождении этого захвата, иначе у него будут нежелательные побочные эффекты, такие как необходимость лишнего дополнительного щелчка, чтобы что-то сделать. Кроме того, захват мыши , как правило, происходит только между событием «нажатие мыши» и «вверх», но это ограничение не обязательно должно применяться. Но даже когда сообщение достигает элемента управления, оно отправляется методу MouseWheelHandler, который просто отправляет его обратно либо в форму, либо в активный элемент управления. Таким образом, неоконные элементы управления VCL никогда не могут воздействовать на сообщение по умолчанию. Я полагаю, что это еще одна ошибка, иначе почему бы все управление колесами было реализовано в TControl? Авторы компонентов, возможно, реализовали свой собственный метод MouseWheelHandler для этой цели, и какое бы решение ни пришло к этому вопросу, нужно позаботиться о том, чтобы не сломать этот вид существующей настройки.

Собственные элементы управления , которые могут прокручиваться с помощью колеса, такие как TMemo, TListBox, TDateTimePicker, TComboBox, TTreeView, TListView и т. Д., Прокручиваются сама система. Отправка CM_MOUSEWHEEL на такой элемент управления по умолчанию не действует. Эти подклассы управляют прокруткой в ​​результате сообщения WM_MOUSEWHEEL, отправленного в оконную процедуру API, связанную с подклассом, с CallWindowProc, о которой VCL заботится в TWinControl.DefaultHandler. Как ни странно, эта процедура не проверяет Message.Result перед вызовом CallWindowProc, и после отправки сообщения прокрутка не может быть предотвращена. Сообщение возвращается с его установленным Result в зависимости от того, способен ли элемент управления обычно прокручиваться или от типа элемента управления. (Например, a TMemo возвращает <> 0, а TEdit возвращает 0.) То, будет ли эта прокрутка фактически не влиять на результат сообщения.

Элементы управления VCL полагаются на обработку по умолчанию, реализованную в TControl и TWinControl, как изложено выше. Они действуют на событиях колеса в DoMouseWheel, DoMouseWheelDown или DoMouseWheelUp. Насколько я знаю, ни один элемент управления в VCL не переопределил MouseWheelHandler для обработки событий колеса.

Глядя на различные приложения, кажется, что нет соответствия стандартному поведению колесной прокрутки. Например: MS Word прокручивает отображаемую страницу, MS Excel прокручивает рабочую книгу, которая сфокусирована, Windows Eplorer прокручивает сфокусированную панель, веб-сайты реализуют поведение прокрутки по-разному, Evernote прокручивает окно, которое отображается, и т. Д ... И Delphi's Собственная IDE превосходит все, прокручивая сфокусированное окно , а также наведенное окно, кроме случаев, когда вы наводите курсор мыши на редактор кода, тогда редактор кода во время прокрутки (XE2) крадет фокус .

К счастью, Microsoft предлагает как минимум руководств по работе с пользователями для настольных приложений на базе Windows :

  • Заставьте колесо мыши влиять на элемент управления, панель или окно, над которым в данный момент находится указатель. Это позволяет избежать непредвиденных результатов.
  • Включите колесико мыши, не щелкая мышью и не имея фокуса ввода. Наведение достаточно.
  • Сделайте так, чтобы колесо мыши воздействовало на объект с наиболее определенной областью действия. Например, если указатель находится над элементом управления прокручиваемого списка в прокручиваемой панели внутри прокручиваемого окна, колесо мыши влияет на список. коробка управления.
  • Не меняйте фокус ввода при использовании колесика мыши.

Таким образом, требование вопроса только прокрутить находящийся элемент управления имеет достаточные основания, но разработчики Delphi не упростили его реализацию.

Вывод и решение

Предпочтительным решением является решение без окон подклассов или нескольких реализаций для различных форм или элементов управления.

Чтобы предотвратить прокрутку сфокусированного элемента управления, элемент управления может не получить сообщение CM_MOUSEWHEEL. Следовательно, MouseWheelHandler любого контроля не может быть вызван. Следовательно, WM_MOUSEWHEEL не может быть отправлено никакому элементу управления. Таким образом, единственное место, оставленное для вмешательства, - TApplication.OnMessage. Кроме того, сообщение может не выходить из него, поэтому обработка all должна происходить в этом обработчике событий, и когда вся обработка колеса VCL по умолчанию обойдена, необходимо учитывать все возможные условия.

Давайте начнем с простого. Активированное окно, которое в данный момент находится в окне, вызывается с WindowFromPoint.

procedure TForm1.ApplicationEvents1Message(var Msg: tagMSG;
  var Handled: Boolean);
var
  Window: HWND;
begin
  if Msg.message = WM_MOUSEWHEEL then
  begin
    Window := WindowFromPoint(Msg.pt);
    if Window <> 0 then
    begin

      Handled := True;
    end;
  end;
end;

С помощью FindControl мы получаем ссылку на элемент управления VCL. Если результат равен nil, то окно наведения не относится к процессу приложения или это окно, неизвестное VCL (например, выпадающее меню TDateTimePicker). В этом случае сообщение необходимо переслать обратно в API, и его результат нас не интересует.

  WinControl: TWinControl;
  WndProc: NativeInt;

      WinControl := FindControl(Window);
      if WinControl = nil then
      begin
        WndProc := GetWindowLongPtr(Window, GWL_WNDPROC);
        CallWindowProc(Pointer(WndProc), Window, Msg.message, Msg.wParam,
          Msg.lParam);
      end
      else
      begin

      end;

Когда окно является элементом управления VCL, несколько обработчиков сообщений должны рассматриваться как вызывающие в определенном порядке. Когда в позиции мыши есть включенный неоконный элемент управления (типа TControl или потомок), он сначала должен получить сообщение CM_MOUSEWHEEL, поскольку этот элемент управления, безусловно, является передним элементом управления. Сообщение должно быть составлено из сообщения WM_MOUSEWHEEL и переведено в его эквивалент VCL. Во-вторых, сообщение WM_MOUSEWHEEL должно быть отправлено методу DefaultHandler элемента управления, чтобы разрешить обработку для собственных элементов управления. И наконец, снова сообщение CM_MOUSEWHEEL должно быть отправлено элементу управления, когда ни один предыдущий обработчик не позаботился о сообщении. Эти два последних шага не могут быть выполнены в обратном порядке, например, потому что заметка на поле прокрутки должна быть в состоянии прокручивать тоже.

  Point: TPoint;
  Message: TMessage;

        Point := WinControl.ScreenToClient(Msg.pt);
        Message.WParam := Msg.wParam;
        Message.LParam := Msg.lParam;
        TCMMouseWheel(Message).ShiftState :=
          KeysToShiftState(TWMMouseWheel(Message).Keys);
        Message.Result := WinControl.ControlAtPos(Point, False).Perform(
          CM_MOUSEWHEEL, Message.WParam, Message.LParam);
        if Message.Result = 0 then
        begin
          Message.Msg := Msg.message;
          Message.WParam := Msg.wParam;
          Message.LParam := Msg.lParam;
          WinControl.DefaultHandler(Message);
        end;
        if Message.Result = 0 then
        begin
          Message.WParam := Msg.wParam;
          Message.LParam := Msg.lParam;
          TCMMouseWheel(Message).ShiftState :=
            KeysToShiftState(TWMMouseWheel(Message).Keys);
          Message.Result := WinControl.Perform(CM_MOUSEWHEEL, Message.WParam,
            Message.LParam);
        end;

Когда окно захватывает мышь, все сообщения колеса должны быть отправлены на него. Окно, полученное с помощью GetCapture, обязательно будет окном текущего процесса, но оно не обязательно должно быть элементом управления VCL. Например. во время операции перетаскивания создается временное окно (см. TDragObject.DragHandle), которое принимает сообщения мыши. Все сообщения? Нееет, WM_MOUSEWHEEL не отправляется в окно захвата, поэтому мы должны перенаправить его. Кроме того, когда окно захвата не обрабатывает сообщение, должна выполняться вся другая ранее описанная обработка. Эта особенность отсутствует в VCL: при работе во время операции перетаскивания действительно вызывается Form.OnMouseWheel, но элемент управления с фокусировкой или зависанием не получает сообщение. Это означает, например, что текст нельзя перетащить в содержимое заметки в месте, которое находится за пределами видимой части заметки.

    Window := GetCapture;
    if Window <> 0 then
    begin
      Message.Result := GetCaptureControl.Perform(CM_MOUSEWHEEL, Message.WParam,
        Message.LParam);
      if Message.Result = 0 then
        Message.Result := SendMessage(Window, Msg.message, Msg.wParam,
          Msg.lParam);
    end;

Это, по сути, делает работу, и это было основой для единицы, представленной ниже. Чтобы заставить его работать, просто добавьте имя модуля в одно из предложений использования в вашем проекте. Он имеет следующие дополнительные функции:

  • Возможность предварительного просмотра действия колеса в главной форме, активной форме или активном элементе управления.
  • Регистрация классов управления, для которых должен быть вызван их метод MouseWheelHandler.
  • Возможность поставить этот TApplicationEvents объект перед всеми остальными.
  • Возможность отменить отправку события OnMessage всем другим TApplicationEvents объектам.
  • Возможность по-прежнему разрешать обработку VCL по умолчанию впоследствии для аналитических или тестовых целей.

ScrollAnywhere.pas

unit ScrollAnywhere;

interface

uses
  System.Classes, System.Types, System.Contnrs, Winapi.Windows, Winapi.Messages,
  Vcl.Controls, Vcl.Forms, Vcl.AppEvnts;

type
  TWheelMsgSettings = record
    MainFormPreview: Boolean;
    ActiveFormPreview: Boolean;
    ActiveControlPreview: Boolean;
    VclHandlingAfterHandled: Boolean;
    VclHandlingAfterUnhandled: Boolean;
    CancelApplicationEvents: Boolean;
    procedure RegisterMouseWheelHandler(ControlClass: TControlClass);
  end;

  TMouseHelper = class helper for TMouse
  public
    class var WheelMsgSettings: TWheelMsgSettings;
  end;

procedure Activate;

implementation

type
  TWheelInterceptor = class(TCustomApplicationEvents)
  private
    procedure ApplicationMessage(var Msg: tagMSG; var Handled: Boolean);
  public
    constructor Create(AOwner: TComponent); override;
  end;

var
  WheelInterceptor: TWheelInterceptor;
  ControlClassList: TClassList;

procedure TWheelInterceptor.ApplicationMessage(var Msg: tagMSG;
  var Handled: Boolean);
var
  Window: HWND;
  WinControl: TWinControl;
  WndProc: NativeInt;
  Message: TMessage;
  OwningProcess: DWORD;

  procedure WinWParamNeeded;
  begin
    Message.WParam := Msg.wParam;
  end;

  procedure VclWParamNeeded;
  begin
    TCMMouseWheel(Message).ShiftState :=
      KeysToShiftState(TWMMouseWheel(Message).Keys);
  end;

  procedure ProcessControl(AControl: TControl;
    CallRegisteredMouseWheelHandler: Boolean);
  begin
    if (Message.Result = 0) and CallRegisteredMouseWheelHandler and
      (AControl <> nil) and
      (ControlClassList.IndexOf(AControl.ClassType) <> -1) then
    begin
      AControl.MouseWheelHandler(Message);
    end;
    if Message.Result = 0 then
      Message.Result := AControl.Perform(CM_MOUSEWHEEL, Message.WParam,
        Message.LParam);
  end;

begin
  if Msg.message <> WM_MOUSEWHEEL then
    Exit;
  with Mouse.WheelMsgSettings do
  begin
    Message.Msg := Msg.message;
    Message.WParam := Msg.wParam;
    Message.LParam := Msg.lParam;
    Message.Result := LRESULT(Handled);
    // Allow controls for which preview is set to handle the message
    VclWParamNeeded;
    if MainFormPreview then
      ProcessControl(Application.MainForm, False);
    if ActiveFormPreview then
      ProcessControl(Screen.ActiveCustomForm, False);
    if ActiveControlPreview then
      ProcessControl(Screen.ActiveControl, False);
    // Allow capturing control to handle the message
    Window := GetCapture;
    if (Window <> 0) and (Message.Result = 0) then
    begin
      ProcessControl(GetCaptureControl, True);
      if Message.Result = 0 then
        Message.Result := SendMessage(Window, Msg.message, Msg.wParam,
          Msg.lParam);
    end;
    // Allow hovered control to handle the message
    Window := WindowFromPoint(Msg.pt);
    if (Window <> 0) and (Message.Result = 0) then
    begin
      WinControl := FindControl(Window);
      if WinControl = nil then
      begin
        // Window is a non-VCL window (e.g. a dropped down TDateTimePicker), or
        // the window doesn't belong to this process
        WndProc := GetWindowLongPtr(Window, GWL_WNDPROC);
        Message.Result := CallWindowProc(Pointer(WndProc), Window,
          Msg.message, Msg.wParam, Msg.lParam);
      end
      else
      begin
        // Window is a VCL control
        // Allow non-windowed child controls to handle the message
        ProcessControl(WinControl.ControlAtPos(
          WinControl.ScreenToClient(Msg.pt), False), True);
        // Allow native controls to handle the message
        if Message.Result = 0 then
        begin
          WinWParamNeeded;
          WinControl.DefaultHandler(Message);
        end;
        // Allow windowed VCL controls to handle the message
        if not ((MainFormPreview and (WinControl = Application.MainForm)) or
          (ActiveFormPreview and (WinControl = Screen.ActiveCustomForm)) or
          (ActiveControlPreview and (WinControl = Screen.ActiveControl))) then
        begin
          VclWParamNeeded;
          ProcessControl(WinControl, True);
        end;
      end;
    end;
    // Bypass default VCL wheel handling?
    Handled := ((Message.Result <> 0) and not VclHandlingAfterHandled) or
      ((Message.Result = 0) and not VclHandlingAfterUnhandled);
    // Modify message destination for current process
    if (not Handled) and (Window <> 0) and
      (GetWindowThreadProcessID(Window, OwningProcess) <> 0) and
      (OwningProcess = GetCurrentProcessId) then
    begin
      Msg.hwnd := Window;
    end;
    if CancelApplicationEvents then
      CancelDispatch;
  end;
end;

constructor TWheelInterceptor.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  OnMessage := ApplicationMessage;
end;

procedure Activate;
begin
  WheelInterceptor.Activate;
end;

{ TWheelMsgSettings }

procedure TWheelMsgSettings.RegisterMouseWheelHandler(
  ControlClass: TControlClass);
begin
  ControlClassList.Add(ControlClass);
end;

initialization
  ControlClassList := TClassList.Create;
  WheelInterceptor := TWheelInterceptor.Create(Application);

finalization
  ControlClassList.Free;

end.

Отказ от ответственности:

Этот код намеренно не прокручивает что-либо, он только подготавливает маршрутизацию сообщений для событий VCL OnMouseWheel*, чтобы получить надлежащую возможность быть запущенным. Этот код не тестируется на сторонних элементах управления. Когда VclHandlingAfterHandled или VclHandlingAfterUnhandled установлено True, события мыши могут запускаться дважды. В этом посте я сделал несколько заявлений и решил, что в VCL есть три ошибки, но все они основаны на изучении документации и тестировании. Пожалуйста, проверьте это устройство и прокомментируйте выводы и ошибки. Я прошу прощения за этот довольно длинный ответ; У меня просто нет блога.

1) Наименование дерзкое взято из Одиссея Ки

2) См. Мой Центральный отчет об ошибках качества # 135258

3) См. Мой Центральный отчет об ошибках качества # 135305

7 голосов
/ 12 февраля 2010

Переопределить событие TApplication.OnMessage (или создать Компонент TApplicationEvents) и перенаправить сообщение WM_MOUSEWHEEL в обработчик события:

procedure TMyForm.AppEventsMessage(var Msg: tagMSG;
  var Handled: Boolean);
var
  Pt: TPoint;
  C: TWinControl;
begin
  if Msg.message = WM_MOUSEWHEEL then begin
    Pt.X := SmallInt(Msg.lParam);
    Pt.Y := SmallInt(Msg.lParam shr 16);
    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;

Здесь все работает нормально, хотя вы можете добавить некоторую защиту, чтобы сохранить от повторения, если случится что-то неожиданное.

2 голосов
/ 24 февраля 2014

Это решение, которое я использовал:

  1. Добавьте amMouseWheel к условию использования раздела реализации модуля вашей формы после forms unit:

    unit MyUnit;
    
    interface
    
    uses
      Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
      // Fix and util for mouse wheel
      amMouseWheel;
    ...
    
  2. Сохраните следующий код в amMouseWheel.pas:

    unit amMouseWheel;
    
    // -----------------------------------------------------------------------------
    // The original author is Anders Melander, anders@melander.dk, http://melander.dk
    // Copyright © 2008 Anders Melander
    // -----------------------------------------------------------------------------
    // License:
    // Creative Commons Attribution-Share Alike 3.0 Unported
    // http://creativecommons.org/licenses/by-sa/3.0/
    // -----------------------------------------------------------------------------
    
    interface
    
    uses
      Forms,
      Messages,
      Classes,
      Controls,
      Windows;
    
    //------------------------------------------------------------------------------
    //
    //      TForm work around for mouse wheel messages
    //
    //------------------------------------------------------------------------------
    // The purpose of this class is to enable mouse wheel messages on controls
    // that doesn't have the focus.
    //
    // To scroll with the mouse just hover the mouse over the target control and
    // scroll the mouse wheel.
    //------------------------------------------------------------------------------
    type
      TForm = class(Forms.TForm)
      public
        procedure MouseWheelHandler(var Msg: TMessage); override;
      end;
    
    //------------------------------------------------------------------------------
    //
    //      Generic control work around for mouse wheel messages
    //
    //------------------------------------------------------------------------------
    // Call this function from a control's (e.g. a TFrame) DoMouseWheel method like
    // this:
    //
    // function TMyFrame.DoMouseWheel(Shift: TShiftState; WheelDelta: Integer;
    //   MousePos: TPoint): Boolean;
    // begin
    //   Result := ControlDoMouseWheel(Self, Shift, WheelDelta, MousePos) or inherited;
    // end;
    //
    //------------------------------------------------------------------------------
    function ControlDoMouseWheel(Control: TControl; Shift: TShiftState;
      WheelDelta: Integer; MousePos: TPoint): Boolean;
    
    implementation
    
    uses
      Types;
    
    procedure TForm.MouseWheelHandler(var Msg: TMessage);
    var
      Target: TControl;
    begin
      // Find the control under the mouse
      Target := FindDragTarget(SmallPointToPoint(TCMMouseWheel(Msg).Pos), False);
    
      while (Target <> nil) do
      begin
        // If the target control is the focused control then we abort as the focused
        // control is the originator of the call to this method.
        if (Target = Self) or ((Target is TWinControl) and (TWinControl(Target).Focused)) then
        begin
          Target := nil;
          break;
        end;
    
        // Let the target control process the scroll. If the control doesn't handle
        // the scroll then...
        Msg.Result := Target.Perform(CM_MOUSEWHEEL, Msg.WParam, Msg.LParam);
        if (Msg.Result <> 0) then
          break;
    
        // ...let the target's parent give it a go instead.
        Target := Target.Parent;
      end;
    
      // Fall back to the default processing if none of the controls under the mouse
      // could handle the scroll.
      if (Target = nil) then
        inherited;
    end;
    
    type
      TControlCracker = class(TControl);
    
    function ControlDoMouseWheel(Control: TControl; Shift: TShiftState;
      WheelDelta: Integer; MousePos: TPoint): Boolean;
    var
      Target: TControl;
    begin
      (*
      ** The purpose of this method is to enable mouse wheel messages on controls
      ** that doesn't have the focus.
      **
      ** To scroll with the mouse just hover the mouse over the target control and
      ** scroll the mouse wheel.
      *)
      Result := False;
    
      // Find the control under the mouse
      Target := FindDragTarget(MousePos, False);
    
      while (not Result) and (Target <> nil) do
      begin
        // If the target control is the focused control then we abort as the focused
        // control is the originator of the call to this method.
        if (Target = Control) or ((Target is TWinControl) and (TWinControl(Target).Focused)) then
          break;
    
        // Let the target control process the scroll. If the control doesn't handle
        // the scroll then...
        Result := TControlCracker(Target).DoMouseWheel(Shift, WheelDelta, MousePos);
    
        // ...let the target's parent give it a go instead.
        Target := Target.Parent;
      end;
    end;
    
    end.
    
2 голосов
/ 12 февраля 2010

Вы можете найти эту статью полезной: отправьте сообщение прокрутки вниз в список с помощью колесика мыши, но список не имеет фокуса [1] , он написан на C #, но преобразование в Delphi не должно быть слишком большой проблемой. Он использует крючки для достижения желаемого эффекта.

Чтобы выяснить, над каким компонентом находится мышь в данный момент, вы можете использовать функцию FindVCLWindow, пример которой можно найти в этой статье: Получить элемент управления под мышью в приложении Delphi [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

0 голосов
/ 05 ноября 2016

Только для использования с элементами управления DevExpress

Работает на XE3. Это не было проверено на других версиях.

procedure TMainForm.DoApplicationMessage(var AMsg: TMsg; var AHandled: Boolean);
var
  LControl: TWinControl;
  LMessage: TMessage;
begin

  if AMsg.message <> WM_MOUSEWHEEL then
    Exit;

  LControl := FindVCLWindow(AMsg.pt);
  if not Assigned(LControl) then
    Exit;

  LMessage.WParam := AMsg.wParam;
  // see TControl.WMMouseWheel
  TCMMouseWheel(LMessage).ShiftState := KeysToShiftState(TWMMouseWheel(LMessage).Keys);
  LControl.Perform(CM_MOUSEWHEEL, LMessage.WParam, AMsg.lParam);

  AHandled := True;

end;

если вы не используете элементы управления DevExpress, тогда Выполните -> SendMessage

SendMessage(LControl.Handle, AMsg.message, AMsg.WParam, AMsg.lParam);
0 голосов
/ 07 декабря 2015

У меня была та же проблема, и я решил ее небольшим взломом, но она работает.

Я не хотел возиться с сообщениями и решил просто вызвать метод DoMouseWheel для контроля, который мне нужен. Хак в том, что DoMouseWheel является защищенным методом и поэтому недоступен из файла модуля формы, поэтому я определил свой класс в модуле формы:

TControlHack = class(TControl)
end;  //just to call DoMouseWheel

Затем я написал обработчик события TForm1.onMouseWheel:

procedure TForm1.FormMouseWheel(Sender: TObject; Shift: TShiftState;
    WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
var i: Integer;
    c: TControlHack;
begin
  for i:=0 to ComponentCount-1 do
    if Components[i] is TControl then begin
      c:=TControlHack(Components[i]);
      if PtInRect(c.ClientRect,c.ScreenToClient(MousePos)) then 
      begin
        Handled:=c.DoMouseWheel(shift,WheelDelta,MousePos);
        if Handled then break;
      end;
   end;
end;

Как видите, он ищет все элементы управления в форме, а не только непосредственных детей, и оказывается для поиска от родителей к детям. Было бы лучше (но больше кода) сделать рекурсивный поиск у детей, но приведенный выше код работает просто отлично.

Чтобы заставить только один элемент управления реагировать на событие mousewheel, вы должны всегда устанавливать Handled: = true, когда оно реализовано. Если, например, у вас есть панель списка внутри панели, то панель сначала выполнит DoMouseWheel, а если она не обработала событие, то будет запущен listbox.DoMouseWheel. Если никакой элемент управления под курсором мыши не обрабатывает DoMouseWheel, сфокусированный элемент управления будет казаться довольно адекватным.

0 голосов
/ 12 февраля 2010

В событии OnMouseEnter для каждого прокручиваемого элемента управления добавьте соответствующий вызов SetFocus

Так для ListBox1:

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

Достигает ли это желаемого эффекта?

...