Reputation:
I am trying to use FindVCLWindow
on a TGraphicControl
component such as TLabel and TImage so that I can return their names for example in a Label or Statusbar, but I am facing a few problems.
Problem 1
The first problem is that FindVCLWindow
only works for TWinControl
and not for descendants of TGraphicControl
, so I tried messing around with the following which appears to work:
function FindVCLGraphicWindow(const Pos: TPoint): TGraphicControl;
var
Window: TWinControl;
Ctrl: TControl;
begin
Result := nil;
Window := FindVCLWindow(Pos);
if Window <> nil then
begin
Ctrl := Window.ControlAtPos(Window.ScreenToClient(Pos), True, True, True);
if Ctrl is TGraphicControl then
begin
Result := TGraphicControl(Ctrl);
end;
end;
end;
I guess that is one problem down as it appears to work, but maybe there is a better solution?
Problem 2
The biggest problem I have is that the labels and images I need the above function to work on, are underneath a TPaintBox and as such the label or image component does not seem to receive or respond to mouse movements. In otherwords the function does not work unless the label or image is at the top (ie BringToFront).
I remember a while back learning from another question I had posted here that by setting the TPaintbox to Enabled := False
will allow underlying controls to receive mouse messages etc.
However, using the above function always returns nil/false as it "cannot see" the graphic controls underneath the painbox.
So my main question is, how can I use a function like FindVCLWindow
on a TGraphicControl
that is behind a TPaintBox?
For example, if the following controls were inside a panel:
Image1.SendToBack;
Image2.SendToBack;
Label1.SendToBack;
Label2.SendToBack;
PaintBox1.BringToFront;
The above would only work if they were not behind the paintbox.
Having the images and labels above the paintbox is not an option, they must be behind the paintbox, but by doing so they don't respond to the above function.
So how do I get it to work? The function appears to only see the paintbox, not the underlying images and labels?
Upvotes: 2
Views: 1336
Reputation: 43659
The second parameter of TWinControl.ControlAtPos
specifies whether it allows disabled controls. You have it set True
, thus it will return the disabled PaintBox. Set it False
, and your function will return the Labels and Images in the back of the PaintBox:
function FindVCLGraphicWindow(const Pos: TPoint): TGraphicControl;
var
Window: TWinControl;
Ctrl: TControl;
begin
Result := nil;
Window := FindVCLWindow(Pos);
if Window <> nil then
begin
Ctrl := Window.ControlAtPos(Window.ScreenToClient(Pos), False, True, True);
if Ctrl is TGraphicControl then
begin
Result := TGraphicControl(Ctrl);
end;
end;
end;
Upvotes: 4
Reputation: 23036
It seems that you wish to find all controls at a certain position and then to ignore one/some of those controls based on the context in your application. It seems as though you are trying to use controls underneath a paintbox as some sort of clickable "hotspot".
Your problem is that you are using an approach that involves a function to locate a single control from a given position and this function by necessity must implement it's own rules to determine which one of potentially many such controls it will actually return. The rules in that function do not work for your needs.
The obvious answer then is that you need an approach which allows you to use your rules, not the rules in that other function. i.e. don't use that function. :)
Instead you should simply iterate over all the controls that may satisfy your criteria. That is, controls on the form at the position you require.
To obtain the form you can use the VCL function, as-is, to identify the VCL control at a point and from that determine the form on which that control is placed:
form := GetParentForm(FindVCLWindow(ptPos));
Once you have the form involved you can then simply iterate over the controls to find those at the specific point of interest. In the VCL, the Controls property identifies all the child controls of some parent control, so you cannot use this to find controls that are children of other controls on a form (without some recursion).
But the Components property identifies ALL components owned by some other component. In the VCL, a form owns all components placed on it at design-time (and any others placed at runtime as long as the form is specified as their owner), so you can use this Components property to iterate over all of the components on the form, whether they are visual controls, non-visual, windowed, graphic etc:
var
i: Integer;
comp: TComponent;
ctrl: TControl absolute comp;
begin
result := NIL;
bIsHotspot := FALSE;
form := GetParentForm(FindVCLWindow(ptPos));
if NOT Assigned(form) then // No form = no control to find
EXIT;
ptPos := form.ScreenToClient(ptPos); // pt must be converted to form client co-ords
for i := 0 to Pred(form.ComponentCount) do
begin
comp := form.Components[i];
if NOT (comp is TControl) then // Only interested in visual controls
CONTINUE;
if NOT PtInRect(ctrl.BoundsRect, ptPos) then // Only controls at the required position
CONTINUE;
// Is this a paintbox (= potential hotspot) or some other control ?
if (ctrl is TPaintBox) then
bIsHotspot := TRUE
else
result := ctrl;
// If we have now identified a hotspot AND some other control then we're done
if bIsHotspot and Assigned(result) then
BREAK;
end;
// If we didn't find a hotspot then any other control we may have found is NOT the result
if NOT bIsHotspot then
result := NIL;
end;
This routine iterates over all components on a form, skipping any that are not a visual control or not at the required position.
For the visual controls it then tests for a TPaintbox to determine that the specified position ptPos represents a potential hotspot. If the control is not a hotspot then it is a potential result, assuming that a paintbox is (or has been) also found at that same position.
If it finds both a paintbox and some other control at the specified position, then the result is the non-paintbox control. If it finds both before having iterated over all the components then the routine stops iterating, for efficiency (this means that hotspot controls cannot overlap since this routine finds only the "first" matching other control).
Otherwise the result is NIL.
The above routine is not 100% complete, the last 20% or so is left as an exercise, to incorporate into your code as most appropriate. And you can of course adapt it to implement whatever rules you require to identify controls or components.
Upvotes: 3