【问题标题】:Delphi, How to show an overlayed control on mouse moveDelphi,如何在鼠标移动时显示覆盖控件
【发布时间】:2014-01-23 21:54:44
【问题描述】:

我使用 Delphi 7,我有一个 TFrame(由 TForm 托管),三个面板横跨整个表面,呈“倒 T”形布局。 面板应该是可调整大小的,所以我可以使用 2 个分离器,但我想提供更好的用户体验:我想在 T 形连接处有一个“尺寸夹”。 此“手柄”应仅在用户悬停连接区域时出现。

所以这是我的问题:在鼠标移动时让控件显示在任何其他控件之上的最佳方式是什么? TFrame.OnMouseMove 不要被调用(显然),因为里面有面板,可能还有其他任何类型的控件。 我也强烈希望将所有代码保留在框架内。

我看到了 2 个解决方案:

  1. 安装本地鼠标钩并使用它。但可能有一些 性能问题(或不是?)
  2. 内部处理TApplication.OnMessage 框架,但添加一些其他代码以模拟“链” 的事件处理程序。这是因为应用程序的其他部分 可能需要为自己的目的处理 TApplication.OnMessage。

还有什么想法吗?

谢谢

【问题讨论】:

  • 我不确定我是否解决了您的问题,但您不能为您拥有的每个内部面板处理OnMouseMove 吗?它可以只是一个常见的事件处理程序。
  • @TLama 我明白你的意思,但这些面板将充满其他内部控件,其中一些将在运行时创建或托管,我事先并不知道。
  • 啊,当然。谁需要空面板。傻我:-)

标签: delphi delphi-7 mousemove


【解决方案1】:

要为整个框架制作鼠标移动事件通知程序,无论悬停哪个子控件,您都可以为WM_SETCURSOR 消息编写一个处理程序,正如我在this post 中从TOndrej 那里学到的那样。然后,您可以从此类事件处理程序中确定悬停哪个控件并将其置于前面。

请注意,我在这里犯了很常见的错误。 GetMessagePos 结果不能以这种方式读取。它甚至在文档中明确提到。我没有 Windows SDK 来查看 MAKEPOINTS 宏,所以我稍后会修复这个问题:

type
  TFrame1 = class(TFrame)
    // there are many controls here; just pretend :-)
  private
    procedure WMSetCursor(var Msg: TWMSetCursor); message WM_SETCURSOR;
  end;

implementation

procedure TFrame1.WMSetCursor(var Msg: TWMSetCursor);
var
  MsgPos: DWORD;
  Control: TWinControl;
begin
  inherited;
  MsgPos := GetMessagePos;
  Control := FindVCLWindow(Point(LoWord(MsgPos), HiWord(MsgPos)));
  if Assigned(Control) then
    Control.BringToFront;
end;

【讨论】:

  • 太棒了!我从未调查过 WM_SETCURSOR。在阅读您的答案之前,我找到了一种成功实施我的第一个问题的解决方案 2) 的方法,但这当然是一个更好的解决方案!谢谢。
  • 不客气!好吧,在 TOndrej 回答之前,我不知道WM_SETCURSOR。它的名字隐藏得很好。
【解决方案2】:

我将发布这个自我回答只是因为它有效并且在某些情况下可能有用,但我将 TLama 标记为最佳答案。
这是问题的解决方案2):

TMyFrame = class(TFrame)
  // ...design time stuff...
private
  FMouseHovering: Boolean;
  FPreviousOnAppMessage: TMessageEvent;
  procedure DoOnAppMessage(var Msg: TMsg; var Handled: Boolean);
protected
  procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;
  procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
public
  constructor Create(AOwner: TComponent); override;
  destructor Destroy; override;
end;


implementation

constructor TMyFrame.Create(AOwner: TComponent);
begin
  inherited;
  FMouseHovering := False;
  FPreviousOnAppMessage := Application.OnMessage;
  Application.OnMessage := DoOnAppMessage;
end;

destructor TMyFrame.Destroy;
begin
  Application.OnMessage := FPreviousOnAppMessage;
  inherited;
end;

procedure TRiascoFrame.CMMouseEnter(var Message: TMessage);
begin
  FMouseHovering := True;
end;

procedure TRiascoFrame.CMMouseLeave(var Message: TMessage);
begin
  FMouseHovering := False;
end;

procedure TMyFrame.DoOnAppMessage(var Msg: TMsg; var Handled: Boolean);
begin
  if (Msg.message = WM_MOUSEMOVE) and FMouseHovering then
    DoHandleMouseMove(Msg.hwnd, Integer(LoWord(Msg.lParam)), Integer(HiWord(Msg.lParam)));
  if Assigned(FPreviousOnAppMessage) then
    FPreviousOnAppMessage(Msg, Handled);
end;

procedure TMyFrame.DoHandleMouseMove(hWnd: HWND; X, Y: Integer);
var
  ClientPoint: TPoint;
begin
  ClientPoint := Point(X, Y);
  Windows.ClientToScreen(hwnd, ClientPoint);
  Windows.ScreenToClient(Self.Handle, ClientPoint);
  if PtInRect(ClientRect, ClientPoint) then
  begin
    // ...do something...
  end;
end;

【讨论】:

  • 这样,您将从唯一的全局 TApplication 实例中窃取 OnMessage 事件处理程序。考虑一下如果你有多个这样的框架会发生什么。此框架的每个新实例都会从先前创建的实例中窃取它(不讨论可能想要获取它的其他控件)。
  • 这就是为什么我在构造函数中保存了任何预先存在的处理程序,在我自己的处理程序结束时调用它并在析构函数中恢复它。这样我就创建了一个处理程序链。
  • 我明白了,但一次只能存在一个处理程序,所以这种帧不能超过一个。如果您有多个邮件,则只有最新创建的邮件会处理这些 WM_MOUSEMOVE 消息。
  • 我不明白你的意思:Application.OnMessage 挂钩整个应用程序中的所有消息,因此不存在“只有一帧会处理消息”之类的东西。任何对象“直接”处理此事件都可以看到任何消息,从而将它们传递给链中的下一个处理程序(以前是官方处理程序的对象)。所以,是的,只有一个官方处理程序,但它会调用所有其他处理程序,并且此事件的性质使其成为可能,因为它是应用程序范围的。
  • 对不起。我完全忽略了您将事件分派给原始的OnMessage 处理程序。我的错。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-11-15
  • 1970-01-01
  • 2016-08-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多