2

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?

NGLN
  • 43,011
  • 8
  • 105
  • 200
  • 3
    The same would be valid for FindVCLWindow if you put a bunch of win controls on top of each other, isn't it? Iterate all controls and pick the ones you like. – Sertac Akyuz Dec 23 '14 at 19:17
  • @SertacAkyuz I think I know what you are saying, but I need `FindVCLGraphicWindow` to work by ignoring the paintbox and instead looking under it. Yes I know you can use a for loop to iterate all controls inside a parent but I need the controls that are directly underneath the mouse cursor, and FindVCLWindow I thought would be perfect for my needs except that it cannot get the image and labels under the paintbox. I will keep experimenting on a new project... –  Dec 23 '14 at 20:21

2 Answers2

4

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;
NGLN
  • 43,011
  • 8
  • 105
  • 200
  • This works thanks NGLN! In fact if I recall it might have been your help/asnwer before with the Enabled paintbox tip :) –  Dec 23 '14 at 21:10
  • Thanks, you saved the day again ;) –  Dec 23 '14 at 21:15
  • > *"will return the Labels and Images"* > It will return only one control. If you want all/some of them, you need to iterate. In regard of the accept, I think the question is misleading. – Sertac Akyuz Dec 23 '14 at 21:46
  • @SertacAkyuz I am not sure if the question is misleading or just not explained as best as I could. I simply have some images and labels added at runtime to a panel as the parent, then a paintbox sits on top of those images and labels. Using something like FindVCLWindow I needed to get the control (the image or label) that is directly below the paintbox. My original code and the correction found by NGLN is exactly what I needed. Now I just use `GraphicControl := FindVCLGraphicWindow` then I can check if it is label or image etc, `if GraphicControl.ClassType = TImage then` etc. –  Dec 23 '14 at 22:54
  • @Blobby - *".. I needed to get the control (the image or label) that is directly below the paintbox .."* - That's not what the question asks. Please read the example you gave involving several controls. Hence Deltic's answer... – Sertac Akyuz Dec 23 '14 at 22:59
  • @Sertac The question asks twice for a singular object solution, once in the title and once in the body: _How to use FindVCLWindow on a TGraphicControl that is underneath a TPaintBox?_ and _So my main question is, how can I use a function like FindVCLWindow on a TGraphicControl that is behind a TPaintBox?_ There where OP states a plurality, he is just informative on his situation: There are a multiple of controls behind the PaintBox. It is absolutely clear that the intention of the question is to find a solution for one of them, resolving in a solution for all of them. – NGLN Dec 25 '14 at 06:59
  • " It is absolutely clear that the intention of the question is to find a solution for one of them, " - I presume it is also clear that the question intends to find *which* one of them. – Sertac Akyuz Dec 25 '14 at 09:08
  • @Sertac Uh...yes? (ie. What do you mean?) OP wants to know what GraphicControl is at a mouse position, if any, and show its name in a StatusBar. The function results in _which_. Is that what you're wondering? – NGLN Dec 25 '14 at 09:40
  • @NGLN - I thought there were several images, labels stacked on top of each other under the paintbox. Maybe I got it all wrong. Then again I think it would be an implication that the question is not clear. Never mind though, since you got it all sorted out... – Sertac Akyuz Dec 25 '14 at 09:46
3

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.

Deltics
  • 22,162
  • 2
  • 42
  • 70
  • Thanks for the dedicated answer. NGLN has solved my problem now though, but your answer gives me some ideas to think about. And yes in a way the underlying images I suppose would be a like a hot spot. –  Dec 23 '14 at 21:14