Autosize / Layout/ru

From Free Pascal wiki
Jump to navigationJump to search

English (en) русский (ru) 中文(中国大陆)‎ (zh_CN) 中文(台灣)‎ (zh_TW)



ENG: AT THE MOMENT THIS PAGE IS UNDER TRANSLATION.
RUS: В НАСТОЯЩИЙ МОМЕНТ СТРАНИЦА НАХОДИТСЯ В ПРОЦЕССЕ ПЕРЕВОДА.



Введение

LCL может автоматически изменять размер и положение элемента управления, поэтому он адаптируется к изменениям шрифта, темы и текста или другого содержимого. Если вы хотите запустить программу на нескольких платформах, или если ваши подписи доступны на нескольких языках, то ваши средства управления должны правильно адаптироваться к своей среде. LCL позволяет не только сделать быстрый начальный дизайн (перемещение элементов управления на форму с помощью мыши), но и позже установить несколько ключевых свойств, которые сделают регуляторы автоматически адаптироваться впоследствии измененным содержанием и т.д.

  • Фиксированный дизайн (Fixed design): это значение по умолчанию при размещении элемента управления в дизайнере форм. Положение элемента управления фиксируется относительно его родителя. Размер и положение элемента управления (Left, Top) полностью изменяются программистом. Вы можете перемещать элемент с помощью мыши и изменять его размер свободно.
  • Выравнивание (Aligned): выравненные элементы управления заполняют оставшееся пространство родителя вверху, снизу, слева или справа, или заполняют всё оставшееся пространство.
  • Привязка (Anchored): вы можете закрепить стороны элемента управления (слева, сверху, справа, снизу) с его родителем или с другим элементот управления. Закрепление: LCL попытается оставаться на таком же расстоянии от точки привязки.
  • Разметка (Layout): элементы управления могут быть автоматически выровнены по строкам и столбцам (например, TRadioGroup)
  • Пользовательская разметка через OnResize/OnChangeBounds: можно выровнять элементы управления самостоятельно в коде, используя события OnResize и OnChangeBounds.
  • Пользовательские элементы управления: написав собственные элементы управления, вы можете переопределить почти каждое поведение LCL как вы хотите.

Правила приоритета

  1. Constraints (Ограничения)
  2. Align (Выравнивание)
  3. Anchors(Привязки)
  4. ChildSizing.Layout
  5. AutoSize (Автомасштаб элемента)
  6. OnResize, OnChangeBounds - однако, если вы установите границы, которые конфликтуют с вышеуказанными правилами, то это создаст бесконечный цикл

Общие свойства

Несколько важных свойств могут быть изменены, чтобы настроить автоматическое изменение размера:

  • Left, Top, Width, Height
  • AutoSize: Автомасштаб, указывает LCL автоматически изменить ширину и высоту элемента управления
  • Anchors: позволяет создавать зависимости, например, чтобы привязать ComboBox к правой стороне #Label.
  • Align
  • Constraints: позволяет установить минимум и максимум для ширины и высоты
  • BorderSpacing: позволяет установить расстояние между привязанными элементами управления
  • ChildSizing: позволяет установить расположение и расстояние дочерних элементов управления

Внутренние алгоритмы объясняются здесь: LCL AutoSizing.

Фиксированный дизайн

Фиксированный ​​дизайн установлен по умолчанию. Якорь установлен на [akLeft, akTop], что означает, значения Top и Left не будут изменяться LCL. Если AutoSize равно False, LCL не изменяет ширину или высоту. Если AutoSize равно True, то ширина и высота изменяются по размеру содержимого. Например, TLabel.AutoSize умолчанию True и тем самым изменяя его текст будет изменяться размер TLabel, чтобы поместился измененный текст. TButton.AutoSize умолчанию установлен в False, такbv образом изменение надписи на кнопке не изменит размеры кнопки. При установке Button.AutoSize в True, кнопка будет уменьшаться или увеличиваться каждый раз при изменении Caption на кнопке или при изменении шрифта или темы. Обратите внимание, что это изменение не всегда происходит сразу. Например во время FormCreate всё автоизмение размера приостанавливается. В любой момент вы можете изменить свойства Left, Top, Width и Height.

Автомасштаб

AutoSize логическое свойство во многих классах; оно позволяет отрегулировать размер элемента управления автоматически учитывать изменения в тексте или графике, содержащейся в нем, и позволяет наиболее эффективно использовать имеющееся пространство. Это очень важный механизм для создания кроссплатформенных форм.

Обычно AutoSize = True делает две вещи для видимого элемента управления:

  • Если возможно, изменяет управление в нужный размер. Например ширина и высота TButton изменяется в соответствии с надписью, в то время как TEdit изменяется только в высоту. Ширина TEdit не изменяется автоматически. Вы можете изменить ширину TEdit. (см GetPreferredSize).
  • Она передвигает все фиксированно расположенные дочерние элементы управления, так чтобы самый левый дочерний элемент управления имел Left = 0 (зависит от BorderSpacing) и самый верхний дочерний элемент управления имел Top = 0.

Отличия от Delphi

  • AutoSize в Delphi происходит только тогда, когда определенные свойства изменяются, например, когда шрифт из TLabel изменяется.LCL AutoSize всегда активен. Delphi позволяет изменить размер TLabel, который имеет AutoSize = True, LCL нет.
  • Скрытые управления не изменяют размер автоматически.
  • Изменение размера элемента управления не изменяет размер/расположение заякоренных дочерних элементов управления во время загрузки. Имейте в виду, что конструкторы элементов управления можно вызвать во время загрузки. При использовании akRight, akBottom якоря с установленными AnchorSides и BorderSpacing сохраняют правильное расстояние.

Автомасштаб и изменение размера элемента управления

С помощью [свойства] кнопки AutoSize=false задаются фиксированный размер по умолчанию.

Autosize1.png

При установке AutoSize=true для каждой кнопки, кнопки растягиваются (или сжимаются) в соответствии с текстом и темой.

Autosize on.png

AutoSize не сжимает [элемент управления] до минимально возможного размера, как вы можете видеть в кнопке OK. Он использует метод GetPreferredSize элемента управления, который вызывает метод CalculatePreferredSize. По умолчанию реализация TWinControl запрашивает виджетсет, который может иметь оптимальную ширину или высоту. Каждый элемент управления может переопределять метод CalculatePreferredSize. Например, TImage переопределяет его и возвращает размер изображения. Если нет оптимальной ширины (высоты), возвращаемое значение равно 0, и LCL будет хранить ширину(высоту) [элемента управления] (если не установлен флаг ControlStyle csAutoSize0x0, который в настоящее время используется только TPanel'ью).

TWinControl вычисляет размер всех [своих] дочерних элементов управления и использует это для вычисления своего оптимального размера.

Когда элемент управления привязан как слева, так и справа, его ширина фиксирована. Например, со [значением привязки] Align=alTop элемент управления привязан слева и справа, и соответствует ширине Родителя. Если Parent.AutoSize [имеет значение] true, тогда Родитель будет использовать оптимальную ширину элемента управления для вычисления своей собственной оптимальной ширины, и, таким образом, размер элемента управления будет изменен до его оптимальной ширины. См. Выравнивание и свойство AutoSize. Если [значение] оптимальной ширины недоступно, используются последние установленные границы (BaseBounds или ReadBounds). Если никаких ограничений не было установлено, используется метод GetControlClassDefaultSize. То же самое для [свойства] Height и привязки сверху и снизу.

Ограничения применяются всегда и имеют приоритет.

Автомасштаб и перемещение дочерних элементов управления

Когда AutoSize=false, вы можете размещать и перемещать элементы управления свободно:

Autosize on.png

Когда AutoSize=true, дочерние элементы управления с фиксированным положением перемещаются с подгонкой.



--Zoltanleo 22:53, 26 September 2018 (CEST): на самом деле, перемещаются границы родительского элемента управления так, чтобы дочерние элементы управления с фиксированной относительно друг друга позицией полностью умещались на родительском. При этом родительский элемент управления будет иметь минимально возможный размер. Таким образом, кнопки "перемещаются" относительно верхнего левого угла родительского элемента управления.



Autosize panel on.png

Обе кнопки на панели были перемещены влево и кверху на одно и то же значение так, чтобы сверху и слева не оставалось свободного пространства. Если будут установлены свойства BorderSpacing>0 (у кнопок) или Panel.ChildSizing.LeftRightSpacing>0 (у панели), то кнопки будут перемещены так, чтобы предопределенный [в упомянутых выше свойствах] зазор был задействован.

Могут быть перемещены дочерние элементы управления только со следующими свойствами:

  • Anchors=[akLeft,akRight]
  • AnchorSide[akLeft].Control=nil
  • AnchorSide[akTop].Control=nil
  • Align=alNone

Перемещение дочерних элементов управления зависит от свойства ChildSizing.Layout. Разметка применяется в методе TWinControl.AlignControls, который может быть полностью или частично переопределен. Например, TToolBar переопределяет ControlsAligned, чтобы разместить все элементы управления со значением свойства Align=alCustom, и задает разметку для перемещения на следующие линии не "вписавшимся" [в родительский контрол] [дочерним] элементам управления.

[Родительские] элементы управления могут отключать перемещение дочерних элементов управления установкой [у свойства] ControlStyle флагов csAutoSizeKeepChildLeft и csAutoSizeKeepChildTop (начиная с 0.9.29).

Автомасштаб и формы

Формы без Parent [родительского элемента управления] управляются диспетчером окон и, следовательно, не могут свободно размещены или перемещены. Изменение размера [в RunTime] является лишь рекомендацией, и может быть проигнорировано диспетчером окон. Например, вы можете устанавливать ширину формы 1000 [px], а widgetset в ответ изменит размер к 800 [px]. Если вы устанавливаете [свойство] Witdh в [событии формы] OnResize, то можете создать бесконечную петлю. Вот почему LCL TForm отключает AutoSize, когда widgetset изменяет размеры формы.

Это означает, что [действие свойства] AutoSize для форм приостанавливается, когда пользователь изменяет размер формы или если диспетчеру окон не нравятся [текущие] границы [окна]. Например, некоторые диспетчеры окон в Linux имеют такие фишки, как магнитные [("прилипающие")] края, которые изменяют размеры [текущего] окна в привязке к другим окнам.

Принудительная установка автомасштабирования формы

Вы можете выставить новое [значение свойства] AutoSize, выполнив:

Form1.AutoSize := False;
Form1.AutoSize := True;

Расчет размера формы с "автомасштабом" заранее

При размещении формы с "автомасштабом" [(очевидно, имеется ввиду Form1.AutoSize=True)] вам может потребоваться [получить] размер формы, прежде чем показывать ее. Для [установки] автомасштаба требуется дескриптор [хэндл окна]. Вы можете рассчитать размер формы прежде, чем ее показывать, с помощью:

Form1.HandleNeeded;
Form1.GetPreferredSize(PreferredWidth,PreferredHeight);
Light bulb  Примечание: Диспетчер окон и события формы могут изменять размер [окна]. Предпочтительный размер не включает рамку окна формы. Это запланированная особенность.



Привязка сторон (Anchored)

Элементы управления [для привязки к] четырем сторонам имеют [свойство Anchors с возможными значениями]: akLeft, akTop, akRight и akBottom. Каждая сторона может быть привязана к родителю или к стороне другого собрата (элемент управления с одним и тем же родителем). Привязка означает сохранение расстояния [между привязанными элементами управления]. По умолчанию [значение свойства] Anchors = [akLeft, akTop]. Вертикальные привязки не зависят от горизонтальных привязок. Некоторые свойства, такие как Align и Parent.AutoSize имеют более высокий приоритет, и могут изменять поведение.

Привязка к родителю или nil

Привязка к nil (по умолчанию) имеет почти такой же эффект, как привязка к родителю. Оба пытаются сохранять расстояние до края клиентской области Родителя. Для привязки к Nil используется расстояние от последнего вызова SetBounds, в то время как для привязки к родительскому используется значение BorderSpacing.

Существует четыре комбинации akLeft, akRight (akTop, akBottom):

  • akLeft, no akRight: Left контрола фиксирован и не [может быть] изменен LCL. Правая сторона не закреплена, поэтому она следует за левой стороной. Это означает, что Width также сохраняется.
  • akLeft and akRight: Left элемента управления фиксирован и не [может быть] изменен LCL. Правая сторона привязана к правой стороне Родителя. Это означает, что если Родитель увеличен на 100 пикселей, тогда Width контрола также будет увеличена на 100 пикселей.
  • akRight, no akLeft: левая сторона управления не закреплена, поэтому она будет следовать за ее правой стороной. Правая сторона закреплена. Это означает, что если Родитель увеличится на 100 пикселей, тогда контроле перемещается вправо на 100 пикселей.
  • no akLeft and no akRight: ни одна из сторон не закреплена. Положение центра контрола масштабируется с родителем. Например, если контрол находится в середине родительского элемента, а родительский элемент увеличен на 100 пикселей, тогда контрол перемещается на 50 пикселей вправо.

Изменение размера родителя с привязанным контролом

При изменении размера родителя все привязанные дочерние контролы перемещаются и/или изменяются по размеру сразу, пока не будет отключено [свойство] AutoSizing. Например, AutoSizing отключается во время загрузки формы и при создании формы.

GroupBox с кнопкой: Anchors1.png

Когда GroupBox увеличивается [в размере], расстояние до привязанной стороны сохраняется:

С akLeft, без akRight: Anchors akLeft.png

С akLeft и akRight: Anchors akLeft akRight.png

С akRight, без akLeft: Anchors akRight no akLeft.png

Три кнопки в GroupBox: Anchors no akLeft no akRight small.png

Без akLeft и без akRight их центры масштабируются: Anchors no akLeft no akRight big.png


На заметку:

  • Загрузка формы похожа на один большой SetBounds. Во время загрузки свойства задаются с использованием значений [, сохраненных в файле формы] lfm. Имейте в виду, что из-за предков и фреймов может быть несколько файлов lfm. После загрузки LCL активирует autosizing. Привязанные контролы используют ограничение в конце загрузки. Любой шаг между ними игнорируется.
  • Для пользовательских контролов часто лучше устанавливать AnchorSides вместо Anchors.

Изменение размера привязанного элемента управления

При изменении ширины привязанного элемента управления, например, через Object Inspector или через код Button1.Width:=3, вы можете увидеть разницу между привязкой к "родителю" и привязкой к "nil". Привязка к родителю будет изменять размер и перемещать Button1, а привязка к nil будет только изменять размер. Например:

Привязка к nil

Anchored right nil resize1.png Кнопка Button1 привязана [akTop,akRight], AnchorSide[akRight].Control=nil

Anchored right nil resize2.png Установка ширины на меньшее значение приведет к сужению кнопки, сохраняя [значение] Left кнопки, увеличивая расстояние от правой стороны [кнопки до правого края].

Anchored right nil resize3.png При изменении размера Groupbox кнопка сохранит новое расстояние [до правого края].

Объяснение: установка [значения] Width [кнопки] эквивалентна вызову SetBounds(Left,Top,NewWidth,Height). Вот почему [значение] Left сохраняется. Это совместимо с [поведением контролов в] Delphi.

Привязка к родителю

Anchored right parent resize1.png Кнопка Button1 привязана [akTop,akRight], AnchorSide[akRight].Control=Button1.Parent

Anchored right parent resize2.png Установка [значения] Width на меньшее значение приведет к сужению кнопки, сохранив расстояние [от ее правой границы до края компонента-родителя] и изменив [значение] Left кнопки.

Привязка к соседу

Вы можете привязываться к соседним элементам управления. Следующий пример показывает[, что]:

  • вы можете привязать левый [край] label'а к левому краю кнопки
  • привязать верхний край label'а к нижнему краю кнопки
  • привязать центр label'а к центру кнопки

Anchorsides example1.png

Anchorside example2.png

Подробнее о том, как установить привязку, см. Стороны привязки.

Anchoreditor.png

Зазор

Свойство BorderSpacing[(зазор)] управляет минимальным количеством пространства вокруг элемента управления. Свойства:

  • Around: эта величина в пикселах, [которая] добавляется к [каждому значению] Left, Top, Right, Bottom [элемента управления].
  • Left: зазор в пикселах с левой стороны элемента управления
  • Top: зазор в пикселах сверху элемента управления
  • Right: зазор в пикселах с правой стороны элемента управления
  • Bottom: зазор в пикселах под элементом управления
  • InnerBorder: это зазор в пикселях, добавляется дважды к оптимальной ширине и высоте. Некоторые элементы управления переопределяют вычисление и игнорируют это свойство. Пример, где он работает, - TButton. С помощью InnerBorder вы можете сделать кнопку больше, чем необходимо.
  • CellAlignHorizontal: Этот [параметр] используется в табличных макетах, таких как ChildSizing.Layout=cclLeftToRightThenTopToBottom. Если элемент управления меньше ячейки таблицы, это свойство определяет, как выровнять элемент управления: слева ccaLeftTop, справа ccaRightBottom или посередине ccaCenter.
  • CellAlignVertical: так же, как CellAlignHorizontal, но для вертикального выравнивания.

Правила зазоров

  • [Значение] Around добавляется к зазору для Left,Top,Right,Bottom [элемента управления].
  • Зазор может быть еще больше, если элементы управления имеют ограничения, которые не позволяют [им] растягиваться.

Привязка к противоположной стороне

Например, правая сторона A к левой стороне B.

Используются обе границы A и B.

  • Горизонтальный зазор между двумя элементами управления (LeftControl, RightControl на [общем] родителе) он максимален[, если:]
    • LeftControl.BorderSpacing.Right + LeftControl.BorderSpacing.Around
    • RightControl.BorderSpacing.Left + RightControl.BorderSpacing.Around
    • Parent.ChildSizing.HorizontalSpacing
  • Вертикальный зазор работает аналогично: между двумя элементами управления (TopControl, BottomControl на [общем] родителе) он максимален[, если:]
    • TopControl.BorderSpacing.Bottom + TopControl.BorderSpacing.Around
    • BottomControl.BorderSpacing.Top + BottomControl.BorderSpacing.Around
    • Parent.ChildSizing.VerticalSpacing

Например:

  • если LeftControl.BorderSpacing.Right=3 и LeftControl.BorderSpacing.Around=4, то между двумя элементами управления должно быть не менее 7px
  • если RightControl.BorderSpacing.Left=4 и RightControl.BorderSpacing.Around=4, то пространство будет не менее 8px
  • если Parent.ChildSizing.HorizontalSpacing=10, тогда пространство будет не менее 10px

Привязка к этой же стороне

Например, правая сторона A к правой стороне B.

  • Задействуется только зазор у границы А.
  • Свойство Parent.ChildSizing.Horizontal/VerticalSpacing не используется.

Привязка к центру

Например, центр А привязан к центру В.

Не задействуются ни зазор, ни [свойство] Parent.ChildSizing.Horizontal/VerticalSpacing.

Пример

Общим примером является привязка Label'а к Edit'у.

BorderSpacing Anchors.png

Центр Label'а вертикально привязан к Edit'у. Левая сторона Edit'а привязана к правой стороне Label'а. Оба имеют BorderSpacing.Around=6. Это приводит к [наличию] зазора в 6px между Label'ом и Edit'ом и [наличию] зазора в 6px слева от Label'а, а также [наличию] зазора в 6px справа от Edit'а. [Также еще] существует 6 пикселей зазора выше и ниже Edit'а.

  • Зазор слева между элементом управления и его родителем (Label1 к GroupBox1) является максимальным, [когда]
    • Label1.BorderSpacing.Left + Label1.BorderSpacing.Around
    • GroupBox1.ChildSizing.LeftTopSpacing
  • Зазор справа между элементом управления и его родителем (Edit1 к GroupBox1) является максимальным, [когда]
    • Edit1.BorderSpacing.Right + Edit1.BorderSpacing.Around
    • GroupBox1.ChildSizing.RightBottomSpacing
  • Когда центр элемента управления привязан к другому элементу управления, например, вышеуказанный Label центрирован по вертикали к Edit1, тогда все [остальные] зазоры игнорируются.
  • Когда левая сторона элемента управления привязана к левой стороне другого элемента управления (т.е., они выровнены по левой стороне), то расстояние между обеими левыми сторонами является
    • Control1.BorderSpacing.Left + Control1.BorderSpacing.Around


Вот более сложный пример:

Borderspacing anchors2.png

Важные части кода lfm:

  object GroupBox1: TGroupBox
    AutoSize = True
    Caption = 'GroupBox1'
    TabOrder = 0
    object Label1: TLabel
      AnchorSideLeft.Control = GroupBox1
      AnchorSideTop.Control = Edit1
      AnchorSideTop.Side = asrCenter
      BorderSpacing.Around = 6
      Caption = 'Label1'
    end
    object Edit1: TEdit
      AnchorSideLeft.Control = Label1
      AnchorSideLeft.Side = asrBottom
      AnchorSideTop.Control = GroupBox1
      AnchorSideRight.Control = GroupBox1
      AnchorSideRight.Side = asrBottom
      Anchors = [akTop, akLeft, akRight]
      BorderSpacing.Around = 6
      TabOrder = 0
      Text = 'Edit1'
    end
    object Label2: TLabel
      AnchorSideLeft.Control = GroupBox1
      AnchorSideTop.Control = Edit1
      AnchorSideTop.Side = asrBottom
      BorderSpacing.Around = 6
      Caption = 'Label2'
    end
    object ComboBox1: TComboBox
      AnchorSideLeft.Control = Label2
      AnchorSideTop.Control = Label2
      AnchorSideTop.Side = asrBottom
      AnchorSideRight.Control = GroupBox1
      AnchorSideRight.Side = asrBottom
      Anchors = [akTop, akLeft, akRight]
      BorderSpacing.Right = 6
      TabOrder = 1
      Text = 'ComboBox1'
    end
    object CheckBox1: TCheckBox
      AnchorSideLeft.Control = GroupBox1
      AnchorSideTop.Control = ComboBox1
      AnchorSideTop.Side = asrBottom
      BorderSpacing.Around = 6
      Caption = 'CheckBox1'
      TabOrder = 2
    end
    object Label3: TLabel
      AnchorSideLeft.Control = GroupBox1
      AnchorSideTop.Control = Edit2
      AnchorSideTop.Side = asrCenter
      BorderSpacing.Around = 6
      Caption = 'Label3'
    end
    object Edit2: TEdit
      AnchorSideLeft.Control = Label3
      AnchorSideLeft.Side = asrBottom
      AnchorSideTop.Control = CheckBox1
      AnchorSideTop.Side = asrBottom
      BorderSpacing.Around = 6
      TabOrder = 3
      Text = 'Edit2'
    end
  end

Зазор и выравнивание

Зазор работает с выравниванием. В приведенном ниже примере есть Memo1 с Align=alTop и Memo2 с Align=alCLient.

  • Обычно два Memo заполняют весь GroupBox.
  • Но Memo1 имеет BorderSpacing.Around=10, так что вокруг Memo1 будет [присутствовать зазор в] 10 пикселей.
  • Memo2 имеет BorderSpacing.Top=20. Зазор между Memo1 и Memo2 будет максимальным, который [от Memo1 до] Memo2 [составит] 20px.
  • Memo2 также имеет и BorderSpacing.Right=50, поэтому справа от Memo2 имеется [зазор в] 50 пикселей.
  • GroupBox может задавать зазор по умолчанию для всех своих дочерних элементов управления через [значения свойств] ChildSizing.LeftRightSpacing/VerticalSpacing/HorizontalSpacing. В этом примере их нет (все [упомянутые значения равны] 0).

Borderspacing align1.png

Выравнивание (Aligned)

Свойство Align[(выравнивание)] работает почти так же, как в Delphi, и может использоваться для быстрого заполнения [какой-нибудь] области. Например, чтобы TListBox заполнил всю область своего родителя, установите ListBox1.Align=alClient. Значения выравнивания alTop, alBottom, alLeft и alRight будут размещать элементы управления по возможности без перекрытия [друг друга]. Это означает, что все элементы управления [свойства] Align [со значением] alLeft, alTop, alBottom, alRight не будут перекрываться, если есть достаточно места.

Алгоритм работает следующим образом:

  • Сначала все элементы управления с помощью [значения свойства] alTop помещаются в верхнюю часть клиентской области. Если имеется несколько элементов управления с помощью alTop, последний добавленный/перемещенный будет помещен выше всех. Алгоритм будет стараться избегать перекрытия и сохранять высоту элемента управления, пока ширина [всех элементов остается] максимальной. Стороны привязки левой, верхней и правой сторон игнорируются. Нижняя сторона привязки работает нормально. Зазор и [значение свойства] Parent.ChildSize просчитываются, поэтому вы можете задавать зазор вокруг каждого выровненного элемента управления.
  • Затем все элементы управления с помощью [значения свойства] alBottom помещаются в нижнюю часть клиентской области. В противном случае оно работает аналогично alTop.
  • Потом все элементы управления с помощью [значения свойства] alLeft помещаются слева от клиентской области между элементами управления alTop и alBottom. В противном случае оно работает аналогично alTop.
  • Затем все элементы управления с помощью [значения свойства] alRight помещаются справа от клиентской области между элементами управления alTop и alBottom. В противном случае оно работает аналогично alTop.
  • Если есть элемент управления с [установленным свойством] alClient, он заполнит оставшуюся [часть] клиентской [области].
  • Если имеется более одного элемента управления с [установленным свойством] alClient, они будут помещаться в одно и то же положение, перекрывая друг друга. Используйте свойство Visibility, чтобы определить, какой из них [будет] показан.

Autosize align.png

Выравнивание и зазоры

Величина [свойства] BorderSpacing и [свойства] ChildSize родителя применяется к выравниваемым элементам управления. [Элемент управления] memo ниже имеет Align=alClient.


--Zoltanleo 15:27, 11 October 2018 (CEST): Ох, уж эти языковые особенности. Автор, очевидно, имел в виду следующее: даже если у дочернего контрола выравнивание выставлено как Align=alClient, то он не будет заполнять собой контрол-родитель полностью, если его свойство BorderSpacing или свойство ChildSize родителя имеют ненулевое значение.



Autosize align borderspacing.png

Выравнивание и Привязка

Свободная сторона выровненного элемента управления (напр., правая сторона Align=alLeft) следует правилам привязки. Если привязка не установлена, тогда элемент управления будет сохранять [значение] своей Width. Если привязка установлена, то [значение] Width изменится.

Выравнивание и свойство AutoSize

Элемент управления, выровненный [посредством] alLeft или alRight, растягивается по вертикали и будет использовать свою ширину[, определенную на этапе проектирования формы]. Если [свойство] AutoSize установлено в true, то [значение параметра] Width будет оптимальной шириной. Кнопка снизу имеет Align=alRight и AutoSize=true.

Autosize align autosize.png

Выравнивание и свойство AutoSize родителя

Элемент управления, выровненный [посредством] alClient, заполняет оставшееся пространство. Родительский элемент управления со [значением свойства] AutoSize=true [при этом] будет растягиваться или сжиматься [так], чтобы впритирку умещать [на себе] свои дочерние элементы. Что произойдет, если вы объедините эти два свойства? Скажем, вы положили кнопку с Align=alClient на groupbox с AutoSize=true?

Autosize nested1.png

LCL использует предпочтительный размер кнопок и потому растягивает или сжимает groupbox:

Autosize nested2.png

alCustom

Это значение Align существует для пользовательских алгоритмов AutoSize и обрабатывается LCL почти как alNone. Элементы управления [с установленным значением] alCustom не перемещаются LCL, но [могут быть перемещены вашим] пользовательским элементом управления. [Для этого] вы должны переопределить [методы] CalculatePreferredSize и DoAutoSize.

Порядок расположения элементов управления с одинаковым [значением] Align

Элементы управления с одинаковым выравниванием добавляются в следующем порядке. Для alLeft элемент управления с наименьшим [значением] Left, для alTop - наименьшим [значением] Top, для alRight - наибольшая [величина] Left+Width, для alBottom - наибольшая [величина] Top+Height. Если два элемента управления имеют одну и ту же координату, последний из них одерживает победу (вызывая SetBounds или SetParent). Элементы управления могут переопределять CreateControlAlignList, чтобы изменить порядок или переопределить DoAlignChildControls для обработки всего Align.

Light bulb  Примечание: до [версии Lazarus] 0.9.29 элементы управления размещались в Z-порядке. В некоторых случаях это приводило к переупорядочиванию [элементов управления] и должно было быть исправлено. Существует одна несовместимость: если вы добавили несколько элементов управления с [выравниванием] alTop или alLeft без указания границ, [все] элементы управления будут помещены в [координаты] 0.0, и, следовательно, последние добавленные [окажутся] в выигрыше [там], где раньше [выигрывал] первый.



Разметка (Layout)

Строки, столбцы и строки

Вы можете выравнивать дочерние элементы управления в строках и столбцах, используя свойства ChildSize. Например:

  • ChildSizing.Layout=cclLeftToRightThenTopToBottom
  • ChildSizing.ControlsPerLine=3
  • AutoSize=false (не изменяет размеры, чтобы подстраиваться под дочерние элементы управления)

Autosize layout lefttoright.png

Свойство Layout по умолчанию [имеет значение] cclNone. Если вы установите Layout в другое значение, каждый дочерний элемент, имеющий нормальные привязки, будет выровнен. Нормальные привязки означают:

  • Anchors=[akLeft,akTop]
  • AnchorSide[akLeft].Control=nil
  • AnchorSide[akTop].Control=nil
  • Align=alNone

Значение cclLeftToRightThenTopToBottom будет помещать первый дочерний элемент управление вверху [и] слева, второй - [вверху и] правее, и так далее. Это - линия. Свойство ControlsPerLine определяет, когда начинается новая строка. В приведенном выше примере каждая линия (строка) имеет 3 элемента управления. Существует [всего] 12 элементов управления, поэтому есть 4 линии (строки), каждая из которых имеет [по] 3 элемента управления (столбцы). Если ControlsPerLine равен 0, это означает неограниченное количество элементов управления в строке - будет только одна строка со всеми дочерними элементами.

Вы можете видеть, что строки имеют разные размеры, каждая строка имеет размер самого большого элемента управления и что элементы управления изменяют размеры до ширины столбца. Между строками нет пробела. Пространство в изображении определяется используемой темой, а не [приходит] из LCL.

Фиксированные зазоры между строками и столбцами

Вы можете добавить зазоры между [строками и столбцами] этими свойствами:

  • ChildSizing.VerticalSpacing - зазор между строками
  • ChildSizing.HorizontalSpacing - зазор между столбцами
  • ChildSizing.LeftRightSpacing - зазор между левой и правой [стороной] всех столбцов
  • ChildSizing.TopBottomSpacing - зазор выше и ниже всех столбцов

Выше приведенный пример с [параметрами]:
ChildSizing.VerticalSpacing=6,
ChildSizing.HorizontalSpacing=15,
ChildSizing.LeftRightSpacing=30,
ChildSizing.TopBottomSpacing=10,
AutoSize=true

Autosize layout parentspacing.png

Кроме того, вы можете добавить зазор для каждого элемента управления отдельно с [помощью] его свойства BorderSpacing.

Растяжение

Приведенный выше пример изменяет размер GroupBox до необходимых размеров. Если ваш GroupBox имеет фиксированный размер или если он не является свободно изменяемым, например, если GroupBox должен заполнять всю ширину формы, то дочерние элементы должны растягиваться. Существует несколько режимов. Режим по умолчанию ChildSizing.EnlargeHorizontal=crsAnchorAligning запрещает что-либо растягивать. Зазоры с правой стороны не будут использоваться.

  • crsAnchorAligning - не использует дополнительные зазоры
  • crsScaleChilds - умножает ширину/высоту на тот же коэффициент
  • crsHomogeneousChildResize - добавляет к каждой ширине/высоте ту же величину
  • crsHomogeneousSpaceResize - добавляет к каждому зазору между дочерними элементами ту же величину

crsScaleChilds

ChildSizing.EnlargeHorizontal=crsScaleChilds
ChildSizing.EnlargeVertical=crsScaleChilds
AutoSize=false

Autosize layout scalechilds.png

Например, если значение ClientWidth вдвое больше необходимого, то каждый дочерний элемент будет в два раза больше.

crsHomogeneousChildResize

ChildSizing.EnlargeHorizontal=crsHomogeneousChildResize
ChildSizing.EnlargeVertical=crsHomogeneousChildResize
AutoSize=false

Autosize layout homogeneouschildresize.png

Например, если ClientWidth на 30 пикселей больше, чем нужно, то каждый дочерний элемент будет на 10 пикселей шире.

crsHomogeneousSpaceResize

ChildSizing.EnlargeHorizontal=crsHomogeneousSpaceResize
ChildSizing.EnlargeVertical=crsHomogeneousSpaceResize
AutoSize=false

Autosize layout homogeneousspaceresize.png

Например, если ClientWidth на 40 пикселов больше, чем необходимо, появится [дополнительно по] 10 пикселей зазора слева, справа и между каждым дочерним элементом.

Сжатие

Сжатие работает аналогично растяжению. Вы можете установить разные режимы, если для элементов управления недостаточно места. ShrinkHorizontal, ShrinkVertical.

Отдельные ячейки

В приведенных выше примерах все элементы управления изменялись одинаково, каждый заполнял всю ячейку. Ячейка - это пространство в определенной строке и столбце. Обычно элемент управления заполняет все пространство ячейки. Это можно изменить с помощью свойств BorderSpacing.CellAlignHorizontal и BorderSpacing.CellAlignVertical.

Например, установите BorderSpacing.CellAlignHorizontal пятой кнопки в caCenter [и] вы получите следующее:

Autosize layout cellhorizontal cacenter.png

Существует четыре возможных значения для CellAlign Horizontal/CellAlign Vertical:

  • caFill: дочерний элемент управления заполняет всю ширину (высоту) ячейки
  • caCenter: дочерний элемент управления использует свою оптимальную ширину (высоту) и будет центрироваться в ячейке
  • caLeftTop: дочерний элемент управления использует свою оптимальную ширину (высоту) и выравнивается в ячейке слева
  • caRightBottom: дочерний элемент управления использует свою оптимальную ширину (высоту) и будет в ячейке выравниваться по правому краю

Autosize layout cellalign.png

Пользовательская разметка через OnResize/OnChangeBounds

Иногда разметка LCL недостаточно [оптимальна]. В приведенном ниже примере показан GroupBox1 со списком ListBox1 и Memo1. ListBox1 должен заполнить одну треть пространства, Memo1 - забрать все остальное.

Autosize onresize.png

Всякий раз, когда размер GroupBox изменяется, ListBox1.Width должен составлять одну треть. Для этого определите событие OnResize для GroupBox1:

procedure TForm1.GroupBox1Resize(Sender: TObject);
begin
  ListBox1.Width := GroupBox1.ClientWidth div 3;
end;

Общая ошибка: неправильное событие OnResize

Не помещайте весь свой код изменения размера [элементов управления] в событие Form OnResize. Событие Form OnResize вызывается только при изменении размера формы. Дочерние элементы управления (напр., GroupBox1) изменятся позже, поэтому GroupBox1.ClientWidth все еще [будет] иметь старое значение во время события FormResize. Вы должны использовать событие GroupBox1.OnResize, чтобы реагировать на изменения GroupBox1.ClientWidth.

Общая ошибка: Width вместо ClientWidth, AdjustClientRect

[Свойство] Width - это размер [элемента управления], включая рамку. [Свойство] ClientWidth - это внутренняя ширина [элемента управления] без рамки. Некоторые элементы управления, такие как TPanel, рисуют еще одну рамку. Потом вы используете AdjustClientRect. Тот же пример, но вместо GroupBox1 на Panel1 находится ListBox1:

procedure TForm1.Panel1Resize(Sender: TObject);
var 
  r: TRect;
begin
  r := Panel1.ClientRect;
  Panel1.AdjustClientRect(r);
  ListBox1.Width := (r.Right - r.Left) div 3;
end;



Пользовательские элементы управления

Когда вы пишете свой собственный элемент управления, вы можете переопределить и тонко настроить многие части автомасштаба LCL.

SetBounds, ChangeBounds, DoSetBounds

SetBounds вызывается, когда устанавливаются свойства Left, Top, Width, Height, BoundsRect, или пользователь вызывает его напрямую. SetBounds обновляет BaseBounds и BaseParentClientSize, которые используются [механизмом] привязки для сохранения размеров. Например, загрузка формы с TMemo и lfm[-файлом], содержащим [значения параметров] Left и Width [компонента] TMemo, вызывает [метод] SetBounds для TMemo два раза. Когда пользователь распахивает окно, SetBounds вызывается для формы, но не для Memo, сохраняя BaseBounds Memo. Если Memo привязан справа, [значение параметра] Width [компонента] Memo изменяется на основе [кода методов] BaseBounds и BaseParentClientSize. Имейте в виду, что данные aLeft, aTop, aWidth, aHeight могут быть недействительными и будут изменены LCL перед применением. Delphi чаще всего называет SetBounds. SetBounds вызывает ChangeBounds с KeepBase=false.

ChangeBounds вызывается всякий раз, когда позиция или размер элемента управления задаются либо с помощью свойств, либо с помощью [механизма разметки] LCL. SetBounds вызывает внутри [себя] ChangeBounds с KeepBase=false, в то время как [механизма разметки] LCL вызывает его с KeepBase=true. Переопределение этого в коде может изменить предпочтительный размер или изменить размеры других элементов управления. Имейте в виду, что данные aLeft, aTop, aWidth, aHeight могут быть недействительными и будут изменены LCL перед применением. Вы можете вызвать эту функцию.

DoSetBounds - это функция низкого уровня для установки private-переменных FLeft, FTop, FWidth, FHeight. Не вызывайте эту функцию, [потому что] только LCL вызывает ее. Она также обновляет FClientWidth и FClientHeight соответственно. Переопределите ее, чтобы обновить содержимое разметки элемента управления, например полосы прокрутки. Как всегда: не рисуйте здесь, но вызывайте Invalidate и рисуйте в OnPaint или переопределите Paint.

DoAdjustClientRectChange вызывается LCL и интерфейсом LCL, когда ClientRect изменился, а Width и Height сохранились.

WMSize существует для совместимости с Delphi/VCL. Он вызывается интерфейсом LCL при каждом изменении границ.

AdjustClientRect

Метод AdjustClientRect может быть переопределен вашими пользовательскими элементами управления и влияет на [свойства] Align, ChildSizing.Layout и AnchorSides. Это не влияет на значение Left, Top и не влияет на нормальный [механизм] привязки (например, установку).

Когда вы хотите нарисовать свою собственную рамку, тогда дочерние элементы управления должны быть выровнены в этой рамке. Например, TPanel рисует рамку и уменьшает клиентскую область, переопределяя метод AdjustClientRect:

  TCustomPanel = class(TCustomControl)
  ...
  protected
    ...
    procedure AdjustClientRect(var aRect: TRect); override;
  ...

procedure TCustomPanel.AdjustClientRect(var aRect: TRect);
var
  BevelSize: Integer;
begin
  inherited AdjustClientRect(aRect);

  BevelSize := BorderWidth;
  if (BevelOuter <> bvNone) then
    inc(BevelSize, BevelWidth);
  if (BevelInner <> bvNone) then
    inc(BevelSize, BevelWidth);

  InflateRect(aRect, -BevelSize, -BevelSize);
end;

AdjustClientRect и выравнивание

AdjustClientRect может использоваться для уменьшения клиентской области, используемой всеми операциями [автоматической установки] размера. Например, TPanel использует AdjustClientRect для уменьшения клиентской области посредством [изменения величины] зазора:

Adjustclientrect align.png

[Кнопка] Button1 на скриншоте была создана со [значением] Align=alClient. Также [был] задействован [свойство панели] ChildSizing.Layout.

Когда [дочерний элемент] привязывается к родительскому элементу через [свойство] AnchorSides, также используется параметр AdjustClientRect:

  Button1.AnchorParallel(akTop,0,Button1.Parent);

Верхний край Button1 привязан к верхнему краю клиентской области родителя. Если AdjustClientRect добавляет 3px к [значению] Top, Button1.Top будет 3px (3 плюс 0).

Собственный AutoSize

Когда параметр AutoSize установлено значение true, элемент управления должен быть изменен до оптимального размера, если это возможно.

Предпочтительный размер

Новый размер выбирается [механизмом] LCL через [метод] GetPreferredSize, который вызывает CalculatePreferredSize [и] который можно переопределить. Например, давайте опишем TQuadrat, который является [наследником] TShape, но его высота должна быть равна его ширине:

  TQuadrat = class(TShape)
  protected
    procedure CalculatePreferredSize(var PreferredWidth,
        PreferredHeight: integer; WithThemeSpace: Boolean); override;
  end;
...
procedure TQuadrat.CalculatePreferredSize(var PreferredWidth,
  PreferredHeight: integer; WithThemeSpace: Boolean);
begin
  PreferredHeight:=Width;
end;

Метод CalculatePreferredSize получает два переменных параметра: PreferredWidth и PreferredHeight. Они по умолчанию равны 0, что означает: нет оптимального размера, поэтому LCL не изменяет размер. Вышеупомянутая функция устанавливает [свойство] PreferredHeight в текущее [свойство] Width. Логический параметр WithThemeSpace устарел и всегда [равен] false.

Важно: [метод] CalculatePreferredSize не должен изменять границы или любое другое значение элемента управления, которое может инициировать [механизм] autosize. Это создаст [бесконечный] цикл.

Вычисление PreferredWidth/Height может быть дорогостоящей [операцией]. Поэтому LCL кэширует результат до тех пор, пока для элемента управления не будет вызвана [процедура] InvalidatePreferredSize. В нашем примере PreferredHeight зависит от [параметра] Width, поэтому мы должны [вызвать] перерисовку [элемента управления], когда изменяется [параметр] Width:

  TQuadrat = class(TShape)
  protected
    ...
    procedure DoSetBounds(ALeft, ATop, AWidth, AHeight: integer); override;
  end;
...
procedure TQuadrat.DoSetBounds(ALeft, ATop, AWidth, AHeight: integer);
begin
  inherited DoSetBounds(ALeft, ATop, AWidth, AHeight);
  InvalidatePreferredSize;
  // Примечание: здесь [вызов метода] AdjustSize может быть опущен, поскольку LCL делает это после вызова DoSetBounds.
end;

LCL автоматически активирует [механизм] autosizing'а, когда границы изменяются, поэтому пример является полным.

По умолчанию TWinControl для реализации CalculatePreferredSize запрашивает виджетсет, который может вернуть оптимальную ширину и/или высоту. Каждый элемент управления может переопределять метод CalculatePreferredSize. Например, TImage переопределяет его и возвращает размер изображения. Если нет оптимальной ширины (высоты), возвращаемое значение [будет] равно 0, и LCL будет сохранять текущую [величину] Width (Height). Если 0 - допустимый размер для вашего контроля, вы должны присвоить флагу ControlStyle [значение] csAutoSize0x0 (ControlStyle:=ControlStyle+[csAutoSize0x0];). Примером может служить\элемент управления LCL TPanel.

AdjustSize

Когда предпочтительный размер зависит от нового свойства, то каждый раз, когда свойство изменяется, должен быть вызван [механизм] автоматического изменения размера. Например:

procedure TQuadrat.SetSubTitle(const AValue: string);
begin
  if FSubTitle = AValue then exit;
  FSubTitle := AValue;
  InvalidatePreferredSize;
  AdjustSize;
end;



Сокращение накладных расходов с помощью [методов] DisableAutoSizing, EnableAutoSizing

Начиная с [версии] Lazarus 0.9.29, существует новый алгоритм автоизменения размера, который уменьшает накладные расходы и допускает глубокие вложенные зависимости. До [версии] 0.9.28 [методы] DisableAlign/EnableAlign и Disable/EnableAutoSize работают только для одного элемента управления и его прямых дочерних контролов.

Каждый раз, когда вы меняете [какое-нибудь] свойство, LCL запускает [механизм] пересчета разметки:

Label1.Caption := 'A';  // первый перерасчет
Label2.Caption := 'B';  // второй перерасчет

Перерасчет будет возбуждать [механизм вызова] событий и отправки сообщений. Чтобы уменьшить накладные расходы, вы можете приостановить [механизм] автоизменения размера:

DisableAutoSizing;
try
  Label1.Caption := 'A';  // нет перерасчета
  Label2.Caption := 'B';  // нет перерасчета
finally
  EnableAutoSizing; // однократный перерасчет
end;

Вы должны уравновешивать вызовы Disable/EnableAutoSizing. Автоизменение размера начинается только тогда, когда вызывается EnableAutoSizing после соответствующего (ранее) [вызова] DisableAutoSizing.

Начиная с [версии Lazarus] 0.9.29, Disable/EnableAutoSize работает для всей формы. Это означает, что каждый вызов DisableAutoSizing приостанавливает автоизменение размера для всех элементов управления на этой форме. Если вы пишете свой собственный элемент управления, вы можете использовать следующее:

procedure TMyRadioGroup.DoSomething;
begin
  DisableAutoSizing;  // отключено автоизменение размера не только [для] TRadioGroup, но и [для] всей формы
  try
    // удаляем элементы ...
    // добавляем, тасуем элементы ...
    // меняем заголовки элементов ...
  finally
    EnableAutoSizing; // перерасчет
  end;
end;

К чему все это: вам не нужно [ни о чем] заботиться. Просто вызовите Disable/EnableAutoSizing.

Light bulb  Примечание: вот так неправильно:

Button1.DisableAutoSizing;
Label1.EnableAutoSizing; // неверно: каждый элемент управления имеет свой собственный 
                         //счетчик ссылок на автоизменение размера

DisableAutoSizing и границы формы

DisableAutoSizing имеет еще один полезный эффект при асинхронных менеджерах окон, которые вы [можете] встретить в системах Linux. Каждый раз, когда форма изменяет размер или перемещается, [ее] границы отправляются в widgetset (DoSendBoundsToInterface). Даже если форма не отображается, дескриптор изменяется. Менеджер окон зачастую рассматривает эти границы только в качестве предложения. У диспетчера окон есть своя логика, и часто используются только границы, переданные первыми. Второе, третье или дальнейшие перемещения могут быть проигнорированы. С помощью параметра DisableAutoSizing вы можете убедиться, что только последние границы отправляются в виджетсет, что делает границы формы более надежными [для отображения].



-- Zoltanleo 09:28, 16 October 2018 (CEST): Очевидно, имеется ввиду прерывистый механизм перерисовки границ окна, когда при перетаскивании его края мышью рисуется только рамка новой границы окна, а окончательная перерисовка происходит после отпускания кнопки мыши.




Пример: панель кнопок

В этом примере сочетаются несколько механизмов компоновки LCL для создания панели с тремя кнопками: кнопка Help слева и кнопки Ok и Cancel справа. Мы хотим, чтобы панель была внизу формы, заполняя всю [ее] ширину. Кнопки и панель автоматически изменяют размеры, чтобы соответствовать всем шрифтам и темам.

Шаг 1: Создайте панель и установите ее свойство Align в alBottom. Добавьте три TBitBtns.

Autosize example buttonpanel1.png

Установите в свойстве Kind BitBtns [соответственные] отображения глифов. Возможно, вам нужно будет установить свойство GlyphShowMode в gsmAlways[("отображать всегда")], чтобы увидеть их на своей платформе. Установите для свойства AutoSize [кнопок] BitBtn значение True, которое уменьшит/увеличит [ширину] кнопок для идеального заполнения [их] глифами и текстом. В зависимости от вашей темы и платформы вы можете заметить, что кнопки имеют разную высоту.

Autosize example buttonpanel2.png

Установите свойство Align ' кнопки справки в alLeft и установите для остальных двух кнопок свойство Align в alRight. Это увеличит кнопки по вертикали и переместит их в крайнее левое/правое. alLeft/alRight не влияют на ширину, поэтому кнопки используют свою предпочтительную ширину.

Autosize example buttonpanel3.png

Высота панели по-прежнему фиксирована. Теперь установите для панели свойство AutoSize в True. Панель теперь сжимается вертикально, чтобы соответствовать самой высокой кнопке.

Autosize example buttonpanel4.png

Кнопка Ok имеет короткий заголовок, поэтому кнопка очень маленькая. Установите для кнопки Constraints.MinWidth в значение 75. Теперь кнопка несколько расширится.

Autosize example buttonpanel5.png

Теперь добавьте некоторое пространство вокруг кнопок. Установите для панели ChildSizing.LeftTopSpacing/RightBottomSpacing/HorizontalSpacing в значение 6.

Autosize example buttonpanel6.png

Наконец, очистите Caption панели и установите ее 'BevelOuter в bvNone.

Autosize example buttonpanel7.png

Прокрутка

Некоторые элементы управления LCL, такие как TScrollBox, TForm' и TFrame, показывают полосы прокрутки, если дочерние элементы управления слишком велики, чтобы поместиться на scrollbox'е, форме или фрейме. Они наследуют это поведение от своего предка TScrollingWinControl.

Прокручиваемая логическая клиентская область элемента управления может быть больше, чем видимая клиентская область. Видимая клиентская область - это ClientRect. Она всегда начинается с [координат] 0,0 и его ширина и высота - это внутренняя область. Например, в TGroupBox - это размер области внутри фрейма. Итак, всегда верно следующее:

ClientWidth <= Width
ClientHeight <= Height

Логическая клиентская область определяется методом GetLogicalClientRect. По умолчанию она совпадает с ClientRect. Когда дочерний элемент управления привязан к правой стороне, он использует логическую клиентская область. TScrollingWinControl переопределяет этот метод и возвращает Range[(диапазон)] полос прокрутки, если [логическая клиентская область] больше, чем ClientRect. Range можно установить вручную или автоматически с помощью AutoScroll=true. Пример для AutoScroll=true:

Autoscroll all fit1.png

Верхняя кнопка имеет фиксированную ширину 200. Нижняя кнопка привязана к правой стороне панели. Поэтому дочерние элементы управления имеют предпочтительную ширину 200. Поскольку панель больше 200, логическая область клиента больше и нижняя кнопка расширяется.

Теперь панель сжата, так что ClientWidth становится меньше 200:

Autoscroll not fit1.png

Предпочтительная ширина по-прежнему равна 200, поэтому логического клиентская область теперь равна 200 и больше, чем видимая клиентская область. Нижняя кнопка имеет ширину 200, а на панели отображается горизонтальная полоса прокрутки.

Позиция прокрутки

Изменение положения полосы прокрутки не изменяет [значения] Left или Top любого дочернего элемента управления и не изменяет логическую клиентскую область, [а также] не влияет на автомасштабирование. Дочерние элементы управления лишь только виртуально перемещаются.

Прокрутка и автомасштаб

Когда AutoSize=true, LCL расширяет [родительский] элемент управления [так], чтобы разместить все его дочерние элементы управления, и никаких полос прокрутки не требуется. Если [родительский] элемент управления не может быть расширен, то (только) [тогда проявляется] вторичное действие AutoSize: перемещение дочерних элементов управления.

Пристыковка

Пристыковка использует описанные методы и свойства этой страницы, см. Docking.

Splitter

См. TSplitter.

TLabel.WordWrap

TLabel.WordWrap changes the behavior of the preferred size of the label. WordWrap=true requires that the Width of the label is fixed, for example by anchoring the left and right size of the label. The preferred height of the label is then computed by breaking the Caption into multiple lines.

DPI auto-adjustment and absolute layout auto-adjustment

Historically the LCL has been utilized mostly to design absolute layouts, despite the huge amount of options which Lazarus offers for flexible layouts, like Align, Anchors, etc, as described on the rest of this article. On top of that, it has also historically ignored the DPI of the target and instead utilized values in pixels to measure the left, top, width and height properties of controls. For desktop platforms and Windows CE this has worked reasonably ok, but with the advent of LCL support for Android this could no longer be ignored. Starts in Lazarus 0.9.31 the LCL can reinterprete the LCL absolute layout in pixels as a flexible grid. There are multiple modes to choose from and they will allow to reinterprete the pixel values as either really absolute, or as in being adjusted for the DPI or as in being considered simply a fraction of the form size.

It is important to note that these new rules affect only controls which are positioned without Align and with the most standard Anchors only.

In the case where the values will be adjusted for DPI, there is a new property: TCustomForm.DesignTimeDPI which should store the DPI value of the system where the form was designed. The positoning values will be expanded when the target DPI is larger then the design time DPI or reduced otherwise. The common value for desktop DPIs is 96 and is the default value given.

property DesignTimeDPI: Integer read FDesignTimeDPI write FDesignTimeDPI;

The way in which the layout is adjusted can be controlled with the property TApplication.LayoutAdjustmentPolicy

 TLayoutAdjustmentPolicy = (
   lapDefault,     // widgetset dependent
   lapFixedLayout, // A fixed absolute layout in all platforms
   lapAutoAdjustWithoutHorizontalScrolling, // Smartphone platforms use this one,
                                            // the x axis is stretched to fill the screen and
                                            // the y is scaled to fit the DPI
   lapAutoAdjustForDPI // For desktops using High DPI, scale x and y to fit the DPI
 );

And the following new methods in TControl allow to force a layout-autoadjustment in a particular control and all its children or to control how particular descendents of TControl react to this:

TControl = class
public
...
    procedure AutoAdjustLayout(AMode: TLayoutAdjustmentPolicy;
      const AFromDPI, AToDPI, AOldFormWidth, ANewFormWidth: Integer); virtual;
    function ShouldAutoAdjustLeftAndTop: Boolean; virtual;
    function ShouldAutoAdjustWidthAndHeight: Boolean; virtual;

LCL-CustomDrawn-Android will call AutoAdjustLayout when the screen rotates, for example.

More details

Many controls override TControl.DoAutoSize to perform the actual auto-sizing.

IMPORTANT: Many Delphi controls override this method and many call this method directly after setting some properties.

During handle creation not all interfaces can create complete Device Contexts which are needed to calculate things like text size.

That's why you should always call AdjustSize instead of DoAutoSize.

TControl.AdjustSize calls DoAutoSize in a smart fashion.

During loading and handle creation the calls are delayed.

This method initially does the same as TWinControl.DoAutoSize. But since DoAutoSize is commonly overriden by descendant components, it is not useful to perform all tests, which can result in too much overhead. To reduce this the LCL calls AdjustSize instead.

When setting AutoSize = true the LCL autosizes the control in width and height. This is one of the most complex parts of the LCL, because the result depends on nearly a hundred properties. Let's start simple:

The LCL will only autosize the Width (Height) if it is free to resize. In other words - the width is not autosized if:

  • the left and right side is anchored. You can anchor the sides with the Anchors property or by setting the Align property to alTop, alBottom or alClient.
  • the Width is bound by the Constraints properties. The Contraints can also be overriden by the widgetset. For example the winapi does not allow resizing the height of a combobox. And the gtk widgetset does not allow resizing the width of a vertical scrollbar.

Same for Height.

The new size is calculated by the protected method TControl.CalculatePreferredSize. This method asks the widgetset for an appropriate Width and Height. For example a TButton has preferred Width and Height. A TComboBox has only a preferred Height. The preferred Width is returned as 0 and so the LCL does not autosize the Width - it keeps the width unaltered. Finally a TMemo has no preferred Width or Height. Therefore AutoSize has no effect on a TMemo.

Some controls override this method. For example the TGraphicControl descendants like TLabel have no window handle and so cannot query the widgetset. They must calculate their preferred Width and Height themselves.

The widgetsets must override the GetPreferredSize method for each widget class that has a preferred size (Width or Height or both).

Parent.AutoSize

The above described the simple explanation. The real algorithm provides far more possibilities and is therefore far more complex.

Properties / Methods

  • Left
  • Top

If Parent<>nil then Left, Top are the pixel distance to the top, left pixel of the parent's client area (not scrolled). Remember the client area is always without the frame and scrollbars of the parent. For Delphi users: Some VCL controls like TGroupbox define the client area as the whole control including the frame and some not - the LCL is more consistent, and therefore Delphi incompatible. Left and Top can be negative or bigger than the client area. Some widgetsets define a minimum/maximum somewhere around 10.000 or more.

When the client area is scrolled the Left and Top are kept unchanged.

During resizing/moving Left and Top are not always in sync with the coordinates of the Handle object.

If Parent=nil then Left, Top depend on the widgetset and the window manager. Till Lazarus 0.9.25 this is typically the screen coordinate of the left,top of the client area of the form. This is Delphi incompatible. It is planned to change this to the Left, Top of the window.


Hint: Each time you change Left and Top the LCL moves instantly and recomputes the whole layout. If you want to change Left and Top use instead:

with Button1 do
  SetBounds(NewLeft, NewTop, Width, Height);
  • Width
  • Height

The Size in pixels must not be negative, and most widgetsets do not allow Width=0 and/or Height=0. Some controls on some platforms define a bigger minimum constraint in Constraints.MinInterfaceWidth/Height. Instead of sizing a control to Width=0 and/or Height=0, set Visible=false or Parent=nil. During resizing/moving Width and Height are not always in sync with the size of the Handle object.


  • BoundsRect

Same as Bounds(Left, Top, Width, Height).

Common newbie mistake:

BoundsRect.Left := 3; // WRONG: common newbie mistake

This has no effect, because reading BoundsRect is a function. It creates a temporary TRect on the stack. The above is the same as

var
  r: TRect;
begin
  r := BoundsRect; // fetch the bounds
  r.Left := 3;  // change a value on the stack
end;  // no change
  • ClientRect

Left and Top are always 0,0. Width and Height are the visible size in pixels of the client area. Remember the client area is without the frame and without scrollbars. In a scrollable client area the logical client area can be bigger than the visible.

  • ClientOrigin

Returns the screen coordinate of the topleft coordinate 0,0 of the client area. Note that this value is the position as stored in the interface and is not always in sync with the LCL. When a control is moved, the LCL sets the bounds to the desired position and sends a move message to the interface. It is up to the interface to handle moves instantly or queued.

  • LCLIntf.GetClientBounds

Returns the client bounds of a control. Like ClientRect, but Left and Top are the pixel distances to the control's left, top. For example on a TGroupBox the Left, Top are the width and height of the left and top frame border. Scrolling has no effect on GetClientBounds.

  • LCLIntf.GetWindowRect

After the call, ARect will be the control area in screen coordinates. That means, Left and Top will be the screen coordinate of the TopLeft pixel of the Handle object and Right and Bottom will be the screen coordinate of the BottomRight pixel.


  • FBaseBoundsLock: integer

Increased/Decreased by LockBaseBounds/UnlockBaseBounds. Used to keep FBaseBounds during SetBounds calls.


  • FBaseParentClientSize: TPoint

The Parent.ClientRect size valid for the FBaseBounds. FBaseBounds and FBaseParentClientSize are used to calculate the distance for akRight (akBottom). When the parent is resized, the LCL knows what distance to keep.


  • FBoundsRectForNewParent: TRect

When changing the Parent of a control the Handle is recreated and many things can happen. Especially for docking forms the process is too unreliable. Therefore the BoundsRect is saved. The VCL uses a similar mechanism.


  • fLastAlignedBounds: TRect

See TControl.SetAlignedBounds for an explanation. In short: It stops some circles between interface and LCL autosizing.


  • FLastChangebounds: TRect

Used to stop calling ChangeBounds with the same coordinates. This happens very often.


  • FLastDoChangeBounds: TRect

Used to avoid calling OnChangeBounds with the same coordinates. This reduces user defined autosizing.


  • FLastResizeClientHeight: integer
  • FLastResizeClientWidth: integer
  • FLastResizeHeight: integer
  • FLastResizeWidth: integer

Used to avoid calling OnResize with the same coordinates. This reduces user defined autosizing.


  • FLoadedClientSize: TPoint

During loading many things are delayed and many things are set and worse: in the wrong order. That's why SetClientWidth/SetClientHeight calls are stored and set at end of loading again. This way the LCL can restore the distances (e.g. akRight) used during designing.


  • FReadBounds: TRect

Same as FLoadedClientSize, but for SetLeft, SetTop, SetWidth, SetHeight.


  • procedure SetBoundsRectForNewParent(const AValue: TRect);

Used to set FBoundsRectForNewParent. See above.


  • procedure SetAlignedBounds(aLeft, aTop, aWidth, aHeight: integer); virtual;

As SetBounds but without changing the default sizes.


  • procedure SetInitialBounds(aLeft, aTop, aWidth, aHeight: integer); virtual;

A smart version of SetBounds, reducing overhead during creation and loading.


  • procedure UpdateBaseBounds(StoreBounds, StoreParentClientSize, UseLoadedValues: boolean); virtual;

Commit current bounds to base bounds.

  • procedure SetClientHeight(Value: Integer);
  • procedure SetClientSize(Value: TPoint);
  • procedure SetClientWidth(Value: Integer);

Exists for Delphi compatibility too. Resizes the control, to get the wanted ClientRect size.


  • procedure ChangeBounds(ALeft, ATop, AWidth, AHeight: integer); virtual;

This is the internal SetBounds. Applies constraints, updates base bounds, calls OnChangeBound, OnResize, locks bounds.


  • procedure DoSetBounds(ALeft, ATop, AWidth, AHeight: integer); virtual;

This really sets the FLeft, FTop, FWidth, FHeight private variables.


  • procedure SetBounds(aLeft, aTop, aWidth, aHeight: integer); virtual;

This is the standard procedure overriden by many Delphi controls. TWinControl overrides it too.

    • ignores calls when bounds are locked
    • lock the FBoundsRealized to avoid overhead to the interface during auto sizing.

ChangeBounds is not locked this way.


  • Function GetClientOrigin: TPoint; virtual;

Screen coordinate of Left, Top of client area.

  • Function GetClientRect: TRect; virtual;

Size of client area. (always Left=0, Top=0)

  • Function GetScrolledClientRect: TRect; virtual;

Visible client area in ClientRect.


  • function GetChildsRect(Scrolled: boolean): TRect; virtual;

Returns the Client rectangle relative to the control's Left, Top. If Scrolled is true, the rectangle is moved by the current scrolling values (for an example see TScrollingWincontrol).

  • function GetClientScrollOffset: TPoint; virtual;

Returns the scrolling offset of the client area.


  • function GetControlOrigin: TPoint; virtual;

Returns the screen coordinate of the topleft coordinate 0,0 of the control area. (The topleft pixel of the control on the screen) Note that this value is the position as stored in the interface and is not always in sync with the LCL. When a control is moved, the LCL sets the bounds to the wanted position and sends a move message to the interface. It is up to the interface to handle moves instantly or queued.

FAQ

Why does AutoSize not work in the designer properly?

In the designer controls can be dragged around and properties can be set in almost any order. To allow this and avoid possible conflicts, the AutoSizing is not updated on every change at design time.

Why does TForm.AutoSize not work when something changes?

See AutoSize and Forms

Do I need to call Application.ProcessMessages when creating lots of controls?

Application.ProcessMessages is called by the LCL automatically after every message (e.g. after every event like OnClick). Calling it yourself is only needed if the changes should become visible to the user immediately. For example:

<DELPHI>procedure TFrom.Button1Click(Sender: TObject); begin

 // change width of a control
 Button1.Width := Button1.Width + 10;
 // apply any needed changes and repaint the button
 Application.ProcessMessages;
 // do a lot of things that takes a long time
 ...
 // after leaving the OnClick the LCL automatically processes messages

end;</DELPHI>

When enabling Anchors at runtime the control resizes, does not use the current values. Why?

akBottom means: keep a distance to the bottom side of the parent. The distance to keep is defined by the base bounds. They are set at designtime or by runtime calls of SetBounds or UpdateBaseBounds.

For example: A TListBox (Anchors=[akLeft,aTop]) at designtime has a bottom distance of 100 pixel. And a button to enable/disable the akBottom of the TListBox. Now start the application and press the button to enable akBottom. The 100 pixel distance will be activated, because this was the last time the programmer defined the base bounds of the TListBox. All other resizes were done by the LCL and are irrelevant. The programmers base bounds rules. You can resize the form and the 100 pixel will be kept. In order to use the current bounds as base bounds use:

ListBox1.UpdateBaseBounds(true, true, false);
ListBox1.Anchors := ListBox1.Anchors + [akBottom];

Setting Anchors does not automatically call UpdateBaseBounds, because this would destroy the ability to change properties independently.

Resizing stringgrid columns in form's OnResize event does not work

The Form's OnResize is triggered when the Form's Width, Height, ClientWidth or ClientHeight changes. This is per se independent of TStringGrid. Of course it often happens that both the Form and the TStringGrid are resized. This means using forms OnResize will often work, but not always. A prominent example where it always fails is when the theme is changed and you have a TStringGrid in a TGroupBox in a TForm. When the theme changed the form size is kept, so no Forms OnResize is triggered. But the TGroupBox changed, so the TStringGrid should be resized.

Solution: Use the TStringGrid's OnResize.

Why is TForm.Width equal to TForm.ClientWidth?

Mattias' notes:

"There are historical and technical reasons.

For forms without parent the Clientwidth equals the Width, because the real Width including the frame was not available on Linux ten years ago (at least not reliable on various window managers). I didn't test, but I heard it is now possible with gtk2. The main problem is the autosizing, because this needs the frame sizes before the form is mapped to the screen. It might be, that this is only available after an event, which means that you have to wait for it, which means trouble for ShowModal. Changing this breaks compatibility with a lot of existing LCL code, but for this we added the LCLVersion in the lfm files.

For all other controls the rules is ClientWidth<=Width. The Width is the ClientWidth plus the widget frame. The question is if the scrollbars belong to the frame. I would say yes and it was implemented that way some time ago. Apparently this has changed. See the current cursor problem on synedit."


I get an infinite loop / How to debug autosizing?

Here are some notes, what other users made wrong and how to find out:

Differences to Delphi

For Delphi users: Please read: Difference to Delphi

Overriding a LCL method and triggering a recompute

You override a method and tell the LCL to compute again, even if nothing changed. Check for AdjustSize, Realign, AlignControls, InvalidatePreferredSize. For example:

procedure TMainForm.SetBounds(ALeft, ATop, AWidth, AHeight: integer);
begin
  // This will create an endless loop
  InvalidatePreferredSize;

  // This will create an endless loop too:
  OtherComponent.Left:=10;
  OtherComponent.Left:=20;

  inherited SetBounds(ALeft, ATop, AWidth, AHeight);
end;

Explanation: The SetBounds method is called often, even without changing anything. For example a "Left:=30;" will do so.

Remedy: Check for changes:

procedure TMainForm.SetBounds(ALeft, ATop, AWidth, AHeight: integer);
begin
  if (Left <> ALeft) or (Top <> ATop) or (Width <> AWidth) or (Height <> AHeight) then
    InvalidatePreferredSize;
  inherited SetBounds(ALeft, ATop, AWidth, AHeight);
end;

TWinControl.AlignControls

procedure AlignControls(aControl: TControl; var aRect: TRect); override;

AlignControls moves and sizes all child controls. The LCL implementation ignores controls with Align=alCustom.

The parameter aControl: TControl is kept for VCL compatibility. The LCL always passes nil. It gives aControl precedence when applying the Align property. When you have for example two controls A,B with Align=alLeft then the one with the lower Left is put left most. If both have the same Left then creation order wins. Now imagine you want to switch both controls A,B in the designer. You drag B to the left. This results in setting B.Left to 0. Now AlignControls starts and find both A.Left=0 and B.Left=0. Normally A would win. To let B win the VCL calls AlignControls(B,r). So aControl is the last moved. Contrary to the VCL the LCL allows to combine multiple layout changes without recomputing on every step. The LCL keeps track of the last moved controls in TWinControl.fAlignControls and applies the order in TWinControl.CreateControlAlignList. The aControl parameter is always nil.

See TWinControl.CreateControlAlignList.

OnResize/OnChangeBounds conflict with LCL properties

You set bounds that bite the LCL properties. For example a TLabel.AutoSize is true by default. If you set the Label1.Width in an OnResize event, the LCL will recompute, resize the Label1 and call the OnResize again. Start your application in the debugger and reproduce the bug. When it enters the loop, pause the application and see the call stack. If you see one of your events or your methods start searching there. For example:

procedure TMainForm.FormResize(Sender: TObject);
begin
  // if Button1 is anchored or AutoSize=true then the following might create an endless loop:
  Button1.Width:=100;
end;

LCL interface bug, custom widget

Sometimes the LCL interface or your custom control has a bug and undoes the LCL bounds. Compile the LCL with -dVerboseIntfSizing. This will write the communication between LCL and the LCL interface. If a widget does not allow free resizing, it must tell the LCL via the Constraints. Search for SetInterfaceConstraints in the various LCL interfaces and TWSControlClass.ConstraintWidth/Height.

alCustom

You use alCustom. This was only half implemented in earlier Lazarus versions, but some clever programmers used it to create some special effects. Now it is implemented and your program does not work anymore. Please see here what alCustom does: alCustom.

See also