4

I have created a delphi component which descends from TGraphicControl. Is it possible to add support for mouse wheels?

--- Edit ---

I've exposed the MouseWheel events as shown below but they aren't called.

TMyComponent = class(TGraphicControl)
published
  property OnMouseWheel;
  property OnMouseWheelDown;
  property OnMouseWheelUp;
end;

--- Edit ---

As suggested below, I've tried to trap the WM_MOUSEWHEEL and CM_MOUSEWHEEL messages, but it doesn't seem to work. However I've managed to trap the CM_MOUSEENTER message. I don't understand why i can trap one type of message, but not the other.

4

6 回答 6

6

由于几个 VCL 构造(无论它们是故意的实现选择还是可能是错误1),我留在中间)只有焦点控件及其所有父级收到鼠标滚轮消息,以及捕获鼠标和的控件有一个专注的父母。

TControl级别上,可以强制执行后一个条件。CM_MOUSEENTER当鼠标进入控件的客户空间时,控件会收到来自 VCL 的消息。要强制它接收鼠标滚轮消息,请关注其父级并在该消息处理程序中捕获鼠标:

procedure TWheelControl.CMMouseEnter(var Message: TMessage);
begin
  FPrevFocusWindow := SetFocus(Parent.Handle);
  MouseCapture := True;
  inherited;
end;

但这些设置必须在鼠标退出控件时撤消。由于控件现在正在捕获鼠标,CM_MOUSELEAVE因此它没有接收到鼠标,因此您必须手动检查这一点,例如在WM_MOUSEMOVE消息处理程序中:

procedure TWheelControl.WMMouseMove(var Message: TWMMouseMove);
begin
  if MouseCapture and
    not PtInRect(ClientRect, SmallPointToPoint(Message.Pos)) then
  begin
    MouseCapture := False;
    SetFocus(FPrevFocusWindow);
  end;
  inherited;
end;

现在,您会假设控件接收到的滚轮消息随后会触发OnMouseWheel,OnMouseWheelDownOnMouseWheelUp事件。但是不,还需要一次干预。消息进入控件,MouseWheelHandler其中恰好将消息传递给窗体或活动控件。要触发这些事件,CM_MOUSEWHEEL应发送控制消息:

procedure TWheelControl.MouseWheelHandler(var Message: TMessage);
begin
  Message.Result := Perform(CM_MOUSEWHEEL, Message.WParam, Message.LParam);
  if Message.Result = 0 then
    inherited MouseWheelHandler(Message);
end;

这导致了这个最终代码:

unit WheelControl;

interface

uses
  System.Classes, Winapi.Windows, Winapi.Messages, Vcl.Controls;

type
  TWheelControl = class(TGraphicControl)
  private
    FPrevFocusWindow: HWND;
    procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;
    procedure WMMouseMove(var Message: TWMMouseMove); message WM_MOUSEMOVE;
  public
    procedure MouseWheelHandler(var Message: TMessage); override;
  published
    property OnMouseWheel;
    property OnMouseWheelDown;
    property OnMouseWheelUp;
  end;

implementation

{ TWheelControl }

procedure TWheelControl.CMMouseEnter(var Message: TMessage);
begin
  FPrevFocusWindow := SetFocus(Parent.Handle);
  MouseCapture := True;
  inherited;
end;

procedure TWheelControl.MouseWheelHandler(var Message: TMessage);
begin
  Message.Result := Perform(CM_MOUSEWHEEL, Message.WParam, Message.LParam);
  if Message.Result = 0 then
    inherited MouseWheelHandler(Message);
end;

procedure TWheelControl.WMMouseMove(var Message: TWMMouseMove);
begin
  if MouseCapture and
    not PtInRect(ClientRect, SmallPointToPoint(Message.Pos)) then
  begin
    MouseCapture := False;
    SetFocus(FPrevFocusWindow);
  end;
  inherited;
end;

end.

如您所见,这会更改焦点控件,这违反了基于 Windows 的桌面应用程序的用户体验指南,并且当焦点控件具有明确的焦点状态时,可能会导致视觉干扰。

作为替代方案,您可以通过覆盖Application.OnMessage并在那里处理它来绕过所有默认的 VCL 鼠标滚轮处理。这可以按如下方式完成:

unit WheelControl2;

interface

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

type
  TWheelControl = class(TGraphicControl)
  published
    property OnMouseWheel;
    property OnMouseWheelDown;
    property OnMouseWheelUp;
  end;

implementation

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

procedure TWheelInterceptor.ApplicationMessage(var Msg: tagMSG;
  var Handled: Boolean);
var
  Window: HWND;
  WinControl: TWinControl;
  Control: TControl;
  Message: TMessage;
begin
  if Msg.message = WM_MOUSEWHEEL then
  begin
     Window := WindowFromPoint(Msg.pt);
     if Window <> 0 then
     begin
       WinControl := FindControl(Window);
       if WinControl <> nil then
       begin
         Control := WinControl.ControlAtPos(WinControl.ScreenToClient(Msg.pt),
           False);
         if Control <> nil then
         begin
           Message.WParam := Msg.wParam;
           Message.LParam := Msg.lParam;
           TCMMouseWheel(Message).ShiftState :=
             KeysToShiftState(TWMMouseWheel(Message).Keys);
           Message.Result := Control.Perform(CM_MOUSEWHEEL, Message.WParam,
             Message.LParam);
           Handled := Message.Result <> 0;
         end;
       end;
     end;
  end;
end;

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

initialization
  TWheelInterceptor.Create(Application);

end.

注意将事件的Handled参数设置为,否则焦点控件也会滚动。MouseWheel*True

另请参阅如何将鼠标滚轮输入控制在光标下而不是聚焦?有关鼠标滚轮处理的更多背景信息和更通用的解决方案。

1)请参阅Quality Central 错误报告 #135258Quality Central 错误报告 #135305

于 2015-12-25T13:29:21.217 回答
3

TGraphicControl继承自TControl,它已经具有鼠标滚轮支持。查看wm_MouseWheel消息、DoMouseWheelDoMouseWheelDownDoMouseWheelUpMouseWheelHandler方法以及WheelAccumulator属性。

于 2009-01-19T05:38:58.373 回答
1

我也有同样的问题。还没有找到解决方案的运气,但也许这会有所帮助:

我怀疑另一个组件正在调用 Win API 方法 SetCapture,根据 API 帮助:

“SetCapture 函数将鼠标捕获设置为属于当前线程的指定窗口。一旦窗口捕获了鼠标,所有鼠标输入都将定向到该窗口,无论光标是否在该窗口的边界内。只有一个一次窗口可以捕获鼠标。”

作为新用户,我无法发布指向完整主题的链接。

已编辑

如果您将组件创建为 TCustomControl 的后代,则可以如下所述解决您的问题:

  1. 使用 OnMouseEnter 事件来检测鼠标何时进入您的组件。
  2. 在 OnMouseEnter 调用 SetFocus 方法使您的组件聚焦。现在您的组件可以接收 WM_MOUSEWHEEL 消息
于 2009-05-27T12:54:49.863 回答
1

只有 TWinControl 后代可以接收鼠标滚轮消息。TGraphicControl 不是基于窗口的控件,因此不能。如果 VCL 将消息路由到 TGraphicControl,它可以工作,但显然没有。您可以从 TCustomControl 下降,然后它会起作用。

于 2009-01-19T12:09:40.770 回答
0

捕获 WM_MOUSEWHEEL 消息。

于 2009-01-19T05:36:20.533 回答
0

我正在使用以下技术,我订阅了表单事件MouseWheelUp()并在其中,我搜索带有WindowFromPoint()(win32 api 函数)的小部件Vcl.Controls.FindControl(),然后我检查我是否得到了正确的 UI 小部件,当我不检查ActiveControl(当前具有焦点的表单上的小部件)。

此技术确保当小部件位于光标下或不在光标下但具有焦点时,鼠标滚轮向上/向下事件起作用。

下面的示例对鼠标滚轮向上事件作出反应,并在光标下或有焦点TSpinEdit时递增。TSpinEdit

function TFormOptionsDialog.FindSpinEdit(const AMousePos: TPoint): TSpinEdit;
var
  LWindow: HWND;
  LWinControl: TWinControl;
begin
  Result := nil;

  LWindow := WindowFromPoint(AMousePos);
  if LWindow = 0 then
    Exit;

  LWinControl := FindControl(LWindow);
  if LWinControl = nil then
    Exit;

  if LWinControl is TSpinEdit then
    Exit(LWinControl as TSpinEdit);

  if LWinControl.Parent is TSpinEdit then
    Exit(LWinControl.Parent as TSpinEdit);

  if ActiveControl is TSpinEdit then
    Exit(ActiveControl as TSpinEdit);
end;

procedure TFormOptionsDialog.FormMouseWheelUp(Sender: TObject; Shift: TShiftState; MousePos: TPoint;
  var Handled: Boolean);
var
  LSpinEdit: TSpinEdit;
begin
  LSpinEdit := FindSpinEdit(MousePos);
  if LSpinEdit = nil then
    Exit;

  LSpinEdit.Value := LSpinEdit.Value + LSpinEdit.Increment;
  Handled := True;
end;
于 2020-09-02T13:06:33.347 回答