Question

I use Delphi 7 and I have a TFrame (hosted by a TForm) with three panels that span over the whole surface, in a "upside down T" layout. The panels should be resizeable, so I could use 2 splitters, but I want to give a better user experience: I'd like to have a single "size grip" in the T junction. This "handle" should appear only when the user hovers the junction area.

So here is my question: what is the best way to have a control show on top of any other on mouse move? TFrame.OnMouseMove don't get called (obviously) because there are the panels inside and possibly any sort of other controls inside them. I also strongly want to keep all the code inside the frame.

I see 2 solutions:

  1. Install a local Mouse Hook and go with it. But there could be some performance issues (or not?)
  2. Handle TApplication.OnMessage inside the frame, but adding some other code in order to simulate a "chain" of event handlers. This is because other parts of the application could need to handle TApplication.OnMessage for their own purposes.

Any other idea?

Thank you

Was it helpful?

Solution

To make a mouse move event notifier for the whole frame, no matter which child control is hovered, you can write a handler for the WM_SETCURSOR message as I've learnt from TOndrej in this post. From such event handler you can then determine which control is hovered and bring it to front.

Please note, I have done quite commonly used mistake here. The GetMessagePos result must not be read this way. It's even explicitly mentioned in docs. I don't have Windows SDK to see the MAKEPOINTS macro, so I'll fix this later:

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;

OTHER TIPS

I'll post this self-answer just because it works and it could be useful in some cases, but I marked TLama's as the best answer.
This is the solution 2) of the question:

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;
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top