Autosize / Layout/ru
│
English (en) │
русский (ru) │
中文(中国大陆) (zh_CN) │
中文(臺灣) (zh_TW) │
Введение
LCL может автоматически изменять размер и положение элемента управления, поэтому он адаптируется к изменениям шрифта, темы и текста или другого содержимого. Если вы хотите запустить программу на нескольких платформах, или если ваши подписи доступны на нескольких языках, то ваши средства управления должны правильно адаптироваться к своей среде. LCL позволяет не только сделать быстрый начальный дизайн (перемещение элементов управления на форму с помощью мыши), но и позже установить несколько ключевых свойств, которые сделают регуляторы автоматически адаптироваться впоследствии измененным содержанием и т.д.
- Фиксированный дизайн (Fixed design): это значение по умолчанию при размещении элемента управления в дизайнере форм. Положение элемента управления фиксируется относительно его родителя. Размер и положение элемента управления (Left, Top) полностью изменяются программистом. Вы можете перемещать элемент с помощью мыши и изменять его размер свободно.
- Выравнивание (Aligned): выравненные элементы управления заполняют оставшееся пространство родителя вверху, снизу, слева или справа, или заполняют всё оставшееся пространство.
- Привязка (Anchored): вы можете закрепить стороны элемента управления (слева, сверху, справа, снизу) с его родителем или с другим элементот управления. Закрепление: LCL попытается оставаться на таком же расстоянии от точки привязки.
- Разметка (Layout): элементы управления могут быть автоматически выровнены по строкам и столбцам (например, TRadioGroup)
- Пользовательская разметка через OnResize/OnChangeBounds: можно выровнять элементы управления самостоятельно в коде, используя события OnResize и OnChangeBounds.
- Пользовательские элементы управления: написав собственные элементы управления, вы можете переопределить почти каждое поведение LCL как вы хотите.
Правила приоритета
- Constraints (Ограничения)
- Align (Выравнивание)
- Anchors(Привязки)
- ChildSizing.Layout
- AutoSize (Автомасштаб элемента)
- События 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, таким образом изменение надписи на кнопке не изменит размеры кнопки. При установке значения 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
изменяется. В LCLAutoSize
всегда активен. Delphi позволяет изменить размерTLabel
, который имеет AutoSize = True, LCL - нет.- Скрытые управления не изменяют размер автоматически.
- Изменение размера элемента управления не изменяет размер/расположение заякоренных(привязанных) дочерних элементов управления во время загрузки. Имейте в виду, что конструкторы элементов управления можно вызвать во время загрузки. При использовании
akRight
,akBottom
якоря с установленнымиAnchorSides
иBorderSpacing
сохраняют правильное расстояние.
Автомасштаб и изменение размера элемента управления
С помощью свойства кнопки AutoSize=false
задаются фиксированный размер по умолчанию.
При установке AutoSize=true
для каждой кнопки, кнопки растягиваются (или сжимаются) в соответствии с текстом и темой.
AutoSize
не сжимает элемент управления до минимально возможного размера, как вы можете видеть по кнопке OK. Он использует метод GetPreferredSize
элемента управления, который вызывает метод CalculatePreferredSize
. По умолчанию реализация TWinControl
запрашивает виджетсет, который может иметь оптимальную ширину или высоту. Каждый элемент управления может переопределять метод CalculatePreferredSize
. Например, TImage
переопределяет его и возвращает размер изображения. Если нет оптимальной ширины (высоты), возвращаемое значение равно 0, и LCL будет хранить ширину(высоту) элемента управления (если не установлен флаг ControlStyle = csAutoSize
, который в настоящее время используется только TPanel
).
TWinControl
вычисляет размер всех своих дочерних элементов управления и использует это для вычисления своего оптимального размера.
Когда элемент управления привязан как слева, так и справа, его ширина фиксирована. Например, со значением привязки Align=alTop
элемент управления привязан слева и справа, и соответствует ширине Родителя. Если Parent.AutoSize
имеет значение true, тогда Родитель будет использовать оптимальную ширину элемента управления для вычисления своей собственной оптимальной ширины, и, таким образом, размер элемента управления будет изменен до его оптимальной ширины. См. Выравнивание и свойство AutoSize. Если значение оптимальной ширины недоступно, используются последние установленные границы (BaseBounds или ReadBounds). Если никаких ограничений не было установлено, используется метод GetControlClassDefaultSize
. То же самое для свойства Height
и привязки сверху и снизу.
Ограничения применяются всегда и имеют приоритет.
Автомасштаб и перемещение дочерних элементов управления
Когда AutoSize=false, вы можете размещать и перемещать элементы управления свободно:
Когда AutoSize=true, дочерние элементы управления с фиксированным положением перемещаются с подгонкой.
Прим.переводчика: на самом деле, перемещаются границы родительского элемента управления так, чтобы дочерние элементы управления с фиксированной относительно друг друга позицией полностью умещались на родительском. При этом родительский элемент управления будет иметь минимально возможный размер. Таким образом, кнопки "перемещаются" относительно верхнего левого угла родительского элемента управления.
Обе кнопки на панели были перемещены влево и кверху на одно и то же значение так, чтобы сверху и слева не оставалось свободного пространства. Если будут установлены свойства 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);
Привязка сторон (Anchoring)
Элементы управления для привязки к четырем сторонам имеют свойство 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 увеличивается в размере, расстояние до привязанной стороны сохраняется:
Без akLeft и без akRight их центры масштабируются:
На заметку:
- Загрузка формы похожа на один большой SetBounds. Во время загрузки свойства задаются с использованием значений , сохраненных в файле формы lfm. Имейте в виду, что из-за предков и фреймов может быть несколько файлов lfm. После загрузки LCL активирует autosizing. Привязанные контролы используют ограничение в конце загрузки. Любой шаг между ними игнорируется.
- Для пользовательских контролов часто лучше устанавливать AnchorSides вместо Anchors.
Изменение размера привязанного элемента управления
При изменении ширины привязанного элемента управления, например, через Object Inspector или через код Button1.Width:=3, вы можете увидеть разницу между привязкой к "родителю" и привязкой к "nil". Привязка к родителю будет изменять размер и перемещать Button1, а привязка к nil будет только изменять размер. Например:
Привязка к nil
Кнопка Button1 привязана [akTop,akRight], AnchorSide[akRight].Control=nil
Установка ширины на меньшее значение приведет к сужению кнопки, сохраняя значение Left кнопки, увеличивая расстояние от правой стороны кнопки до правого края.
При изменении размера Groupbox кнопка сохранит новое расстояние до правого края.
Объяснение: установка значения Width кнопки эквивалентна вызову SetBounds(Left,Top,NewWidth,Height). Вот почему значение Left сохраняется. Это совместимо с поведением контролов в Delphi.
Привязка к родителю
Кнопка Button1 привязана [akTop,akRight], AnchorSide[akRight].Control=Button1.Parent
Установка значения Width на меньшее значение приведет к сужению кнопки, сохранив расстояние от ее правой границы до края компонента-родителя и изменив значение Left кнопки.
Привязка к соседу
Вы можете привязываться к соседним элементам управления. Следующий пример показывает, что:
- вы можете привязать левый край label'а к левому краю кнопки
- привязать верхний край label'а к нижнему краю кнопки
- привязать центр label'а к центру кнопки
Подробнее о том, как установить привязку, см. Стороны привязки.
Зазор
Свойство 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'у.
Центр 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
Вот более сложный пример:
Важные части кода 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).
Выравнивание (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, чтобы определить, какой из них будет показан.
Выравнивание и зазоры
Величина свойства BorderSpacing и свойства ChildSize родителя применяется к выравниваемым элементам управления. Элемент управления memo ниже имеет Align=alClient.
Прим.переводчика: Ох, уж эти языковые особенности. Автор, очевидно, имел в виду следующее: даже если у дочернего контрола выравнивание выставлено как Align=alClient, то он не будет заполнять собой контрол-родитель полностью, если его свойство BorderSpacing или свойство ChildSize родителя имеют ненулевое значение.
Выравнивание и Привязка
Свободная сторона выровненного элемента управления (напр., правая сторона Align=alLeft) следует правилам привязки. Если привязка не установлена, тогда элемент управления будет сохранять значение своей Width. Если привязка установлена, то значение Width изменится.
Выравнивание и свойство AutoSize
Элемент управления, выровненный посредством alLeft или alRight, растягивается по вертикали и будет использовать свою ширину, определенную на этапе проектирования формы. Если свойство AutoSize установлено в true, то значение параметра Width будет оптимальной шириной. Кнопка снизу имеет Align=alRight и AutoSize=true.
Выравнивание и свойство AutoSize родителя
Элемент управления, выровненный посредством alClient, заполняет оставшееся пространство. Родительский элемент управления со значением свойства AutoSize=true при этом будет растягиваться или сжиматься так, чтобы впритирку умещать на себе свои дочерние элементы. Что произойдет, если вы объедините эти два свойства? Скажем, вы положили кнопку с Align=alClient на groupbox с AutoSize=true?
LCL использует предпочтительный размер кнопок и потому растягивает или сжимает groupbox:
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.
Разметка (Layout)
Строки, столбцы и строки
Вы можете выравнивать дочерние элементы управления в строках и столбцах, используя свойства ChildSize. Например:
- ChildSizing.Layout=cclLeftToRightThenTopToBottom
- ChildSizing.ControlsPerLine=3
- AutoSize=false (не изменяет размеры, чтобы подстраиваться под дочерние элементы управления)
Свойство 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
Кроме того, вы можете добавить зазор для каждого элемента управления отдельно с [помощью] его свойства BorderSpacing.
Растяжение
Приведенный выше пример изменяет размер GroupBox до необходимых размеров. Если ваш GroupBox имеет фиксированный размер или если он не является свободно изменяемым, например, если GroupBox должен заполнять всю ширину формы, то дочерние элементы должны растягиваться. Существует несколько режимов. Режим по умолчанию ChildSizing.EnlargeHorizontal=crsAnchorAligning запрещает что-либо растягивать. Зазоры с правой стороны не будут использоваться.
- crsAnchorAligning - не использует дополнительные зазоры
- crsScaleChilds - умножает ширину/высоту на тот же коэффициент
- crsHomogeneousChildResize - добавляет к каждой ширине/высоте ту же величину
- crsHomogeneousSpaceResize - добавляет к каждому зазору между дочерними элементами ту же величину
crsScaleChilds
ChildSizing.EnlargeHorizontal=crsScaleChilds
ChildSizing.EnlargeVertical=crsScaleChilds
AutoSize=false
Например, если значение ClientWidth вдвое больше необходимого, то каждый дочерний элемент будет в два раза больше.
crsHomogeneousChildResize
ChildSizing.EnlargeHorizontal=crsHomogeneousChildResize
ChildSizing.EnlargeVertical=crsHomogeneousChildResize
AutoSize=false
Например, если ClientWidth на 30 пикселей больше, чем нужно, то каждый дочерний элемент будет на 10 пикселей шире.
crsHomogeneousSpaceResize
ChildSizing.EnlargeHorizontal=crsHomogeneousSpaceResize
ChildSizing.EnlargeVertical=crsHomogeneousSpaceResize
AutoSize=false
Например, если ClientWidth на 40 пикселов больше, чем необходимо, появится [дополнительно по] 10 пикселей зазора слева, справа и между каждым дочерним элементом.
Сжатие
Сжатие работает аналогично растяжению. Вы можете установить разные режимы, если для элементов управления недостаточно места. ShrinkHorizontal, ShrinkVertical.
Отдельные ячейки
В приведенных выше примерах все элементы управления изменялись одинаково, каждый заполнял всю ячейку. Ячейка - это пространство в определенной строке и столбце. Обычно элемент управления заполняет все пространство ячейки. Это можно изменить с помощью свойств BorderSpacing.CellAlignHorizontal и BorderSpacing.CellAlignVertical.
Например, установите BorderSpacing.CellAlignHorizontal пятой кнопки в caCenter [и] вы получите следующее:
Существует четыре возможных значения для CellAlign Horizontal/CellAlign Vertical:
- caFill: дочерний элемент управления заполняет всю ширину (высоту) ячейки
- caCenter: дочерний элемент управления использует свою оптимальную ширину (высоту) и будет центрироваться в ячейке
- caLeftTop: дочерний элемент управления использует свою оптимальную ширину (высоту) и выравнивается в ячейке слева
- caRightBottom: дочерний элемент управления использует свою оптимальную ширину (высоту) и будет в ячейке выравниваться по правому краю
Пользовательская разметка через OnResize/OnChangeBounds
Иногда разметка LCL недостаточно [оптимальна]. В приведенном ниже примере показан GroupBox1 со списком ListBox1 и Memo1. ListBox1 должен заполнить одну треть пространства, Memo1 - забрать все остальное.
Всякий раз, когда размер 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 для уменьшения клиентской области посредством [изменения величины] зазора:
[Кнопка] 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.
Примечание: вот так неправильно:
Button1.DisableAutoSizing;
Label1.EnableAutoSizing; // неверно: каждый элемент управления имеет свой собственный
//счетчик ссылок на автоизменение размера
DisableAutoSizing и границы формы
DisableAutoSizing имеет еще один полезный эффект при асинхронных менеджерах окон, которые вы [можете] встретить в системах Linux. Каждый раз, когда форма изменяет размер или перемещается, [ее] границы отправляются в widgetset (DoSendBoundsToInterface). Даже если форма не отображается, дескриптор изменяется. Менеджер окон зачастую рассматривает эти границы только в качестве предложения. У диспетчера окон есть своя логика, и часто используются только границы, переданные первыми. Второе, третье или дальнейшие перемещения могут быть проигнорированы. С помощью параметра DisableAutoSizing вы можете убедиться, что только последние границы отправляются в виджетсет, что делает границы формы более надежными [для отображения].
Прим.перев.: очевидно, имеется ввиду прерывистый механизм перерисовки границ окна, когда при перетаскивании его края мышью рисуется только рамка новой границы окна, а окончательная перерисовка происходит после отпускания кнопки мыши.
Пример: панель кнопок
В этом примере сочетаются несколько механизмов компоновки LCL для создания панели с тремя кнопками: кнопка Help слева и кнопки Ok и Cancel справа. Мы хотим, чтобы панель была внизу формы, заполняя всю [ее] ширину. Кнопки и панель автоматически изменяют размеры, чтобы соответствовать всем шрифтам и темам.
Шаг 1: Создайте панель и установите ее свойство Align в alBottom. Добавьте три TBitBtns.
Установите в свойстве Kind BitBtns [соответственные] отображения глифов. Возможно, вам нужно будет установить свойство GlyphShowMode в gsmAlways[("отображать всегда")], чтобы увидеть их на своей платформе. Установите для свойства AutoSize [кнопок] BitBtn значение True, которое уменьшит/увеличит [ширину] кнопок для идеального заполнения [их] глифами и текстом. В зависимости от вашей темы и платформы вы можете заметить, что кнопки имеют разную высоту.
Установите свойство Align ' кнопки справки в alLeft и установите для остальных двух кнопок свойство Align в alRight. Это увеличит кнопки по вертикали и переместит их в крайнее левое/правое. alLeft/alRight не влияют на ширину, поэтому кнопки используют свою предпочтительную ширину.
Высота панели по-прежнему фиксирована. Теперь установите для панели свойство AutoSize в True. Панель теперь сжимается вертикально, чтобы соответствовать самой высокой кнопке.
Кнопка Ok имеет короткий заголовок, поэтому кнопка очень маленькая. Установите для кнопки Constraints.MinWidth в значение 75. Теперь кнопка несколько расширится.
Теперь добавьте некоторое пространство вокруг кнопок. Установите для панели ChildSizing.LeftTopSpacing/RightBottomSpacing/HorizontalSpacing в значение 6.
Наконец, очистите Caption панели и установите ее 'BevelOuter в bvNone.
Прокрутка
Некоторые элементы управления LCL, такие как TScrollBox, TForm' и TFrame, показывают полосы прокрутки, если дочерние элементы управления слишком велики, чтобы поместиться на scrollbox'е, форме или фрейме. Они наследуют это поведение от своего предка TScrollingWinControl.
Прокручиваемая логическая клиентская область элемента управления может быть больше, чем видимая клиентская область. Видимая клиентская область - это ClientRect. Она всегда начинается с [координат] 0,0 и его ширина и высота - это внутренняя область. Например, в TGroupBox - это размер области внутри фрейма. Итак, всегда верно следующее:
ClientWidth <= Width
ClientHeight <= Height
Логическая клиентская область определяется методом GetLogicalClientRect. По умолчанию она совпадает с ClientRect. Когда дочерний элемент управления привязан к правой стороне, он использует логическую клиентская область. TScrollingWinControl переопределяет этот метод и возвращает Range[(диапазон)] полос прокрутки, если [логическая клиентская область] больше, чем ClientRect. Range можно установить вручную или автоматически с помощью AutoScroll=true. Пример для AutoScroll=true:
Верхняя кнопка имеет фиксированную ширину 200. Нижняя кнопка привязана к правой стороне панели. Поэтому дочерние элементы управления имеют предпочтительную ширину 200. Поскольку панель больше 200, логическая область клиента больше и нижняя кнопка расширяется.
Теперь панель сжата, так что ClientWidth становится меньше 200:
Предпочтительная ширина по-прежнему равна 200, поэтому логического клиентская область теперь равна 200 и больше, чем видимая клиентская область. Нижняя кнопка имеет ширину 200, а на панели отображается горизонтальная полоса прокрутки.
Позиция прокрутки
Изменение положения полосы прокрутки не изменяет [значения] Left или Top любого дочернего элемента управления и не изменяет логическую клиентскую область, [а также] не влияет на автомасштабирование. Дочерние элементы управления лишь только виртуально перемещаются.
Прокрутка и автомасштаб
Когда AutoSize=true, LCL расширяет [родительский] элемент управления [так], чтобы разместить все его дочерние элементы управления, и никаких полос прокрутки не требуется. Если [родительский] элемент управления не может быть расширен, то (только) [тогда проявляется] вторичное действие AutoSize: перемещение дочерних элементов управления.
Пристыковка
Пристыковка использует описанные методы и свойства этой страницы, см. Docking.
Splitter
См. TSplitter.
TLabel.WordWrap
TLabel.WordWrap[(перенос слов)] изменяет поведение предпочтительного размера [компонента] label. WordWrap=true требует, чтобы [величина] Width [компонента] label была фиксированной, например, путем привязки левого и правого размера [компонента] label. Предпочтительная высота label затем вычисляется путем разбиения [своства] Caption на несколько строк.
Автоматическая настройка DPI и автоматическая настройка статической разметки
Исторически LCL использовался в основном для создания статической разметки, несмотря на огромное количество опций, которые Lazarus предлагает для гибкой разметки макетов, таких как Align, Anchors и т.д., которые описаны в остальной части этой статьи. Кроме того, он также исторически игнорировал DPI целевого [рабочего стола] и вместо этого использовал значения в пикселях для измерения свойств слева, сверху, ширины и высоты элементов управления. Для настольных платформ и Windows CE это работает нормально, но с появлением поддержки LCL для Android это больше нельзя игнорировать. Начиная с Lazarus 0.9.31, LCL может пересчитывать статическую разметку LCL в пикселях [на разметку] в виде гибкой сетки. Существует выбор нескольких режимов, [которые] позволяют пересчитывать значения пикселей как в абсолютных [величинах], так и в корректируемых для DPI, или как считающихся просто частью размера формы.
Важно отметить, что эти новые правила влияют только на элементы управления, которые расположены без выравнивания и только с большинством стандартных привязок.
В случае, когда значения будут корректироваться для DPI, появляется новое свойство: TCustomForm.DesignTimeDPI, которое должно хранить значение DPI системы, в которой была создана форма. Значения позиционирования будут расширены, когда DPI целевого [рабочего стола] будет больше, чем время DPI времени разработки или уменьшено в противном случае. Обычное значение DPI для рабочего стола - 96, и это значение, заданное по умолчанию.
property DesignTimeDPI: Integer read FDesignTimeDPI write FDesignTimeDPI;
Способ настройки разметки можно контролировать с помощью свойства TApplication.LayoutAdjustmentPolicy
TLayoutAdjustmentPolicy = (
lapDefault, // Зависимость от виджетсета
lapFixedLayout, // Фиксированная абсолютная разметка на всех платформах
lapAutoAdjustWithoutHorizontalScrolling, // Это используется платформой для смартфонов,
// ось x растягивается, чтобы заполнить экран и
// y масштабируется, чтобы соответствовать DPI
lapAutoAdjustForDPI // Для рабочих столов с использованием High DPI, x и y масштабируются соответственно DPI
);
Следующие новые методы в TControl позволяют [задать] принудительную автонастройку разметки в конкретном контроле и во всех его дочерних элементах, или контролировать, как реагируют на это отдельные потомки TControl:
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 будет вызывать AutoAdjustLayout, например, когда экран вращается.
Подробнее
Многие элементы управления переопределяют TControl.DoAutoSize для выполнения фактического автомасштабирования.
ВАЖНО: Многие элементы управления Delphi переопределяют этот метод, и многие вызывают этот метод напрямую после установки некоторых свойств.
Во время создания дескриптора не все интерфейсы могут создавать полные Device Contexts[(контексты устройств)], которые необходимы для вычисления таких вещей, как размер текста.
Вот почему вы всегда должны называть AdjustSize вместо DoAutoSize.
TControl.AdjustSize вызывает DoAutoSize умным способом.
Во время загрузки и создания дескриптора вызовы задерживаются.
Этот метод изначально делает то же самое, что и TWinControl.DoAutoSize. Но поскольку DoAutoSize обычно переопределяется компонентами-потомками, нецелесообразно выполнять все проверки, которые могут привести к слишком большим издержкам. Чтобы уменьшить это, LCL вызывает AdjustSize.
При установке AutoSize=true LCL автомасштабирует элемент управления по ширине и высоте. Это одна из самых сложных частей LCL, потому что результат зависит от почти сотен свойств. Начнем с простого:
LCL будет только автомасштабировать Width (Height), если [это свойство] свободно для изменения величины. Другими словами - ширина не автомасштабируется, если:
- левая и правая стороны привязаны. Вы можете привязать стороны с [помощью] свойства Anchors или установить свойство Align в alTop, alBottom или alClient.
- [параметр] Width ограничен [значением] свойства Constraints. [Свойство] Constraints также может быть переопределено виджетсетом. Например, winapi не позволяет изменять размер выпадающего списка. Или gtk widgetset не позволяет изменять ширину вертикальной полосы прокрутки.
То же [справедливо и для свойства] Height.
Новый размер рассчитывается protected-методом TControl.CalculatePreferredSize. Этот метод запрашивает виджетсет для [получения] подходящих Width и Height. Например, TButton имеет предпочтительные ширину и высоту. TComboBox имеет только предпочтительную высоту. [У него] предпочтительная ширина возвращается как 0, и поэтому LCL не автомасштабирует ширину - [LCL] сохраняет ширину неизменной. Наконец, у TMemo нет предпочтительной ширины или высоты. Поэтому [параметр] AutoSize не влияет на TMemo.
Некоторые элементы управления перекрывают этот метод. Например, потомки TGraphicControl, такие как TLabel, не имеют дескриптора окна и поэтому не могут запрашивать виджетсет. Они должны сами рассчитать их предпочтительную ширину и высоту.
Виджетсеты должны переопределять метод GetPreferredSize для каждого класса виджетов, который имеет предпочтительный размер (ширина или высота, или оба).
Parent.AutoSize
Выше описанное [дает] простое объяснение. Реальный алгоритм [функционально] предусматривает гораздо больше возможностей и, следовательно, гораздо более сложен.
Свойства / Методы
- Left
- Top
Если Parent<>nil, то [значения] Left, Top - это расстояние в пикселях до верхнего левого пикселя клиентской области родителя (не прокручивается). Помните, что клиентская область - это всегда [область] без рамки и полосы прокрутки родителя. Для пользователей Delphi: некоторые элементы управления VCL, такие как TGroupbox, определяют клиентскую область как весь элемент управления, включая рамку, а некоторые нет - LCL же более последователен, поэтому Delphi [с ним] несовместим. Left и Top могут иметь отрицательные [значения] или [быть] больше, чем область клиента. Некоторые виджетсеты определяют минимум/максимум где-то около 10000[px] или более.
Когда клиентская область прокручивается, Left и Top остаются неизменными.
Во время изменения размера/перемещения [элемента управления] Left и Top не всегда синхронизируются с координатами дескриптора объекта.
Если Parent=nil, тогда [значения] Left, Top зависят от виджетсета и диспетчера окон. До [версии] Lazarus 0.9.25 это обычно были координаты экрана верхне-левой части клиентской области формы. Это несовместимо с Delphi. Планируется изменить это [поведение] на Left, Top окна.
Подсказка: Каждый раз, когда вы изменяете Left и Top, LCL мгновенно приходит в движение и перекомпонует весь макет. Если вы хотите изменить Left и Top, используйте взамен:
with Button1 do
SetBounds(NewLeft, NewTop, Width, Height);
- Width
- Height
Размер в пикселях не должен быть отрицательным, и большинство виджетсетов не допускают Width=0 и/или Height=0. Некоторые элементы управления на некоторых платформах определяют наибольшее минимальное ограничение в Constraints.MinInterfaceWidth/Height. Вместо того, чтобы изменять размер элемента управления в Width=0 и/или Height=0, установите Visible=false или Parent=nil. Во время изменения размера/перемещения [свойства] Width и Height не всегда синхронизируются с размером дескриптора объекта.
- BoundsRect
Аналогично Bounds(Left, Top, Width, Height).
Обычная ошибка новичка:
BoundsRect.Left := 3; // НЕПРАВИЛЬНО: обычная ошибка новичка
Это не [оказывает никакого] влияния, потому что чтение BoundsRect является функцией. Она создает временный TRect в стеке. Вышеупомянутое те же, что и
var
r: TRect;
begin
r := BoundsRect; // получаем границы
r.Left := 3; // изменяем значение в стеке
end; // нет изменений
- ClientRect
Left и Top всегда равны 0,0. [Свойства] Width и Height - это видимый размер клиентской области в пикселях. Помните, что клиентская область - это [то, что] без рамки и без полос прокрутки. В прокручиваемой клиентской области логическая клиентская область может быть больше видимой.
- ClientOrigin
Возвращает экранную позицию верхне-левой координаты 0,0 клиентской области. Обратите внимание, что это значение является позицией, сохраненной в интерфейсе, и не всегда синхронизируется с LCL. Когда элемент управления перемещается, LCL устанавливает границы в нужную позицию и отправляет сообщение [о] перемещении в интерфейс. Интерфейс обрабатывает [сообщения о] перемещениях сразу или по очереди.
- LCLIntf.GetClientBounds
Возвращает клиентские границы элемента управления. Аналогичен ClientRect, но Left и Top - это расстояния в пикселах до левого и верхнего краев элемента управления. Например, в TGroupBox [параметры] Left, Top являются шириной и высотой левой и верхней границ рамки. Прокрутка не влияет на GetClientBounds.
- LCLIntf.GetWindowRect
После вызова [этой функции] ARect будет областью элемента управления в координатах экрана. Это означает, что Left и Top будут экранной координатой верхне-левого пикселя Дескриптора объекта, а [значения] Right и Bottom будут экранной координатой нижне-правого пиксела.
- FBaseBoundsLock: integer
Увеличивается/уменьшается [методами] LockBaseBounds/UnlockBaseBounds.
Используется для сохранения [поля] FBaseBounds во время вызова SetBounds.
- FBaseParentClientSize: TPoint
Размер Parent.ClientRect действителен для FBaseBounds.
[Поля] FBaseBounds и FBaseParentClientSize используются для вычисления расстояния для [параметра выравнивания]
akRight (aBBottom). Когда размер родителя изменяется, LCL знает, какое расстояние сохранить.
- FBoundsRectForNewParent: TRect
При смене родителя элемента управления Дескриптор [объекта] создается [заново], и многое
может случиться. Особенно этот процесс ненадежен для пристыкованных форм. Поэтому BoundsRect сохраняется. VCL использует аналогичные механизм.
- fLastAlignedBounds: TRect
Для получения пояснения см. TControl.SetAlignedBounds.
Коротко: он останавливает некоторые циклы между интерфейсом и автомасштабированием в LCL.
- FLastChangebounds: TRect
Используется для остановки вызова ChangeBounds с одинаковыми координатами. Это случается очень часто.
- FLastDoChangeBounds: TRect
Используется, чтобы избежать вызова OnChangeBounds с теми же координатами. Это подавляет пользовательскую настройку автомасштабирования.
- FLastResizeClientHeight: integer
- FLastResizeClientWidth: integer
- FLastResizeHeight: integer
- FLastResizeWidth: integer
Используется, чтобы избежать вызова OnResize с теми же координатами. Это подавляет пользовательскую настройку автомасштабирования.
- FLoadedClientSize: TPoint
Во время загрузки многие вещи задерживаются, а многие вещи устанавливаются в неправильном порядке и ухудшаются. Вот почему сохраняется и вновь вызывается SetClientWidth/SetClientHeight в конце загрузки.
Таким образом, LCL может восстанавливать размеры (напр., при akRight), используемые во время проектирование.
- FReadBounds: TRect
Аналогично FLoadedClientSiz, но для SetLeft, SetTop, SetWidth, SetHeight.
- procedure SetBoundsRectForNewParent(const AValue: TRect);
Используется для установки FBoundsRectForNewParent. См. выше.
- procedure SetAlignedBounds(aLeft, aTop, aWidth, aHeight: integer); virtual;
Как SetBounds, но без изменения размеров по умолчанию.
- procedure SetInitialBounds(aLeft, aTop, aWidth, aHeight: integer); virtual;<//tt>
"Умная" версия SetBounds, уменьшающая накладные расходы при создании и загрузке.
- procedure UpdateBaseBounds(StoreBounds, StoreParentClientSize, UseLoadedValues: boolean); virtual;
Фиксирует текущие границы базовых границ.
- procedure SetClientHeight(Value: Integer);
- procedure SetClientSize(Value: TPoint);
- procedure SetClientWidth(Value: Integer);
Существует также для совместимости с Delphi. Изменяет размер элемента управления, чтобы получить желаемый размер ClientRect.
- procedure ChangeBounds(ALeft, ATop, AWidth, AHeight: integer); virtual;
Это внутренний SetBounds.
Применяет ограничения, обновляет базовые границы, вызывает OnChangeBound, OnResize, блокирует границы.
- procedure DoSetBounds(ALeft, ATop, AWidth, AHeight: integer); virtual;
Этот [метод] действительно устанавливает частные private-переменные FLeft, FTop, FWidth, FHeight.
- procedure SetBounds(aLeft, aTop, aWidth, aHeight: integer); virtual;
Это стандартная процедура, переопределяющая многие элементы управления Delphi. Также переопределяет TWinControl.
- игнорирует вызовы, когда границы заблокированы
- блокирует FBoundsRealized, чтобы избежать накладных расходов на интерфейс во время автомасштабирования. ChangeBounds таким образом не блокируется.
- Function GetClientOrigin: TPoint; virtual;
Координаты экрана Left, Top клиентской области.
- Function GetClientRect: TRect; virtual;
Размер клиентской области. (всегда Left=0, Top=0)
- Function GetScrolledClientRect: TRect; virtual;
Видимая клиентская область ClientRect.
- function GetChildsRect(Scrolled: boolean): TRect; virtual;
Возвращает клиентский прямоугольник относительно Left, Top элемента управления.
Если [свойство] Scrolled [имеет значение] true, прямоугольник перемещается текущими значениями прокрутки (например, см. TScrollingWincontrol).
- function GetClientScrollOffset: TPoint; virtual;
Возвращает смещение прокрутки клиентской области.
- function GetControlOrigin: TPoint; virtual;
Возвращает экранные координаты верхне-левой координаты 0,0 области элемента управления (верхне-левый пиксель элемента управления на экране).
Обратите внимание, что это значение является позицией, сохраненной в интерфейсе, и не всегда синхронизируется с LCL. Когда элемент управления перемещается, LCL устанавливает границы в желаемую позицию и отправляет сообщение о перемещении в интерфейс. [А уж] интерфейс обрабатывает перемещаемый дескриптер сразу или в [порядке] очереди.
ЧаВо
Почему [свойство] AutoSize не работает в дизайнере должным образом?
В дизайнере элементы управления можно перемещать, и свойства могут быть установлены практически в любом порядке. Чтобы разрешать это и избегать возможных конфликтов, [свойство] AutoSizing не обновляется при каждом изменении во время разработки.
Почему TForm.AutoSize не работает, когда что-то меняется?
Нужно ли мне вызывать Application.ProcessMessages при создании большого количества элементов управления?
Application.ProcessMessages вызывается LCL автоматически после каждого сообщения (например, после каждого события, такого как OnClick). Вызов его сам по себе необходим только в том случае, если изменения должны немедленно стать видимыми пользователю. Например:
procedure TFrom.Button1Click(Sender: TObject);
begin
// изменяем ширину элемента управления
Button1.Width := Button1.Width + 10;
// применяем все необходимые изменения и перерисовываем кнопку
Application.ProcessMessages;
// делаем много вещей, которые занимают много времени
...
// после выхода из OnClick LCL автоматически обрабатывает сообщения
end;
При включении привязки во время исполнения [приложения] элементы управления изменяют размеры, но не используют текущие значения. Почему?
akBottom означает: сохранить расстояние до нижней части родителя. Зазоры для удержания определяются основными границами. Они устанавливаются в режиме разработки или во время исполнения вызовом [методов] SetBounds или UpdateBaseBounds.
Например: TListBox (Anchors=[akLeft,aTop]) при проектировании имеет нижний зазор в 100 пикселей. И кнопку для включения/выключения [значения] akBottom TListBox'а. Теперь запустите приложение и нажмите кнопку, чтобы включить akBottom. 100-пиксельное расстояние будет активировано, потому что это был последний раз, когда программист определил базовые границы TListBox. Все остальные изменения были сделаны LCL и не имеют значения. Основные границы[, заданные] программистом определяют правила. Вы можете изменить размер формы, но 100 пикселей будет сохранено. Чтобы использовать текущие границы в качестве базовых, используйте:
ListBox1.UpdateBaseBounds(true, true, false);
ListBox1.Anchors := ListBox1.Anchors + [akBottom];
Установка привязки не вызывает автоматически [вызов метода] UpdateBaseBounds, потому что это само по себе может уничтожить возможность изменения свойств.
Изменение размера столбцов stringgrid в событии OnResize формы не работает
[Событие] OnResize формы запускается, когда изменяются [значения] Width, Height, ClientWidth или ClientHeight формы. Это само по себе не зависит [непосредственно] от TStringGrid. Конечно, часто бывает, что и форма, и TStringGrid изменяют размеры. Это означает[, что в таких случаях] использование [события] OnResize формы часто будет работать, но не всегда. Ярким примером [того, что использование OnResize формы] всегда терпит неудачу, является [случай], когда тема изменена, и у вас есть TStringGrid[, лежащий] в TGroupBox на TForm. Когда тема меняется, размеры формы сохраняются, поэтому никакого [события] OnResize формы не запускается. Но меняется [соответственно теме] TGroupBox, поэтому TStringGrid должен быть изменен.
Решение: используйте [событие] OnResize TStringGrid'а.
Почему TForm.Width равен TForm.ClientWidth?
Примечание Mattias'а:
"Есть исторические и технические причины.
Для форм без родителя Clientwidth равен Width, поскольку реальная ширина, включая рамку, не была доступна в Linux десять лет назад (по крайней мере, не достоверно для разных оконных менеджеров). Я не тестировал, но слышал, что теперь это возможно с gtk2. Основная проблема заключается в автомасштабировании, потому что для этого нужны размеры рамки до того, как форма будет отображена на экране. Возможно, [размер рамки] доступен только после [наступления] события, а это значит, что вам нужно подождать, что означает проблему для ShowModal. Изменив это, вы нарушите совместимость с большим количеством существующего кода LCL/ru, но для этого мы добавили LCLVersion в файлы lfm.
Существует новое определение компилятора LCLRealFormBounds в Lazarus trunk 1.7, которое позволяет использовать реальный размер для формы. Чтобы использовать его, просто скомпилируйте свое приложение с помощью LCLRealFormBounds ON. Пока что поддерживается только widgetset win32.
Для всех остальных элементов управления применяются правила ClientWidth<=Width. Width - это ClientWidth плюс рамка виджета. Вопрос в том, принадлежат ли полосы прокрутки рамке. Я бы сказал "да", и это было реализовано таким образом некоторое время назад. Видимо, это изменилось. См. Текущую проблему с курсором в synedit."
Я получаю бесконечный цикл / Как отладить автомасштабирование?
Вот некоторые примечания, что другие пользователи сделали неправильно и как найти выход:
Отличия от Delphi
Для пользователей Delphi: пожалуйста, прочитайте: Отличия от Delphi
Переопределение метода LCL и запуск пересчета
Вы переопределяете метод и просите LCL пересчитать снова, даже если ничего не изменилось. Проверьте настройки AdjustSize, Realign, AlignControls, InvalidatePreferredSize. Например:
procedure TMainForm.SetBounds(ALeft, ATop, AWidth, AHeight: integer);
begin
// Это создаст бесконечный цикл
InvalidatePreferredSize;
// Это также создаст бесконечный цикл:
OtherComponent.Left:=10;
OtherComponent.Left:=20;
inherited SetBounds(ALeft, ATop, AWidth, AHeight);
end;
Объяснение: метод SetBounds вызывается часто, даже ничего не меняя. Например, так будет делать "Left:=30;".
Решение: отслеживайте изменения:
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 перемещает и упорядочивает все дочерние элементы управления. Реализация LCL игнорирует элементы управления с помощью Align=alCustom.
Параметр aControl: TControl поддерживается для совместимости с VCL. LCL всегда возвращает nil. Он дает приоритет aControl при применении свойства Align. Если у вас есть, например, два элемента управления A,B со [значением] Align=alLeft, то [элемент управления, который будет] нижним слева расположится левее [другого]. Если оба имеют одинаковые [значения] Left, [то] приоритетнее [элемент в] порядке создания. Теперь представьте, что вы хотите переключить оба элемента управления A, B в дизайнере. Вы перетащите B влево. Это приведет к установке [значения] B.Left в 0. Теперь AlignControls запускается и находит[, что у обоих элементов управления значения] A.Left=0 и B.Left=0. В обычном [случае] A [по расположению окажется в] приоритете. Чтобы [приоритетнее оказался элемент управления] B, VCL вызовет AlignControls(B,r). Таким образом, aControl является последним перемещенным. В отличие от VCL, LCL позволяет комбинировать несколько изменений разметки без перерасчета на каждом шагу. LCL отслеживает последние перемещенные элементы управления в TWinControl.fAlignControls и применяет порядок в TWinControl.CreateControlAlignList. Параметр aControl всегда равен nil.
Смотри TWinControl.CreateControlAlignList.
OnResize/OnChangeBounds конфликтует со свойствами LCL
Вы устанавливаете границы, которые ущемляют свойства LCL. Например, по умолчанию TLabel.AutoSize [имеет значение] true. Если вы определяете Label1.Width в событии OnResize, LCL будет [запускать] пересчет, изменяя размер Label1, и снова вызывать OnResize. Запустите приложение в отладчике и воспроизведите ошибку. Когда оно войдет в [бесконечный] цикл, приостановите приложение и смотрите стек вызовов. Как вы видите, одно из ваших событий или ваших методов начинают искать там. Например:
procedure TMainForm.FormResize(Sender: TObject);
begin
// если Button1 привязан или AutoSize=true, то следующее может создать бесконечный цикл:
Button1.Width:=100;
end;
Ошибка интерфейса LCL, пользовательский виджет
Иногда интерфейс LCL или ваш пользовательский элемент управления имеет ошибку и отменяет границы LCL. Скомпилируйте LCL с [опцией] -dVerboseIntfSizing. Это запишет связь между LCL и интерфейсом LCL. Если виджет не допускает свободное изменение размера, он должен сказать [об этом] LCL через Constraints[(ограничения)]. Поищите SetInterfaceConstraints в различных интерфейсах LCL и TWSControlClass.ConstraintWidth/Height.
alCustom
Вы используете alCustom. В ранних версиях Lazarus это было реализовано только наполовину, но некоторые даровитые программисты использовали его для создания некоторых специальных эффектов. Теперь это реализовано, и ваша программа больше не работает. Пожалуйста, см. здесь, что alCustom делает: alCustom.