Autosize / Layout/ru

From Lazarus wiki
Jump to navigationJump to search

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


Введение

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, таким образом изменение надписи на кнопке не изменит размеры кнопки. При установке значения 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 = csAutoSize, который в настоящее время используется только TPanel).

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

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

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

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

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

Autosize on.png

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



Прим.переводчика: на самом деле, перемещаются границы родительского элемента управления так, чтобы дочерние элементы управления с фиксированной относительно друг друга позицией полностью умещались на родительском. При этом родительский элемент управления будет иметь минимально возможный размер. Таким образом, кнопки "перемещаются" относительно верхнего левого угла родительского элемента управления.



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  Примечание: Диспетчер окон и события формы могут изменять размер окна. Предпочтительный размер не включает рамку окна формы. Это запланированная особенность.



Привязка сторон (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 с кнопкой: 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.


Прим.переводчика: Ох, уж эти языковые особенности. Автор, очевидно, имел в виду следующее: даже если у дочернего контрола выравнивание выставлено как 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 вы можете убедиться, что только последние границы отправляются в виджетсет, что делает границы формы более надежными [для отображения].



Прим.перев.: очевидно, имеется ввиду прерывистый механизм перерисовки границ окна, когда при перетаскивании его края мышью рисуется только рамка новой границы окна, а окончательная перерисовка происходит после отпускания кнопки мыши.




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

В этом примере сочетаются несколько механизмов компоновки 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[(перенос слов)] изменяет поведение предпочтительного размера [компонента] 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.

См. также