I want to get the Left,Top coordinates of a control regarding to the container form. The target control may be, at the same time, inside of any number of other containers such as TPanels and TGroupBoxes. That means that to get the target control origin, the code should have to take into account the Left,Top coordinates of all the other containers + the Left,Top coordinates of the target control itself. Instead, I'm using a second approuch, wich consist in using the ClientToScreen function to get the Left,Top screen coordinates of the target control and after that, subtracting the Left,Top coordinates of the form. Saddly this approach is not working. I'm attaching an image that clarifies my thinkings and has the actual code I used to calculate the desired coordinates. I appreciate any help on this.
#VitaliyG's answer shows how to convert the coordinates of the control's absolute top-left corner to Form-relative client coordinates. If you want to convert the coordinates of the top-left corner of the control's client area instead, you can pass the control's ClientOrigin property to the Form's ScreenToClient() method:
function GetControlClienOrigin(const aControl: TControl: const aForm: TForm): TPoint;
begin
Result := aForm.ScreenToClient(aControl.ClientOrigin);
end;
If the control in question is a TWinControl descendant, an alternative is to use the Win32 API MapWindowPoints() function instead:
function GetControlClientOrigin(const aControl: TWinControl: const aForm: TForm): TPoint;
var
Pt: TPoint;
begin
Pt := Point(0, 0);
SetLastError(0);
if MapWindowPoints(aControl.Handle, aForm.Handle, Pt, 1) = 0 then
CheckOSError(GetLastError);
Result := Pt;
end;
Try using ClientToParent and specify form as Parent parameter.
You have to pass coords relative to control, so Top, Left of the control will be at control's (0,0)
Control.ClientToParent(TPoint.Create(0,0), Form)
So these 3 statements return the same, wanted TPoint:
aControl.ClientOrigin - aForm.ClientOrigin;
aControl.ClientToParent(Point(0,0), aForm);
aForm.ScreenToClient(aControl.ClientOrigin);
Related
I'm trying to place an outside program right into an FMX TPanel using SetWindowPOS from WinAPI.
In order to do that I need the exact Top and Left values of the TPanel to pass it to SetWindowPOS. However, this value is always according to its parent, so i.e. if the Tpanel's parent is a TLayout and no margins are set, then the value of Top will be 0.
I need the Top value according to the screen.
I tried searching for that quite a bit but I can't figure it out.
Any help with that would be greatly appreciated.
After just posting the question I got the simple solution
var
pnt: TPointF;
begin
pnt := myRctngl.LocalRect.TopLeft;
pnt := myRctngl.LocalToAbsolute(pnt);
pnt := ClientToScreen(pnt);
End;
pnt should now have the value of the Point according to the screen.
You can now use pnt.x and pnt.y to get the top/left values.
I want to copy as efficient as possible the bitmap area behind a TRectangle (in example with red border). This is the boundsrect from the red rectangle in the parent control.
I have this in my Delphi Firemonkey app:
Getting the whole parent surface to a temp parent TBitmap:
(Parent as TControl).PaintTo(FParentBitmap.Canvas,
RectF(0, 0, ParentWidth, ParentHeight));
and then later on to copy the rectangle I want:
bmp.CopyFromBitmap(FParentBitmap, BoundsRect, 0, 0);
Of course this is not efficient. I want to copy the rectangle in 1 pass or at least I don't want to paint the whole parent into a temp TBitmap.
Do you know an efficient way? Please tell.
I created a TFrostGlass component which has the complete source in it. You can see/download it over here: https://github.com/Spelt/Frost-Glass
The copy bitmap code is in: FrostGlass.pas
Unfortunately, PaintTo does not allow painting only part of the control. However, as #Rob Kennedy mentioned, you can control where on your target Bitmap the content ends up by modifying the offsets.
In addition, when calling BeginScene before the call to PaintTo, you can set the ClipRects parameter, which means that only this part of the Canvas will be updated. This is necessary if your target Bitmap is larger than BoundsRect, because otherwise you would also paint the area around it.
procedure PaintPartToBitmap(Control: TControl; SourceRect, TargetRect: TRect; Bitmap: TBitmap);
var ClipRects: TClipRects;
X, Y: Single;
begin
ClipRects := [TRectF.Create(TargetRect)];
if (Bitmap.Canvas.BeginScene(#ClipRects)) then
try
X := TargetRect.Left - SourceRect.Left;
Y := TargetRect.Top - SourceRect.Top;
Control.PaintTo(Bitmap.Canvas, RectF(X, Y, X + Control.Width, Y + Control.Height));
finally
Bitmap.Canvas.EndScene;
end;
end;
I want to get the absolute co-ordinates of the mouse when the mouse is over a control which has been placed on a host control. E.g. Host control is a panel a button is placed on the panel. I want to get the mouse co-ordinates relative to the panel when the mouse is over the button.
I have tried the obvious just to see what I get:
procedure TfmWorkingScreen.pnlScreenAreaMouseMove(Sender: TObject;
Shift: TShiftState; X, Y: Integer);
begin
StatusBar1.SimpleText := 'Left ' + IntToStr(X) + ' Right ' + IntToStr(Y);
end;
Clearly this will only work when the mouse is over the panel control. Is there a way to get the required co-ordinates?
Add an OnMouseMove event handler to the child control, the button in your example. In that OnMouseMove event handler you receive the X and Y cursor coordinates with respect to the child control's client area. If the host control is the immediate parent of the control whose OnMouseMove event has fired you use the control's ClientToParent method:
var
PosRelParent: TPoint:
....
PosRelParent := (Sender as TControl).ClientToParent(Point(X, Y));
If the parent control may be further up the parent/child relationship you can pass the parent control to ClientToParent:
PosRelParent := (Sender as TControl).ClientToParent(Point(X, Y), TheParent);
If you wish to express the position relative to some arbitrary controls client area then you can do so by converting via screen coordinates, a global frame of reference.
var
PosRelScreen, PosRelOtherControl: TPoint:
OtherControl: TWinControl;
....
PosRelScreen := (Sender as TControl).ClientToScreen(Point(X, Y));
PosRelOtherControl := OtherControl.ScreenToClient(PosRelScreen);
As one final offering, you can use GetMessagePos to obtain the screen relative coordinates of the mouse for the last message retrieved by a call to GetMessage.
var
MsgPos: TPoint;
....
MsgPos := TSmallPoint(GetMessagePos());
At this point you can use SomeControl.ScreenToClient(MsgPos) to get the coordinates of the cursor relative to that control's client area. Obviously it only makes sense to call GetMessagePos if you are in an event handler triggered by a queued mouse message.
If you want to listen to your mouse outside your program aswell you need to create a mouse hook.
I'ver earlier summitted a article and source code to About.com : http://delphi.about.com/od/windowsshellapi/a/delphi-hooks.htm
If this have you interest I would love to go deeper on then subject.
if (in Delphi) I do
Panel1.ManualFloat(Rect(500,500,600,600));
the panel is floated not at the specified Rect location, but instead in a sort of windows default location. How do I get a panel (or other control) to float at a specified location. It does seem to have the correct shape however. Is there some other property I need to set to make it work correctly?
Edit: Just to make things clear. I would expect the above code to make the panel a 100x100 square located at (500x500) relative to the top left hand corner of the screen, which it doesn't. The shape is correct but location is not. If subsequent controls are floated they are cascaded down the screen.
Edit2: This doesn't seem to be a problem in Delphi 7, but is in Delphi 2007 through XE2 (and possibly earlier)
Don't look further: Its a bug in the VCL.
ManualFloat creates a floating window and sets its Top, Left values in TControl.CreateFloatingDockSite(Bounds: TRect) and later sets its ClientWidth.
That is a mistake because doing that forces the WindowHandle creation (it didn't have a Handle yet) in
function TCustomForm.GetClientRect: TRect;
begin
if IsIconic(Handle) then // <===
And that calls the default positioning of the Window (cascading yadda yadda...) resetting the Top and Left
The fix would be to set the ClientWidth and ClientHeight before setting the Top and Left properties in TControl.CreateFloatingDockSite(Bounds: TRect)
Update: the fixed code in Controls.pas
function TControl.CreateFloatingDockSite(Bounds: TRect): TWinControl;
begin
Result := nil;
if (FloatingDockSiteClass <> nil) and
(FloatingDockSiteClass <> TWinControlClass(ClassType)) then
begin
Result := FloatingDockSiteClass.Create(Application);
with Bounds do
begin
// Setting Client area can create the window handle and reset Top and Left
Result.ClientWidth := Right - Left;
Result.ClientHeight := Bottom - Top;
// It is now safe to position the window where asked
Result.Top := Top;
Result.Left := Left;
end;
end;
end;
Like the TRect parameter's name of the function - ScreenPos - kind of says it already, the coordinates are in screen units rather then that of the parent.
If you want the panel to stay at the same place where it was, translate the coordinates relative to the screen:
with Panel1.ClientToScreen(Point(0, 0)) do
Panel1.ManualFloat(Bounds(X, Y, 100, 100));
Or, to include the panel's border:
if Panel1.HasParent then
with Panel1.Parent.ClientToScreen(Panel1.BoundsRect.TopLeft) do
Panel1.ManualFloat(Bounds(X, Y, 100, 100));
Or, to translate to a specific coordinate relative to the parent, use:
if Panel1.HasParent then
with Panel1.Parent.ClientOrigin do
Panel1.ManualFloat(Bounds(X + 500, Y + 500, 100, 100));
In a TImage's OnClick event, I would like to extract the x,y coordinates of the mouse. I would prefer them in relation to the image, but in relation to the form or window is just as good.
Mouse.CursorPos contains the TPoint, which in turn contains the X and Y position. This value is in global coordinates, so you can translate to your form by using the ScreenToClient routine which will translate screen coordinates to window coordinates.
According to the Delphi help file, Windows.GetCursorPos can fail, Mouse.CursorPos wraps this to raise an EOsException if it fails.
var
pt : tPoint;
begin
pt := Mouse.CursorPos;
// now have SCREEN position
Label1.Caption := 'X = '+IntToStr(pt.x)+', Y = '+IntToStr(pt.y);
pt := ScreenToClient(pt);
// now have FORM position
Label2.Caption := 'X = '+IntToStr(pt.x)+', Y = '+IntToStr(pt.y);
end;
The Mouse.CursorPos property will tell you the current position of the mouse. If the computer is running sluggishly, or if your program is slow to respond to messages, then it might not be the same as the position the mouse had when the OnClick event first occurred. To get the position of the mouse at the time the mouse button was clicked, use GetMessagePos. It reports screen coordinates; translate to client coordinates with TImage.ScreenToClient.
The alternative is to handle the OnMouseDown and OnMouseUp events yourself; their parameters include the coordinates. Remember that both events need to occur in order for a click to occur. You may also want to detect drag operations, since you probably wouldn't want to consider a drag to count as a click.
As others have said, you can use Mouse.CursorPos or the GetCursorPos function, but you can also just handle the OnMouseDown or OnMouseUp event instead of OnClick. This way you get your X and Y values as parameters to your event handler, without having to make any extra function calls.
How about this?
procedure TForm1.Button1Click(Sender: TObject);
var
MausPos: TPoint;
begin
GetCursorPos(MausPos);
label1.Caption := IntToStr(MausPos.x);
label2.Caption := IntToStr(MausPos.y);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
SetCursorPos(600, 600);
end;
Found this online somewhere once and saved it in my codesnippet DB :)
This page will probably solve all your questions however... There appear to be functions to go from client to screen coordinates and back etc..
Good luck!
To Firemonkey (FMX):
var
p: TPointF;
begin
p := Screen.MousePos;
end;