Centering a Tframe within a TTabSheet; - delphi

I have a delphi application that used a PageControl with a number of TabSheets. I also create new TabSheets at runtime and populate them with instances of predefined frames. These frames work well, except for the cosmetic problem of not centering on the TabSheet. I have tried to use Frame.Align := alClient, but that didn't do it. The relevant code follows:
CreateNewPage(3);
NewLimitedChoiceFrame := TLimitedChoiceFrame.Create(NewInputPage);
NewLimitedChoiceFrame.Parent := NewInputPage;
CreateNewPage creates a new instance of a TabSheet and makes the PageControl it's owner and parent. The result is assigned to the global variable NewInputPage.

To centre a control in its parent do this:
procedure CentreControl(Control: TControl);
begin
Control.Left := (Control.Parent.ClientWidth-Control.Width) div 2;
Control.Top := (Control.Parent.ClientHeight-Control.Height) div 2;
end;
Call this function, passing the frame. Obviously you need to wait until you've assigned the parent before doing so.
If the page control can be re-sized at runtime, add a call to this function from the tabsheet's OnResize event. Or, as NGLN points out simply set the control's Anchors to [] and the VCL framework will take care off centring the control when its parent is resized.

Related

How to pass a component as a parameter of a procedure in Delphi?

I want to be able to use one procedure to center all the components on a form. This is the kind of thing I'm going for:
procedure TForm4.centerComponent(x: Tobject);
begin
x.Left := (Form4.ClientWidth - x.Width) div 2;
end;
I would only be passing built in components (memo, label, edit etc...)
I get the feeling this is either not possible or if it is its probably not best practice
This is easy, but you must be careful about terminology:
A TObject is any Delphi object. It need not be a control. It doesn't even need to be something you can drop on a form.
A TComponent is an object you can drop on a form. It might be a visual control (like a button, a label, or an edit box), or it might be a non-visual component (like a TActionList).
A TControl is a visual control, like a button, a label, an edit box, or an animated analogue clock.
The above classes are ordered by inheritance.
So, you want a procedure that acts on TControls in general:
procedure Centre(AControl: TControl);
var
Parent: TWinControl;
begin
Parent := AControl.Parent;
if Parent = nil then
Exit;
AControl.SetBounds(
(Parent.ClientWidth - AControl.Width) div 2,
(Parent.ClientHeight - AControl.Height) div 2,
AControl.Width,
AControl.Height
);
end;
Every TControl has Top, Left, Width, and Height properties, as well as the SetBounds method, which we use above.
Notice that I centre the control in its parent window. (A control's Top and Left values are always relative to its parent.)
Now, there are two kinds of controls in Delphi:
Controls that are actual Win32 windows (with HWNDs).
Controls that are not actual Win32 windows.
Only the former kind of control can have child controls. These controls derive from TWinControl. That's the reason I declare Parent as a TWinControl. This is also the type of the TControl.Parent property.
Some notes about your code
x.Left := (Form4.ClientWidth - x.Width) div 2;
Here there are two issues (except for x: TObject not having any Width or Left properties):
Form4 is one particular instance of the form class. It is much better to write Self.ClientWidth or simply ClientWidth, so you refer to the current instance of the form class.
But even this is not good enough, because this only works if the form is the parent of x. x might well have a different parent. For instance, x might have a TPanel as its parent (the TPanel's parent being the form).

What causes a control to be placed more left-right-top-bottom than another which has same alignment? [duplicate]

In this particular case I'm using PowerPDF library to dynamically build a PDF document, but the same applies for the general concept of dynamically aligning controls sequentially inside of a parent control. In this library, TPRPage is the base control to contain all element controls, in this case, sequential instances of TPRLayoutPanel.
What I do when dynamically adding controls:
Create a control (TPRLayoutPanel)
Set the control's parent (TPRPage)
Align the control to top (PRLayoutPanel.Align:= alTop;)
The problem is it gets forced to the very beginning (top) instead of the very end (bottom) of the page.
I've tried setting its order PRLayoutPanel.SendToBack; or PRLayoutPanel.BringToFront but with no luck.
How can I dynamically create and align multiple controls within a parent control sequentially? My only current work-around is to add the controls in reverse order (from end to beginning) which is ridiculously unnecessary.
Here's my universal function which creates every new instance of an aligned control in this parent:
function TfrmReport.InsertPanel: TPRLayoutPanel;
begin
Result:= TPRLayoutPanel.Create(PRPage);
Result.Parent:= PRPage;
Result.Align:= alTop;
Result.Height:= 40; //Default, may change later
end;
Once again, DisableAlign and EnableAlign to the rescue:
procedure TForm1.FormCreate(Sender: TObject);
var
I: Integer;
P: TPanel;
begin
DisableAlign;
try
for I := 0 to 4 do
begin
P := TPanel.Create(Self);
P.Caption := IntToStr(I);
P.Align := alTop;
P.Parent := Self;
end;
finally
EnableAlign;
end;
end;
Explanation:
When alignment is enabled, every single addition of a control to a container (the form itself in this specific case) will re-evaluate all alignment (and anchor) settings of all other controls within that container. In case that control has no specific Top property set, then Top will be 0. When there is already another control aligned to the top, then there are two controls with Top = 0, and the one which is about to inserted wins. I (currently) have no in-depth explanation for that, but it just is, and the position order indeed gets reversed from the creation order.
Now, when alignment of the container is disabled, then consecutive added controls are simply just inserted with all their positioning properties unaltered. When alignment is enabled again, then all those controls are re-evaluated in the same manner, with the difference that this takes place in one single loop in the order of the index in the Controls array; i.e. the order in which they were created.
You need to set the Top property to be the bottom of the previous panel. For example, like this:
PanelTop := 0;
for i := 0 to 5 do
begin
Panel[i] := TPanel.Create(Self);
Panel[i].Parent := Self;
Panel[i].Height := PanelHeight;
Panel[i].Align := alTop;
Panel[i].Top := PanelTop;
inc(PanelTop, PanelHeight);
end;
To fit it into your code you'd have to keep track of the location of the most recently added panel. Perhaps you could add a var parameter to your InsertPanel function:
function TfrmReport.InsertPanel(var PanelTop: Integer): TPRLayoutPanel;
begin
Result:= TPRLayoutPanel.Create(PRPage);
Result.Parent:= PRPage;
Result.Top:= PanelTop;
Result.Height:= 40;
Result.Align:= alTop;
inc(PanelTop, Result.Height);
end;
I trust you get the idea!
You may use alCustom align type and control all of your panels positions via CustomAlignPosition method (you will need to override it in parent control). This will give you more flexibility and control.

How do you make a TFrame's OnResize event occur when it first appears?

I have a frame that is being placed on a form. I expect to be placing a number of instances of this frame on the form.
It has a drawgrid with 2 columns and in the OnResize event I make the 2nd column extend to the end of the available space. That works when the form is manually resized with the frame Align set to alTop. But when the form first appears even though FrameResize gets called it has no effect. (Though it did have the desired effect when I put a break point on it).
So, what I am doing now is calling the FrameResize from the forms OnShow handler, but that is ugly. The frame should be able to appear correctly formed without help from the Form.
Any ideas? I did try overriding SetParent, but that didn't work. Using Xe2.
TIA
Mark
I have solved it with advice from Peter Below, Delphi Team B Delphi member.
I overrode the frame's set bounds. It was getting called even before the component variables were set, so it looks like this
procedure TfaDupDisplay.SetBounds(ALeft, ATop, AWidth, AHeight: Integer); // Had to use SetBounds because OnRezise was not working
var grid: TDrawGrid;
begin
inherited;
if pnlWebData = nil then
exit;
pnlWebData.Width := Width div 2;
for grid in TArray<TDrawGrid>.Create(grdData, grdDup) do
grid.ColWidths[1] := grid.Width - grdData.ColWidths[0];
end{ SetBounds};

Why doesn't my run-time-created component appear on the form?

I am testing the example that came from this Q&A Component Creation - Joining Components Together? to learn how to create a custom/composite component.
While the installed component from the example works dragging on to the form, I can't seem to create it at run time.
procedure TForm1.Button1Click(Sender: TObject);
var
MyPanel2 : TMyPanel;
begin
MyPanel2 := TMyPanel.Create(Form1);
With MyPanel2 do
begin
Left := 10;
Top := 10;
Width := 400;
Height := 400;
Visible := True;
Image.Picture.LoadFromFile('C:\test.png');
end;
end;
I tried both self and Form1 as the owner. Played with properties of both the panel and the image.
Just not sure what I am doing wrong. No errors except when I forgot to add pngimage to my uses. Steps through the code just fine, nothing visually occurs for the run time creation.
You need to set Parent in the runtime code.
MyPanel2 := TMyPanel.Create(Self);
with MyPanel2 do
begin
Parent := Self;//oops, you forgot to set this
SetBounds(10, 10, 400, 400);
Image.Picture.LoadFromFile('C:\test.png');
end;
The code in your question won't result in the control showing for a plain vanilla TPanel, or indeed any control.
From the documentation, with my emphasis:
Specifies the parent of the control.
Use the Parent property to get or set the parent of the control. The
parent of a control is the control that contains it. For example, if
an application includes three radio buttons in a group box, the group
box is the parent of the three radio buttons, and the radio buttons
are the child controls of the group box.
To serve as a parent, a control must be an instance of a TWinControl
descendant.
When creating a new control at run time, assign a Parent property
value for the new control. Usually, this is a form, panel, group box,
or a control that is designed to contain another. Changing the parent
of a control moves the control onscreen so that it is displayed within
the new parent. When the parent control moves, the child moves with
the parent.

Resize won't execute untill I manually call ClientHeigh or until I manually resize it

I want to create a custom control derived from TPanel that contains an image and a bunch of other controls on it.
After writing the code I have some weird behavior in my program. I realized that some vars that were supposed to be initialized in TDisplay.Resize (override) was never initialized because the Resize was never executed.
To 'solve it' I put a button on a form and called the LoadSample function which calls ClientHeight which calls Resize FOR THE FIRST TIME!
constructor TDisplay.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Ready := FALSE;
Parent := Owner as TWinControl;
Width := 200;
Height := 86;
Color := clSilver;
Caption := '';
DoubleBuffered:= TRUE;
InternalDisplay:= TImage32.Create(Self);
with Display DO
begin
Parent := Self;
Bitmap.Width := 1;
Bitmap.Height := 1;
RepaintMode := rmOptimizer;
Align := alClient;
SetupBitmap(TRUE, clBlack32);
Visible := TRUE;
OnMouseDown := DMouseDown;
end;
...
end;
Update:
Also the InternalDisplay won't be aligned to its parent size until I manually resize the form (the control) at runtime. Only then it will act as it was supposed to act (to stay aligned to alClient).
Update 2:
Resize is declared like that: procedure Resize; override;
Update 3:
I removed the ClientHeight line from my construnctor and move it here:
procedure TDisplay.LoadSample(VAR Obj: TMySample; CONST bReleaseOnExit: boolean)
begin
ClientHeight; <--------- this will call Resize for the first time and my code will be finally initialized. But until this point my display will look odd because the code was never initialized. So the user will see weird stuff until it pushes the 'LoadSample' button.
more code here....
end;
Update 4:
I used HandleNeeded as David suggested and it solved the initialization problem. However, the Image still won't align to the entire client area unless I manually resize the form/control.
Update 5
Continued here, as David suggested: TImage won't align to parent
Your control is derived from TWinControl, and TWinControl calls Resize in response to the WM_SIZE message. So, Resize will not be called unless the control's window handle has been created.
The Resize method is not called when you assign Height, or indeed Width, because the window handle has not yet been allocated.
When you evaluate the ClientHeight property, that results in the window handle being created, and then Resize is called. That's because GetClientHeight calls GetClientRect which looks like this:
function TWinControl.GetClientRect: TRect;
begin
Winapi.Windows.GetClientRect(Handle, Result);
end;
And it's the evalutation of the Handle property that forces the window handle into existence.
Your form isn't showing yet, so it isn't yet able to receive Windows messages (such as the resize message that triggers the OnResize event).

Resources