user1175743
user1175743

Reputation:

How to use FindVCLWindow on a TGraphicControl that is underneath a TPaintBox?

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

Answers (2)

NGLN
NGLN

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

Deltics
Deltics

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

Related Questions