Копия викизеркало

Это зеркало вики с pascal.chat. Правка, история и вход — там.

Autosize / Layout/ru

English · Русский · 中文(中国) · 中文(台灣)

Введение

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

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

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

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

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

Внутренние алгоритмы объясняются здесь: 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 и делает две вещи для видимого элемента управления:

Отличия от Delphi

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

С помощью свойства кнопки 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 (у панели), то кнопки будут перемещены так, чтобы предопределенный в упомянутых выше свойствах зазор был задействован.

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

Перемещение дочерних элементов управления зависит от свойства 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, выполнив:

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

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

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

pascal
Form1.HandleNeeded;
Form1.GetPreferredSize(PreferredWidth,PreferredHeight);

Note: Диспетчер окон и события формы могут изменять размер окна. Предпочтительный размер не включает рамку окна формы. Это запланированная особенность.

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

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

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

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

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

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

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

GroupBox с кнопкой:

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

С akLeft, без akRight:

С akLeft и akRight:

С akRight, без akLeft:

Три кнопки в GroupBox:

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

На заметку:

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

При изменении ширины привязанного элемента управления, например, через 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 кнопки.

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

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


\

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

Зазор

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

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

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

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

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

Например:

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

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

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

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

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

Пример

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

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

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

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

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.

\

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

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

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

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

Величина свойства 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.

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

Разметка (Layout)

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

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

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

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

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

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

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

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

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

Растяжение

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

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:

\

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

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

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

pascal
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:

pascal
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:

pascal
  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:

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

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

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

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

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

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

pascal
  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:

pascal
  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

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

pascal
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 запускает [механизм] пересчета разметки:

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

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

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

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

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

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

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

Note: вот так неправильно:
\

pascal
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 - это размер области внутри фрейма. Итак, всегда верно следующее:

pascal
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

pascal
  TLayoutAdjustmentPolicy = (
    lapDefault,     // Зависимость от виджетсета
    lapFixedLayout, // Фиксированная абсолютная разметка на всех платформах
    lapAutoAdjustWithoutHorizontalScrolling, // Это используется платформой для смартфонов,
                                             // ось x растягивается, чтобы заполнить экран и
                                             // y масштабируется, чтобы соответствовать DPI
    lapAutoAdjustForDPI // Для рабочих столов с использованием High DPI, x и y масштабируются соответственно DPI
  );

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

pascal
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), если [это свойство] свободно для изменения величины. Другими словами - ширина не автомасштабируется, если:

То же [справедливо и для свойства] Height.

Новый размер рассчитывается protected-методом TControl.CalculatePreferredSize. Этот метод запрашивает виджетсет для [получения] подходящих Width и Height. Например, TButton имеет предпочтительные ширину и высоту. TComboBox имеет только предпочтительную высоту. [У него] предпочтительная ширина возвращается как 0, и поэтому LCL не автомасштабирует ширину - [LCL] сохраняет ширину неизменной. Наконец, у TMemo нет предпочтительной ширины или высоты. Поэтому [параметр] AutoSize не влияет на TMemo.

Некоторые элементы управления перекрывают этот метод. Например, потомки TGraphicControl, такие как TLabel, не имеют дескриптора окна и поэтому не могут запрашивать виджетсет. Они должны сами рассчитать их предпочтительную ширину и высоту.

Виджетсеты должны переопределять метод GetPreferredSize для каждого класса виджетов, который имеет предпочтительный размер (ширина или высота, или оба).

Parent.AutoSize

Выше описанное [дает] простое объяснение. Реальный алгоритм [функционально] предусматривает гораздо больше возможностей и, следовательно, гораздо более сложен.

Свойства / Методы

Если 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, используйте взамен:

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

Размер в пикселях не должен быть отрицательным, и большинство виджетсетов не допускают Width=0 и/или Height=0. Некоторые элементы управления на некоторых платформах определяют наибольшее минимальное ограничение в Constraints.MinInterfaceWidth/Height. Вместо того, чтобы изменять размер элемента управления в Width=0 и/или Height=0, установите Visible=false или Parent=nil. Во время изменения размера/перемещения [свойства] Width и Height не всегда синхронизируются с размером дескриптора объекта.\

Аналогично Bounds(Left, Top, Width, Height).

Обычная ошибка новичка:

pascal
BoundsRect.Left := 3; // НЕПРАВИЛЬНО: обычная ошибка новичка

Это не [оказывает никакого] влияния, потому что чтение BoundsRect является функцией. Она создает временный TRect в стеке. Вышеупомянутое те же, что и

pascal
var
  r: TRect;
begin
  r := BoundsRect; // получаем границы
  r.Left := 3;  // изменяем значение в стеке
end;  // нет изменений

Left и Top всегда равны 0,0. [Свойства] Width и Height - это видимый размер клиентской области в пикселях. Помните, что клиентская область - это [то, что] без рамки и без полос прокрутки. В прокручиваемой клиентской области логическая клиентская область может быть больше видимой.\

Возвращает экранную позицию верхне-левой координаты 0,0 клиентской области. Обратите внимание, что это значение является позицией, сохраненной в интерфейсе, и не всегда синхронизируется с LCL. Когда элемент управления перемещается, LCL устанавливает границы в нужную позицию и отправляет сообщение [о] перемещении в интерфейс. Интерфейс обрабатывает [сообщения о] перемещениях сразу или по очереди.\

Возвращает клиентские границы элемента управления. Аналогичен ClientRect, но Left и Top - это расстояния в пикселах до левого и верхнего краев элемента управления. Например, в TGroupBox [параметры] Left, Top являются шириной и высотой левой и верхней границ рамки. Прокрутка не влияет на GetClientBounds.\

После вызова [этой функции] ARect будет областью элемента управления в координатах экрана. Это означает, что Left и Top будут экранной координатой верхне-левого пикселя Дескриптора объекта, а [значения] Right и Bottom будут экранной координатой нижне-правого пиксела.\

Увеличивается/уменьшается [методами] LockBaseBounds/UnlockBaseBounds. Используется для сохранения [поля] FBaseBounds во время вызова SetBounds.\

Размер Parent.ClientRect действителен для FBaseBounds. [Поля] FBaseBounds и FBaseParentClientSize используются для вычисления расстояния для [параметра выравнивания] akRight (aBBottom). Когда размер родителя изменяется, LCL знает, какое расстояние сохранить.\

При смене родителя элемента управления Дескриптор [объекта] создается [заново], и многое может случиться. Особенно этот процесс ненадежен для пристыкованных форм. Поэтому BoundsRect сохраняется. VCL использует аналогичные механизм.\

Для получения пояснения см. TControl.SetAlignedBounds. Коротко: он останавливает некоторые циклы между интерфейсом и автомасштабированием в LCL.\

Используется для остановки вызова ChangeBounds с одинаковыми координатами. Это случается очень часто.\

Используется, чтобы избежать вызова OnChangeBounds с теми же координатами. Это подавляет пользовательскую настройку автомасштабирования.\

Используется, чтобы избежать вызова OnResize с теми же координатами. Это подавляет пользовательскую настройку автомасштабирования.\

Во время загрузки многие вещи задерживаются, а многие вещи устанавливаются в неправильном порядке и ухудшаются. Вот почему сохраняется и вновь вызывается SetClientWidth/SetClientHeight в конце загрузки. Таким образом, LCL может восстанавливать размеры (напр., при akRight), используемые во время проектирование.\

Аналогично FLoadedClientSiz, но для SetLeft, SetTop, SetWidth, SetHeight.\

Используется для установки FBoundsRectForNewParent. См. выше.\

Как SetBounds, но без изменения размеров по умолчанию.\

"Умная" версия SetBounds, уменьшающая накладные расходы при создании и загрузке.\

Фиксирует текущие границы базовых границ.\

Существует также для совместимости с Delphi. Изменяет размер элемента управления, чтобы получить желаемый размер ClientRect.\

Это внутренний SetBounds. Применяет ограничения, обновляет базовые границы, вызывает OnChangeBound, OnResize, блокирует границы.\

Этот [метод] действительно устанавливает частные private-переменные FLeft, FTop, FWidth, FHeight.\

Это стандартная процедура, переопределяющая многие элементы управления Delphi. Также переопределяет TWinControl.

Координаты экрана Left, Top клиентской области.\

Размер клиентской области. (всегда Left=0, Top=0)\

Видимая клиентская область ClientRect.\

Возвращает клиентский прямоугольник относительно Left, Top элемента управления. Если [свойство] Scrolled [имеет значение] true, прямоугольник перемещается текущими значениями прокрутки (например, см. TScrollingWincontrol).\

Возвращает смещение прокрутки клиентской области.\

Возвращает экранные координаты верхне-левой координаты 0,0 области элемента управления (верхне-левый пиксель элемента управления на экране). Обратите внимание, что это значение является позицией, сохраненной в интерфейсе, и не всегда синхронизируется с LCL. Когда элемент управления перемещается, LCL устанавливает границы в желаемую позицию и отправляет сообщение о перемещении в интерфейс. [А уж] интерфейс обрабатывает перемещаемый дескриптер сразу или в [порядке] очереди.\

ЧаВо

Почему [свойство] AutoSize не работает в дизайнере должным образом?

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

Почему TForm.AutoSize не работает, когда что-то меняется?

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

Нужно ли мне вызывать Application.ProcessMessages при создании большого количества элементов управления?

Application.ProcessMessages вызывается LCL автоматически после каждого сообщения (например, после каждого события, такого как OnClick). Вызов его сам по себе необходим только в том случае, если изменения должны немедленно стать видимыми пользователю. Например:

pascal
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 пикселей будет сохранено. Чтобы использовать текущие границы в качестве базовых, используйте:

pascal
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. Например:

pascal
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;".

Решение: отслеживайте изменения:

pascal
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

pascal
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. Запустите приложение в отладчике и воспроизведите ошибку. Когда оно войдет в [бесконечный] цикл, приостановите приложение и смотрите стек вызовов. Как вы видите, одно из ваших событий или ваших методов начинают искать там. Например:

pascal
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.

См. также


From the Free Pascal wiki, page "Autosize / Layout/ru", as it stood on 2023-09-05 after a change by Zoltanleo, out of the wiki's dump of 2024-05-26. Copyright stays with the author who wrote it, according to the old wiki's Licensing page. If you have concerns about this material, message the admin.

Версия 3, последнее изменение: WikiArchive, .