Difference between revisions of "Anchor Sides"

From Lazarus wiki
Jump to navigationJump to search
m (insert link deorphaning DebugLn)
 
(16 intermediate revisions by 9 users not shown)
Line 1: Line 1:
 
{{Anchor Sides}}
 
{{Anchor Sides}}
  
See also [[Autosize / Layout]].
+
There are some new [[Property|properties]] and [[Method|methods]] for automatic layout of
 
 
There are some new properties and methods for automatic layout of
 
 
controls.
 
controls.
 
You can now setup controls to keep a certain distance to other controls,
 
You can now setup controls to keep a certain distance to other controls,
Line 10: Line 8:
 
Each of the four sides of a control (Left, Top, Right, Bottom) can now
 
Each of the four sides of a control (Left, Top, Right, Bottom) can now
 
be anchored/bound to a side of another control. For example you can now
 
be anchored/bound to a side of another control. For example you can now
anchor the left side of TEdit to the right side of a TLabel. Everytime
+
anchor the left side of [[TEdit]] to the right side of a [[TLabel]]. Everytime
 
the Label is moved or resized the Edit's left side will follow, which
 
the Label is moved or resized the Edit's left side will follow, which
 
normally results in moving the Edit parallel to the Label.
 
normally results in moving the Edit parallel to the Label.
  
=Example 1=
+
==Example 1==
 
  +--------+ +-------+
 
  +--------+ +-------+
 
  | Label1 | | Edit1 |
 
  | Label1 | | Edit1 |
 
  +--------+ |      |
 
  +--------+ |      |
 
             +-------+
 
             +-------+
==In code==
+
===In code===
Edit1.AnchorSide[akLeft].Side:=asrRight;
+
 
Edit1.AnchorSide[akLeft].Control:=Label1;
+
<syntaxhighlight lang="Pascal">Edit1.AnchorSide[akLeft].Side := asrRight;
Edit1.Anchors:=Edit1.Anchors+[akLeft];
+
Edit1.AnchorSide[akLeft].Control := Label1;
 +
Edit1.Anchors := Edit1.Anchors + [akLeft];</syntaxhighlight>
 +
 
 
You can define the distance with the BorderSpacing properties:
 
You can define the distance with the BorderSpacing properties:
Edit1.BorderSpacing.Left:=10;
+
<syntaxhighlight lang="Pascal">Edit1.BorderSpacing.Left := 10;</syntaxhighlight>
 
The same can be done with the method:
 
The same can be done with the method:
Edit1.AnchorToNeighbour(akLeft,10,Label1);
+
<syntaxhighlight lang="Pascal">Edit1.AnchorToNeighbour(akLeft, 10, Label1);</syntaxhighlight>
  
==Notes==
+
===Notes===
 
The Edit1.Left will follow Label1.Left+Label1.Width, not the other way
 
The Edit1.Left will follow Label1.Left+Label1.Width, not the other way
 
around. That means, moving Label1 will move Edit1. But moving Edit1 will
 
around. That means, moving Label1 will move Edit1. But moving Edit1 will
be undone by the LCL.
+
be undone by the [[LCL]].
 
If you also anchor the right side of Label1 to the left side of Edit1,
 
If you also anchor the right side of Label1 to the left side of Edit1,
 
you created a circle, and this can result together with some other
 
you created a circle, and this can result together with some other
autosize properties in a loop. This is ignored by the LCL or automagically repaired when the Parent.AutoSize is true.
+
autosize properties in a loop. This is ignored by the LCL or automagically repaired when the Parent.AutoSize is [[True|true]].
  
==Via the Anchor Editor==
+
===Via the Anchor Editor===
  
 
The anchor editor is a floating window that is available via the menu '''View / Anchor Editor''' or via the button on the '''Anchors''' property in the object inspector.
 
The anchor editor is a floating window that is available via the menu '''View / Anchor Editor''' or via the button on the '''Anchors''' property in the object inspector.
  
[[Image:Anchoreditor.png]]
+
[[Image:Anchor_Editor_en.png]]
  
=Example 2=
+
==Example 2==
  
 
You can anchor the Edit's top side to follow the Label's top side:
 
You can anchor the Edit's top side to follow the Label's top side:
Line 51: Line 51:
 
           +-------+
 
           +-------+
 
</pre>
 
</pre>
<pre>
+
<syntaxhighlight lang="Pascal">Edit1.AnchorSide[akTop].Side := asrTop;
Edit1.AnchorSide[akTop].Side:=asrTop;
+
Edit1.AnchorSide[akTop].Control := Label1;
Edit1.AnchorSide[akTop].Control:=Label1;
+
Edit1.Anchors := Edit1.Anchors + [akTop];</syntaxhighlight>
Edit1.Anchors:=Edit1.Anchors+[akTop];
+
 
</pre>
 
 
The same can be done with the method:
 
The same can be done with the method:
<pre>Edit1.AnchorParallel(akTop,0,Label1);</pre>
+
<syntaxhighlight lang="Pascal">Edit1.AnchorParallel(akTop,0,Label1);</syntaxhighlight>
  
=Example 3=
+
==Example 3==
  
 
Centering a Label vertically to an Edit:
 
Centering a Label vertically to an Edit:
Line 69: Line 68:
 
           +-------+
 
           +-------+
 
</pre>
 
</pre>
<pre>
+
<syntaxhighlight lang="Pascal">Edit1.AnchorSide[akTop].Side := asrCenter;
Edit1.AnchorSide[akTop].Side:=asrCenter;
+
Edit1.AnchorSide[akTop].Control := Label1;
Edit1.AnchorSide[akTop].Control:=Label1;
+
Edit1.Anchors := Edit1.Anchors + [akTop] - [akBottom];</syntaxhighlight>
Edit1.Anchors:=Edit1.Anchors+[akTop]-[akBottom];
+
 
</pre>
 
 
The same can be done with the method:
 
The same can be done with the method:
<pre>Edit1.AnchorVerticalCenterTo(Label1);</pre>
+
<syntaxhighlight lang="Pascal">Edit1.AnchorVerticalCenterTo(Label1);</syntaxhighlight>
  
 
Obviously anchoring the bottom side of Edit1 does not make sense when
 
Obviously anchoring the bottom side of Edit1 does not make sense when
 
centering.
 
centering.
  
=New property=
+
==Example 4==
<pre>property AnchorSide[Kind: TAnchorKind]: TAnchorSide read GetAnchorSide;</pre>
+
Using a [[TSplitter]] with the AnchorEditor between two panels: the left panel should behave as if it had <syntaxhighlight lang="pascal" inline>Align=alLeft</syntaxhighlight>, the right panel should behave as if it had <syntaxhighlight lang="pascal" inline>Align=alClient</syntaxhighlight>.
This is not published in the [http://wiki.lazarus.freepascal.org/IDE_Window:_Object_Inspector object inspector]. You can edit it in the
+
 
designer via the [http://wiki.lazarus.freepascal.org/IDE_Window:_Anchor_Editor anchor editor] (Menu: View -> View anchor editor, or
+
* Do not use the <syntaxhighlight lang="pascal" inline>Align</syntaxhighlight> properties of the panels and of the splitter. They must be set to <syntaxhighlight lang="pascal" inline>alNone</syntaxhighlight>. This is important for the splitter for which the default value is <syntaxhighlight lang="pascal" inline>alLeft</syntaxhighlight>.
 +
* In the anchor editor do the following adjustments:
 +
** Anchor the top and bottom sides of the panels and the splitter to the corresponding sides of the form.
 +
** Select the first panel and anchor its left side to the left side of the form and its right side to the left side of the splitter.
 +
** Select the second panel and anchor its right side to the right side of the form and its left side to the right side of the splitter.
 +
** The left and right sides of the splitter must not be anchored (as seen from the splitter itself) because this would fix the position of the splitter and make it not movable.
 +
 
 +
The same effect can be achieved by this code:
 +
<syntaxhighlight lang="pascal">procedure TForm1.FormCreate(Sender: TObject);
 +
begin
 +
  with Panel1 do
 +
  begin
 +
    AnchorSideTop.Control := Self;
 +
    AnchorSideBottom.Control := Self;
 +
    AnchorSideBottom.Side := asrBottom;
 +
    AnchorSideLeft.Control := Self;
 +
    AnchorSideRight.Control := Splitter1;
 +
    Anchors := [akLeft, akRight, akTop, akBottom];
 +
  end;
 +
  with Panel2 do
 +
  begin
 +
    AnchorSideTop.Control := Self;
 +
    AnchorSideBottom.Control := Self;
 +
    AnchorSideBottom.Side := asrBottom;
 +
    AnchorSideLeft.Control := Splitter1;
 +
    AnchorSideLeft.Side := asrBottom;
 +
    AnchorSideRight.Control := self;
 +
    AnchorSideRight.Side := asrBottom;
 +
    Anchors := [akLeft, akRight, akTop, akBottom];
 +
  end;
 +
  with Splitter1 do
 +
  begin
 +
    AnchorSideTop.Control := Self;
 +
    Splitter1.AnchorSideBottom.Control := Self;
 +
    AnchorSideBottom.Side := asrBottom;
 +
    Anchors := [akTop, akBottom];
 +
  end;
 +
end;</syntaxhighlight>
 +
 
 +
==New property==
 +
<syntaxhighlight lang="Pascal">property AnchorSide[Kind: TAnchorKind]: TAnchorSide read GetAnchorSide;</syntaxhighlight>
 +
This is not published in the [[IDE_Window:_Object_Inspector|object inspector]]. You can edit it in the
 +
designer via the [[IDE_Window:_Anchor_Editor|anchor editor]] (Menu: View -> View anchor editor, or
 
click on button of 'Anchors' property).
 
click on button of 'Anchors' property).
  
=New methods to easily configure common layouts=
+
==New methods to easily configure common layouts==
<pre>
+
 
procedure AnchorToNeighbour(Side: TAnchorKind; Space: integer;
+
<syntaxhighlight lang="Pascal">procedure AnchorToNeighbour(Side: TAnchorKind; Space: integer; Sibling: TControl);
                            Sibling: TControl);
+
procedure AnchorParallel(Side: TAnchorKind; Space: integer; Sibling: TControl);
procedure AnchorParallel(Side: TAnchorKind; Space: integer;
 
                        Sibling: TControl);
 
 
procedure AnchorHorizontalCenterTo(Sibling: TControl);
 
procedure AnchorHorizontalCenterTo(Sibling: TControl);
 
procedure AnchorVerticalCenterTo(Sibling: TControl);
 
procedure AnchorVerticalCenterTo(Sibling: TControl);
procedure AnchorAsAlign(TheAlign: TAlign; Space: Integer);
+
procedure AnchorAsAlign(TheAlign: TAlign; Space: Integer);</syntaxhighlight>
</pre>
+
 
 +
AnchorVerticalCenterTo works with Parent too. Then it will center on the client area, that means the center of the control is at ''ClientHeight div 2''.
 +
 
 +
Center anchoring is not yet fully supported when computing the size of the parent. For example when you put a label center anchored into a [[TGroupBox|Groupbox1]] and set Groupbox1.AutoSize to true then Groupbox1 height will shrink leaving no space for the label. The solution is to center to a control that is not centered. For example center a Label1 to a [[TComboBox|ComboBox]] and use for the ComboBox the default anchors (Anchors=[akLeft,akTop]).
  
=Anchoring to invisible controls=
+
==Anchoring to invisible controls==
  
Since 0.9.25 rev 12800 anchoring to invisible controls has changed to work more intuitively. For example: The below controls A, B, C are anchored (C.Left to B.Right and B.Left to A.Right):
+
Anchoring to invisible controls works intuitively. For example: The below controls A, B, C are anchored (C.Left to B.Right and B.Left to A.Right):
  
 
   +---+ +---+ +---+
 
   +---+ +---+ +---+
Line 105: Line 146:
 
   +---+ +---+ +---+
 
   +---+ +---+ +---+
  
If B is hidden (Visible:=false) then C.Left will skip B and use the right of A, resulting in
+
If B is hidden (Visible:=[[False|false]]) then C.Left will skip B and use the right of A, resulting in
  
 
   +---+ +---+
 
   +---+ +---+
Line 111: Line 152:
 
   +---+ +---+
 
   +---+ +---+
  
=Circular references=
+
==Circular references==
  
 
You can build circular references by anchoring two sides to each other, which creates an impossible anchoring. The LCL notices that, but will not raise an exception, because it can be a temporary circle. For example: A row of buttons. Button1 is left of Button2, Button2 is left of Button3. Now the order is changed to: Button3, Button1, Button2. If the reanchoring is started with Button3, a circle is created temporarily and is fixed when the reordering is completed. The LCL detects circles and will not move the Button. Circles are not the only anomaly.
 
You can build circular references by anchoring two sides to each other, which creates an impossible anchoring. The LCL notices that, but will not raise an exception, because it can be a temporary circle. For example: A row of buttons. Button1 is left of Button2, Button2 is left of Button3. Now the order is changed to: Button3, Button1, Button2. If the reanchoring is started with Button3, a circle is created temporarily and is fixed when the reordering is completed. The LCL detects circles and will not move the Button. Circles are not the only anomaly.
  
There is one exception to this rule. If the Parent.AutoSize is true, then the LCL will automatically break circles on autosize and writes a warning via debugln (windows: --debug-log.txt, linux, et al: stdout). Which anchor is broken depends on the order of controls and the sides. So, temporary circles are still allowed with AutoSize=true if you enclose the change in  
+
There is one exception to this rule. If the Parent.AutoSize is true, then the LCL will automatically break circles on autosize and writes a warning via [[DebugLn|debugln]] (windows: --debug-log.txt, linux, et al: stdout). Which anchor is broken depends on the order of controls and the sides. So, temporary circles are still allowed with AutoSize=true if you enclose the change in  
  
<DELPHI>
+
<syntaxhighlight lang="Pascal">
Parent.DisableAlign;
+
Parent.DisableAutosizing;
 
try
 
try
   ..change anchors, aligns, bounds...
+
   // change anchors, aligns, bounds...
 
finally
 
finally
   Parent.EnableAlign;
+
   Parent.EnableAutosizing;
 
end;
 
end;
</DELPHI>
+
</syntaxhighlight>
  
 
The AutoSize algorithm does not only break circles, but fixes Align/AnchorSide inconsistencies too.
 
The AutoSize algorithm does not only break circles, but fixes Align/AnchorSide inconsistencies too.
  
 
Eventually it would be good to add some hints in the designer or a tool to list the circles and inconsistencies.
 
Eventually it would be good to add some hints in the designer or a tool to list the circles and inconsistencies.
 +
 +
==See also==
 +
 +
*[[Autosize / Layout]]
 +
*[[Example: Anchors. How to reliably align dynamically created controls under changing visibility]]

Latest revision as of 14:12, 11 October 2021

Deutsch (de) English (en) français (fr) 日本語 (ja) русский (ru)

There are some new properties and methods for automatic layout of controls. You can now setup controls to keep a certain distance to other controls, or center relative to other controls. See below for examples.

Each of the four sides of a control (Left, Top, Right, Bottom) can now be anchored/bound to a side of another control. For example you can now anchor the left side of TEdit to the right side of a TLabel. Everytime the Label is moved or resized the Edit's left side will follow, which normally results in moving the Edit parallel to the Label.

Example 1

+--------+ +-------+
| Label1 | | Edit1 |
+--------+ |       |
           +-------+

In code

Edit1.AnchorSide[akLeft].Side := asrRight;
Edit1.AnchorSide[akLeft].Control := Label1;
Edit1.Anchors := Edit1.Anchors + [akLeft];

You can define the distance with the BorderSpacing properties:

Edit1.BorderSpacing.Left := 10;

The same can be done with the method:

Edit1.AnchorToNeighbour(akLeft, 10, Label1);

Notes

The Edit1.Left will follow Label1.Left+Label1.Width, not the other way around. That means, moving Label1 will move Edit1. But moving Edit1 will be undone by the LCL. If you also anchor the right side of Label1 to the left side of Edit1, you created a circle, and this can result together with some other autosize properties in a loop. This is ignored by the LCL or automagically repaired when the Parent.AutoSize is true.

Via the Anchor Editor

The anchor editor is a floating window that is available via the menu View / Anchor Editor or via the button on the Anchors property in the object inspector.

Anchor Editor en.png

Example 2

You can anchor the Edit's top side to follow the Label's top side:

+--------+ +-------+
| Label1 | | Edit1 |
+--------+ |       |
           +-------+
Edit1.AnchorSide[akTop].Side := asrTop;
Edit1.AnchorSide[akTop].Control := Label1;
Edit1.Anchors := Edit1.Anchors + [akTop];

The same can be done with the method:

Edit1.AnchorParallel(akTop,0,Label1);

Example 3

Centering a Label vertically to an Edit:

           +-------+
+--------+ |       |
| Label1 | | Edit1 |
+--------+ |       |
           +-------+
Edit1.AnchorSide[akTop].Side := asrCenter;
Edit1.AnchorSide[akTop].Control := Label1;
Edit1.Anchors := Edit1.Anchors + [akTop] - [akBottom];

The same can be done with the method:

Edit1.AnchorVerticalCenterTo(Label1);

Obviously anchoring the bottom side of Edit1 does not make sense when centering.

Example 4

Using a TSplitter with the AnchorEditor between two panels: the left panel should behave as if it had Align=alLeft, the right panel should behave as if it had Align=alClient.

  • Do not use the Align properties of the panels and of the splitter. They must be set to alNone. This is important for the splitter for which the default value is alLeft.
  • In the anchor editor do the following adjustments:
    • Anchor the top and bottom sides of the panels and the splitter to the corresponding sides of the form.
    • Select the first panel and anchor its left side to the left side of the form and its right side to the left side of the splitter.
    • Select the second panel and anchor its right side to the right side of the form and its left side to the right side of the splitter.
    • The left and right sides of the splitter must not be anchored (as seen from the splitter itself) because this would fix the position of the splitter and make it not movable.

The same effect can be achieved by this code:

procedure TForm1.FormCreate(Sender: TObject);
begin
  with Panel1 do
  begin
    AnchorSideTop.Control := Self;
    AnchorSideBottom.Control := Self;
    AnchorSideBottom.Side := asrBottom;
    AnchorSideLeft.Control := Self;
    AnchorSideRight.Control := Splitter1;
    Anchors := [akLeft, akRight, akTop, akBottom];
  end;
  with Panel2 do
  begin
    AnchorSideTop.Control := Self;
    AnchorSideBottom.Control := Self;
    AnchorSideBottom.Side := asrBottom;
    AnchorSideLeft.Control := Splitter1;
    AnchorSideLeft.Side := asrBottom;
    AnchorSideRight.Control := self;
    AnchorSideRight.Side := asrBottom;
    Anchors := [akLeft, akRight, akTop, akBottom];
  end;
  with Splitter1 do
  begin
    AnchorSideTop.Control := Self;
    Splitter1.AnchorSideBottom.Control := Self;
    AnchorSideBottom.Side := asrBottom;
    Anchors := [akTop, akBottom];
  end;
end;

New property

property AnchorSide[Kind: TAnchorKind]: TAnchorSide read GetAnchorSide;

This is not published in the object inspector. You can edit it in the designer via the anchor editor (Menu: View -> View anchor editor, or click on button of 'Anchors' property).

New methods to easily configure common layouts

procedure AnchorToNeighbour(Side: TAnchorKind; Space: integer; Sibling: TControl);
procedure AnchorParallel(Side: TAnchorKind; Space: integer; Sibling: TControl);
procedure AnchorHorizontalCenterTo(Sibling: TControl);
procedure AnchorVerticalCenterTo(Sibling: TControl);
procedure AnchorAsAlign(TheAlign: TAlign; Space: Integer);

AnchorVerticalCenterTo works with Parent too. Then it will center on the client area, that means the center of the control is at ClientHeight div 2.

Center anchoring is not yet fully supported when computing the size of the parent. For example when you put a label center anchored into a Groupbox1 and set Groupbox1.AutoSize to true then Groupbox1 height will shrink leaving no space for the label. The solution is to center to a control that is not centered. For example center a Label1 to a ComboBox and use for the ComboBox the default anchors (Anchors=[akLeft,akTop]).

Anchoring to invisible controls

Anchoring to invisible controls works intuitively. For example: The below controls A, B, C are anchored (C.Left to B.Right and B.Left to A.Right):

 +---+ +---+ +---+
 | A | | B | | C |
 +---+ +---+ +---+

If B is hidden (Visible:=false) then C.Left will skip B and use the right of A, resulting in

 +---+ +---+
 | A | | C |
 +---+ +---+

Circular references

You can build circular references by anchoring two sides to each other, which creates an impossible anchoring. The LCL notices that, but will not raise an exception, because it can be a temporary circle. For example: A row of buttons. Button1 is left of Button2, Button2 is left of Button3. Now the order is changed to: Button3, Button1, Button2. If the reanchoring is started with Button3, a circle is created temporarily and is fixed when the reordering is completed. The LCL detects circles and will not move the Button. Circles are not the only anomaly.

There is one exception to this rule. If the Parent.AutoSize is true, then the LCL will automatically break circles on autosize and writes a warning via debugln (windows: --debug-log.txt, linux, et al: stdout). Which anchor is broken depends on the order of controls and the sides. So, temporary circles are still allowed with AutoSize=true if you enclose the change in

Parent.DisableAutosizing;
try
  // change anchors, aligns, bounds...
finally
  Parent.EnableAutosizing;
end;

The AutoSize algorithm does not only break circles, but fixes Align/AnchorSide inconsistencies too.

Eventually it would be good to add some hints in the designer or a tool to list the circles and inconsistencies.

See also