如何在Delphi 6中检测鼠标移出TPanel?

6

我正在使用OnMouseMove事件来检测鼠标指针是否悬停在我的TPanel上,有没有办法知道鼠标指针何时移开了它?

当鼠标指针悬停在面板上时,我需要将面板的颜色更改,并在移开后返回到原始颜色。

顺便说一下,我正在使用Delphi 6。

请帮忙解决。

最好的问候。

6个回答

8
你可以使用OnMouseEnter/OnMouseLeave事件对鼠标进行检测。
procedure TForm1.Panel1MouseEnter(Sender: TObject);
begin
  Panel1.Caption:= 'IN';
  Panel1.Color:= clBlue;
end;

procedure TForm1.Panel1MouseLeave(Sender: TObject);
begin
  Panel1.Caption:= 'OUT';
  Panel1.Color:= clWhite;
end;

我无法在Delphi 6中测试以下代码,但希望一切顺利。

更新内容

感谢Sertac Akyuz提供的答案,已添加TrackMouseEvent代码。

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, ExtCtrls;

type
  TForm1 = class(TForm)
    Panel1: TPanel;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure Panel1MouseMove(Sender: TObject; Shift: TShiftState; X,
      Y: Integer);
  private
    { Private declarations }
    FOldWndProc: TWndMethod;
    FMouseInPanel: Boolean;
    procedure PanelWndProc(var Message: TMessage);
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.FormCreate(Sender: TObject);
begin
  FOldWndProc:= Panel1.WindowProc;
  Panel1.WindowProc:= PanelWndProc;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  Panel1.WindowProc:= FOldWndProc;
end;

procedure TForm1.Panel1MouseMove(Sender: TObject; Shift: TShiftState; X,
  Y: Integer);
var
  mEvnt: TTrackMouseEvent;
begin
  if not FMouseInPanel then begin
    mEvnt.cbSize := SizeOf(mEvnt);
    mEvnt.dwFlags := TME_LEAVE;
    mEvnt.hwndTrack := Panel1.Handle;
    TrackMouseEvent(mEvnt);
    Panel1.Caption:= 'IN';
    FMouseInPanel:= True;
  end;
end;

// if not defined in Delphi 6, WM_MOUSELEAVE = $02A3
procedure TForm1.PanelWndProc(var Message: TMessage);
begin
  if Message.Msg = WM_MOUSELEAVE then begin
    Panel1.Caption:= 'OUT';
    FMouseInPanel:= False;
  end;
  FOldWndProc(Message);
end;

end.

等一下,伙计们...我只有OnMouseDown、OnMouseMove和OnMouseUp,但我没有看到OnMouseEnter、OnMouseLeave或OnMouseOut...我正在使用Delphi 6...我是运气不好还是有其他方法? - Snackmoore
如果我没记错的话,在 Delphi 6 中是不存在 OnMouseEnter 和 OnMouseLeave 的。 - Andreas Rejbrand
2
据我所知,如果在 TrackMouseEvent 中没有指定窗口,则不会发布 WM_MOUSELEAVE - Sertac Akyuz

6
另一种解决方案是使用TrackMouseEvent接收WM_MOUSELEAVE事件;
type
  TMyPanel = class(TPanel)
  private
    FMouseTracking: Boolean;
    FOnMouseLeave: TNotifyEvent;
    procedure WMMouseLeave(var Msg: TMessage); message WM_MOUSELEAVE;
  protected
    procedure MouseMove(Shift: TShiftState; X, Y: Integer); override;
  published
    property OnMouseLeave: TNotifyEvent read FOnMouseLeave write FOnMouseLeave;
  end;

procedure TMyPanel.MouseMove(Shift: TShiftState; X, Y: Integer);
var
  mEvnt: TTrackMouseEvent;
begin
  inherited;
  if not FMouseTracking then begin
    mEvnt.cbSize := SizeOf(mEvnt);
    mEvnt.dwFlags := TME_LEAVE;
    mEvnt.hwndTrack := Handle;
    TrackMouseEvent(mEvnt);
    FMouseTracking := True;
  end;
end;

procedure TMyPanel.WMMouseLeave(var Msg: TMessage);
begin
  Msg.Result := 0;
  FMouseTracking := False;
  if Assigned(FOnMouseLeave) then
    FOnMouseLeave(Self);
end;

由于原始问题并未暗示创建TPanel后代类,因此最终的解决方案可能是在TPanel.WindowProc中处理WM_MOUSELEAVE。 - kludg
@Serg - 好的,但是你的回答已经展示了如何完成它 :-)。 - Sertac Akyuz
非常感谢...也非常感谢其他人提供的不同解决方案。 - Snackmoore

4

如果您没有OnMouseEnter和OnMouseLeave,则可以使用OnMouseMove并将鼠标捕获到面板上。 捕获鼠标需要稍微多做一些工作,但效果更好。

procedure Form1.Panel1MouseMove(Sender:TObject; Shift:TShiftState; X,Y:Integer);
begin
  if (X >= 0) and (Y >= 0) and (X < Panel1.Width) and (Y < Panel1.Height) then
    begin
      // Movement within the panel
      if GetCapture <> Panel1.Handle then
      begin
        // The mouse just moved over the panel. Do your "on enter" stuff
        // over here.
        SetCapture(Panel1.Handle); // Capture the mouse so we'll receive
                                   // mouse move messages even if the cursor
                                   // is no longer over our panel.
      end;
    end
  else
    begin
      // Movement outside the panel! This is possible because I've previously
      // captured the mouse.
      // Do your "move out" stuff over here.
      ReleaseCapture; // release mouse capture
    end;
end;

2
这很好,但你也必须监视WM_CANCELMODE,如果鼠标被捕获并强制离开焦点,就会发送该消息。 - mj2008
@mj2008,你说得对,只是 WM_CANCELMODE 消息被发送到之前拥有捕获的窗口,所以它被发送到了 Panel。除非 OP 愿意子类化 TPanel,否则没有简单的方法来处理 WM_CANCELMODE。尽管如此,我应该提到它。 - Cosmin Prund
1
但是有一种(相对)简单的方法可以连接到其他控件的消息处理,而不必实际子类化它们,那就是设置它们的“WindowProc”属性。 - mghie

2

OnMouseLeave。请注意,您还需要查看鼠标是否离开了整个应用程序,因为当面板位于表单边缘且我离开表单时,OnMouseLeave可能不会触发。


+1 如果提到“你还需要看一下鼠标是否离开了整个应用程序”。 - Jeroen Wiert Pluimers

1
创建自己的组件,从 TCustomPanel (或 TPanel) 派生,因为 Delphi 6 默认不包含 MouseEnter 和 MouseLeave 事件,可以自行添加。将以下内容添加到声明部分中的私有部分:
procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;
procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;

接下来就是处理mouseenter和mouseleave事件的事情了:

procedure TMyPanel.CMMouseEnter(var msg: TMessage);
begin
  inherited;
  Color := clBlue;
  { Do Whatever }
end;

procedure TMyPanel.CMMouseLeave(var msg: TMessage);
begin
  inherited;
  Color := clRed;
  { Do Whatever }
end; 

希望这可以帮到你。如果颜色没有显示出来,请将以下内容添加到您的组件构造函数中:

ControlStyle := ControlStyle - [csParentBackground] + [csOpaque];

CM_MOUSE 相关消息的问题在于,它们可能无法跟上快速的鼠标移动,尤其是当面板靠近表单边缘时。 - Sertac Akyuz

1

感谢您的帮助。对我来说非常有效,我从原始的bitbtn派生出一个新的bitbtn控件,并使用Delphi 7实现了mouseenter和mouseleave事件。以下是我的代码:

TBitBtnPanel = class(TBitBtn) private { 私有声明 } FOnMouseLeave: TNotifyEvent; FOnMouseEnter: TNotifyEvent; FActivateMouseLeave: boolean; procedure CMMouseEnter(var Msg: TMessage); message CM_MOUSEENTER; procedure CMMouseLeave(var Msg: TMessage); message CM_MOUSELEAVE; protected { 受保护的声明 } public { 公共声明 } constructor Create(AOwner: TComponent); override; published { 发布声明 } property OnMouseLeave: TNotifyEvent read FOnMouseLeave write FOnMouseLeave; property OnMouseEnter: TNotifyEvent read FOnMouseEnter write FOnMouseEnter; end;
{ TBitBtnPanel } procedure TBitBtnPanel.CMMouseEnter(var Msg: TMessage); begin Msg.Result := WM_CANCELMODE; FActivateMouseLeave:=True; if Assigned(FOnMouseEnter) then FOnMouseEnter(Self); end; procedure TBitBtnPanel.CMMouseLeave(var Msg: TMessage); begin if (FActivateMouseLeave) then begin Msg.Result := WM_CANCELMODE; FActivateMouseLeave:=False; if Assigned(FOnMouseLeave) then FOnMouseLeave(Self); end; end;
constructor TBitBtnPanel.Create(AOwner: TComponent); begin FActivateMouseLeave:=True; inherited; end;

雨果·巴罗斯 - 巴西


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接