Example of ClientToParent to get accurate co-ordinates of an object on a form's canvas.

What is the best way to get an object's co-ordinates relative to a parent?
I assume it's ClientToParent? If so can you please provide an example...

So let's say I have a TForm, with a TPanel somewhere on the right side of the form, and on that panel a TButton.
The button's Left property would be 25 for example, but to get it's position relative to the form I'd have to do this:
Button1.Left + Panel1.Left.
Is there an easier way to get the button's left relative to the canvas of the form itself?
I'm trying this:
 FillRect(Rect(ClientToParent(point(Button1.left, Button1.top), form2).X - 2, 0, 100, 100));
..which compiles, but at runtime I get "Project Project1.exe raised exception class EInvalidOperation with message 'Parent given is not a parent of 'Form2''.

FillRect(Rect(ClientToParent(point(Button1.left, Button1.top), form2).X - 2, 0, 100, 100));

Open in new window

LVL 13
rfwoolfAsked:
Who is Participating?
 
HypoConnect With a Mentor Commented:
I think the problem in your code is that you call ClientToParent on the Form itself, and not on the Button...
With this example below, I have a Form, which has a Label and a Panel and on the Panel I have a TButton. Then when I connect the event, I get the correct coordinates of the button relative to the form.
But when I only used ClientToParent, and not Button1.ClientToParent, I got the same error as you did.

regards
Hypo
procedure TForm1.Button1MouseMove(Sender: TObject; Shift: TShiftState; X,  Y: Integer);
var p : TPoint;
begin
  p := Button1.ClientToParent(Point(X, Y), Form1);
  Label1.Caption := IntToStr(p.X)+ ' ' + IntToStr(p.Y);
end;

Open in new window

0
 
Geert GConnect With a Mentor Oracle dbaCommented:
what about convert the form's topleft clientarea to screencoordinates and also for the component you are interested in ...
then just subtract ...

function TForm1.FormRelative(aWinControl: TWinControl): TPoint;
var P: TWinControl;
begin
  Result := Point(0, 0);
  P := aWinControl;
  while (P <> nil) and (P.Parent <> nil) do
    P := P.Parent;
  if P <> nil then
    Result := Point(aWinControl.ClientOrigin.X - P.ClientOrigin.X, aWinControl.ClientOrigin.Y - P.ClientOrigin.Y);
end;

Open in new window

0
 
Geert GConnect With a Mentor Oracle dbaCommented:
this was a test i used i used to come to that and that's how i found the ClientOrigin property
procedure TForm1.Button2Click(Sender: TObject);
  procedure AddCoord(Msg: string; X, Y: Integer);
  begin
    Memo1.Lines.Add(Format('%s : (%d, %d)', [Msg, X, Y]));
  end;
var FormTopLeft, ControlTopLeft: TPoint;
begin
  FormTopLeft := ClientToScreen(Point(0, 0));
  ControlTopLeft := Button1.ClientToScreen(Point(0, 0));
  AddCoord('Form (Left, Top)', Left, Top);
  AddCoord('Form client (Left, Top)', ClientOrigin.X, ClientOrigin.Y);
  AddCoord('Form screen (Left, Top)', FormTopLeft.X, FormTopLeft.Y);
  AddCoord('Button (Left, Top)', Button1.Left, Button1.Top);
  AddCoord('Button FormRel (Left, Top)', ControlTopLeft.X - ClientOrigin.X, ControlTopLeft.Y - ClientOrigin.Y);
  ControlTopLeft := FormRelative(button1);
  AddCoord('Button FormRel (Left, Top)', ControlTopLeft.X, ControlTopLeft.Y);
end;

Open in new window

0
Get expert help—faster!

Need expert help—fast? Use the Help Bell for personalized assistance getting answers to your important questions.

 
rfwoolfAuthor Commented:
Thanks guys, both solutions work great, except for one problem...
they are off by the size of the titlebar and possibly the form's border, because for some reason the title bar is part of the form's "client" area (which I don't really agree with but anyway).
So in both cases, I need to be able to adjust the co-ordinates by the height of the titlebar, and the width of form's border.
It's bullshit, I know.
Any ideas on that one?
0
 
Geert GOracle dbaCommented:
the ClientOrigin is below the title bar for my delphi 7 app ... i think ... i'll start up my other laptop to check
this new vista sucks ...
0
 
rfwoolfAuthor Commented:
lol... I'm using XP.
Anyway I think I'm close to cracking it.
ClientOrigin.X, ClientOrigin.Y : (357, 195)
Screen: Button.left, button.top : (365, 203)
If you take Button1.ClientToScreen(Point(0, 0)) and subtract ClientOrigin.X and  ClientOrigin.Y, I think that gives you the accurate co-ordinates to paint on the form's canvas while compensating for the title bar and borders :p... will confirm in a few moments
0
 
rfwoolfAuthor Commented:
No joy. Still doesn't give the height of the title bar - I was confused. I'll keep thinking.
0
 
HypoCommented:
In Vista it works fine, but when I tried it on XP earlier, I think I got the same result as you do now... that's strange... :/
0
 
rfwoolfAuthor Commented:
I suppose one workaround is to use the API to get the titlebar height - but I'm loathed to use it - there must be a native way. You do understand the problem, right?
Here's an overview in case you don't follow...
If I paint on the form canvas  as in MyCanvas.Handle := Button1.Parent.Handle;
And I output to 0,0, it will paint at the very top left of the title bar, not on the client area.
I've managed to measure my title bar at about 31 pixels high, with the border being 4 pixels wide.

So then, how do we find the point of the form's client area, not including the title bar and border?
0
 
rfwoolfAuthor Commented:
Here's another idea...
Given that a form's height and width differ from its clientheight and clientwidth by the width of the title bar and border, we could use this as a workaround - however I guess one would have to test it on different systems.
Sigh... this sucks
0
 
HypoCommented:
Could you try if this works (using MapWindowPoints instead)? It works for me in Vista, but so did also the previous solution... :/

regards
Hypo
procedure TForm1.Button1MouseMove(Sender: TObject; Shift: TShiftState; X,  Y: Integer);
var p : TPoint;
begin
  p := Point(X, Y);
  MapWindowPoints(Button1.Handle, Form1.Handle, p, 1);
  Label1.Caption := IntToStr(p.X)+ ' ' + IntToStr(p.Y);
end;

Open in new window

0
 
rfwoolfAuthor Commented:
Hypo,
This solution also "works" as did the previous one, but again, if painting on the form's "canvas", the co-ordinates of your method do not compensate for the height of the title bar or borders.
For example, if I put a button at the top-left of my form, button.left = 0, button.top = 0;
Your procedure will generate 0,0.
If I paint to 0,0 it will paint onto the title bar :(
0
 
rfwoolfAuthor Commented:
LOL
You know my "workaround"  where the height of the taskbar is Form.Height - Form.ClientHeight, and the width of the border is Form.Width - Form.ClientWidth???
Well that won't work either. With the width it will give the width of 2 borders, right? So you halve it, no problem.
But the height includes the titlebar PLUS the bottom border.
You could assume that the height of the bottom border is the same as the width of the left or right borders, but it doesn't give me much confidence.
Sigh...
0
 
HypoCommented:
I think I misunderstood your problem earlier... but now i get it, It's not the coordinates itself that are wrong, it's the fact that painting that's wrong; Anyway... I think I remember that there are some Win32 API routines you can use to get the correct client area of your window... give me a few minutes and I might find them for you... :)
0
 
rfwoolfAuthor Commented:
That would be great.
Now that I look at it, technically this question is answered - you guys showed me how to use ClientToParent and you also showed me another way of finding an objects co-ords relative to the form.
I think I will open a new question.
0
 
rfwoolfAuthor Commented:
Thanks guys.
0
Question has a verified solution.

Are you are experiencing a similar issue? Get a personalized answer when you ask a related question.

Have a better answer? Share it in a comment.

All Courses

From novice to tech pro — start learning today.