tags:

views:

157

answers:

5

Hi everyone,

I am using the OnMouseMove event to detect when the mouse pointer is over my TPanel, is there a way to know when the mouse pointer had moved away from it?

I need the panel to change colour when the mouse pointer is over it and return to its original colour once it moved away from it?

I am using Delphi 6 by the way.

Please help.

Best Regards.

+6  A: 

You can use OnMouseEnter/OnMouseLeave event pair to detect mouse

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;

I can't test the following code in Delphi 6, but I hope it is OK

Updated

TrackMouseEvent code added - thanks to Sertac Akyuz answer

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.
Serg
Hang on guys... I only have OnMouseDown, OnMouseMove, OnMouseUp and I don't see OnMouseEnter nor OnMouseLeave nor OnMouseOut... I am using delphi 6... am I out of luck or there is other way?
Snackmoore
If I remember correctly, OnMouseEnter and OnMouseLeave did not exist in Delphi 6.
Andreas Rejbrand
AFAIK `WM_MOUSELEAVE` is not posted if the window is not specified in a `TrackMouseEvent`.
Sertac Akyuz
+2  A: 

OnMouseLeave. Beware that you also need to see if the mouse left the whole application as I've had OnMouseLeave not fire when the panel was on the edge of the form and I went off the form.

Loren Pechtel
+1 for mentioning "you also need to see if the mouse left the whole application".
Jeroen Pluimers
+3  A: 

If you don't have OnMouseEnter and OnMouseLeave then use OnMouseMove and capture the mouse to your panel. Capturing the mouse is slightly more work but the effects are better.

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;
Cosmin Prund
This is good, but you must monitor WM_CANCELMODE too, which is sent if the mouse is captured and the focus is forced away.
mj2008
@mj2008, you're right, it's just that WM_CANCELMODE is sent to the window that previously held the capture, so it's sent to the Panel. Unless the OP is willing to sub-class TPanel, there's no easy way to handle WM_CANCELMODE. None the less I should have mentioned it.
Cosmin Prund
@Cosmin: But there *is* an (relatively) easy way to hook into other controls' message handling without actually subclassing them, by setting their `WindowProc` property.
mghie
+1  A: 

Create your own component derived from the TCustomPanel (or TPanel), since Delphi 6 doesn't have the MouseEnter and MouseLeave events on by default, you can add them yourself. Add this to the private section of your declaration section:

procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;
procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;

Afterwards it is all about handling the mouseenter and mouseleave events:

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; 

Hope this helps. If the color doesn't show up add this to your components constructor:

ControlStyle := ControlStyle - [csParentBackground] + [csOpaque];
TommyA
The problem with CM_MOUSE.. messages is that they might not fire with fast mouse movements, especially likely if a panel is near the border of a form I think..
Sertac Akyuz
+3  A: 

Yet another solution, using TrackMouseEvent to receive 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;
Sertac Akyuz
Since the original question does not imply creation of a TPanel descendant class probably a final solutuion is to handle WM_MOUSELEAVE in TPanel.WindowProc
Serg
@Serg - Ok, but your answer already shows how it is done :-).
Sertac Akyuz
Thanks a lot... and thank you very much to everyone else for the different solutions.
Snackmoore