Title: Handling mouse messages in overlapped TGraphicControl descendants.
Question: This is the problem I've encountered devloping small CAD application. Draggable elements are implemented as TGraphicControl descendants. They do not occupy all the client rectangle, but if two components overlap, and a user clicks on the visible but overlapped part of the lowest control, mouse message still goes to the topmost.
Answer:
The reason is that when parent TWinControl handles incoming messages in its WndProc, this message is posted to the correspondiong child control by calling IsControlMouseMsg:
procedure TWinControl.WndProc(var Message: TMessage);
...
case Message.Msg of
...
WM_MOUSEFIRST..WM_MOUSELAST:
if IsControlMouseMsg(TWMMouse(Message)) then
begin
if Message.Result = 0 then
DefWindowProc(Handle, Message.Msg, Message.wParam, Message.lParam);
Exit;
end;
...
end;
IsControlMouseMsg tries to find a corresponding child control by calling ControlAtPos. ControlAtPos browses all the child controls and checks if message belongs to control by calling GetControlAtPos function.
function GetControlAtPos(AControl: TControl): Boolean;
begin
with AControl do
begin
P := Point(Pos.X - Left, Pos.Y - Top);
Result := PtInRect(ClientRect, P) and
((csDesigning in ComponentState) and (Visible or
not (csNoDesignVisible in ControlStyle)) or
(Visible and (Enabled or AllowDisabled) and
(Perform(CM_HITTEST, 0, Longint(PointToSmallPoint(P))) 0)));
if Result then
vControl := AControl;
end;
end;
As you may see from the code of the GetControlAtPos, the function checks if control's clientrect contains the point and also performs a CM_HITTEST message on the control. By default TControl handles this message returning the result that indicates that hit was in control's client area regardless the message coordinates:
procedure TControl.CMHitTest(var Message: TCMHitTest);
begin
Message.Result := HTCLIENT;
end;
In our case we got two overlapped controls and the problem is that message ALWAYS goes to the topmost control if its coordinates are in control's client rectangle. By overriding handling of the CM_HITTEST message we may provide additional checking.
For example, if we have calculated a region that our control's graphics occupy, we may implement this check as follows:
TMyControl = class(TGraphicControl)
...
private
procedure CMHitTest(var Message: TCMHitTest); message CM_HITTEST;
...
end;
...
procedure TMyControl.CMHitTest(var Message: TCMHitTest);
begin
with Message do
if PtInRegion(Region, XPos, YPos) then Result := HTCLIENT
else Result := HTNOWHERE;
end;
In this case a mouse message will be passed to the control only if its
coordinates hits region defined by Region variable.
p.s.
This article demonstrates a method for TGraphicControl descendants. If you inherit from TWinControl, you should use SetWindowRGN etc.