Контейнеры: распределение места и прокрутка
Полная раскладка C#: Expand/Fill, предел ширины, пропорции и переход к концу списка.
Выберите владельца раскладки
| Контейнер | Для чего |
|---|---|
| HBoxContainer / VBoxContainer | Строка / колонка |
| GridContainer | Таблица с заданным числом колонок |
| FlowContainer и его варианты | Перенос элементов на следующую строку/колонку |
| MarginContainer | Отступы вокруг содержимого |
| PanelContainer | Фон темы и место под содержимое |
| CenterContainer / AspectRatioContainer | Центрирование / сохранение пропорций |
| ScrollContainer | Прокручиваемая видимая область |
| TabContainer / SplitContainer | Вкладки / разделяемые панели |
Контейнер сортирует подходящих прямых дочерних Control. Промежуточная обычная Entity разрывает это отношение. Вкладывайте контейнеры, чтобы получить иерархию строк и колонок. Не добавляйте элементы поверх друг друга прямо в PanelContainer, если им нужна последовательная раскладка: поместите внутрь VBox/HBox.
В обычной раскладке участвуют непосредственные дочерние Control, которые видимы и не имеют TopLevel. Если элемент выпал из ряда, проверьте эти условия прежде, чем менять anchors. TopLevel выводит Control из обычной сортировки контейнера; это не способ закрепить его Position внутри строки.
Запустить пример
Добавьте UiSampleStyle.cs. Прикрепите ContainersDemo.cs к корню Control, соберите и запустите F6. Верхняя строка показывает распределение A/B/C, ниже — двадцать кнопок в прокрутке. Перейдите по списку клавишей Tab: FollowFocus подводит выбранный элемент в видимую область. Метод RevealLast можно вызвать из вашей кнопки перехода к концу.
ContainersDemo.cs
using Reckfell;
public partial class ContainersDemo : Control
{
public HBoxContainer Row { get; private set; } = null!;
public PanelContainer A { get; private set; } = null!;
public PanelContainer B { get; private set; } = null!;
public PanelContainer C { get; private set; } = null!;
public ScrollContainer Scroll { get; private set; } = null!;
public Button Last { get; private set; } = null!;
public override void _Ready()
{
UiSampleStyle.Prepare(this);
var margin = new MarginContainer(); AddChild(margin);
margin.SetAnchorsAndOffsetsPreset(LayoutPreset.FullRect, margin: 16);
var column = new VBoxContainer(); margin.AddChild(column);
column.AddChild(new Label { Text = UiSampleStyle.Text("Контейнеры и прокрутка", "Containers and scrolling") });
Row = new HBoxContainer(); column.AddChild(Row);
A = Item("A", 64); B = Item("B", 64); C = Item("C", 64);
A.CustomMaximumSize = new Vector2(140, -1);
A.SizeFlagsHorizontal = SizeFlags.ExpandFill; B.SizeFlagsHorizontal = SizeFlags.ExpandFill;
B.SizeFlagsStretchRatio = 2; C.SizeFlagsHorizontal = SizeFlags.Fill;
Row.AddChild(A); Row.AddChild(B); Row.AddChild(C);
Scroll = new ScrollContainer
{
SizeFlagsVertical = SizeFlags.ExpandFill, FollowFocus = true,
HorizontalScrollMode = ScrollContainer.ScrollMode.Disabled
};
column.AddChild(Scroll);
var list = new VBoxContainer { SizeFlagsHorizontal = SizeFlags.ExpandFill }; Scroll.AddChild(list);
for (int i = 1; i <= 20; i++)
{
Last = new Button { Text = UiSampleStyle.Text("Пункт ", "Item ") + i, CustomMinimumSize = new Vector2(0, 40) };
list.AddChild(Last);
}
}
private static PanelContainer Item(string text, float width)
{
var panel = new PanelContainer { CustomMinimumSize = new Vector2(width, 60) };
panel.AddChild(new Label { Text = text, HorizontalAlignment = HorizontalAlignment.Center });
return panel;
}
public void RevealLast()
{
Last.GrabFocus();
Callable.From(() => Scroll.EnsureControlVisible(Last)).CallDeferred();
}
}Fill и Expand решают разные задачи
| Флаг | Результат |
|---|---|
| Fill | Заполнить выделенный контейнером прямоугольник |
| Expand | Участвовать в распределении дополнительного места |
| ExpandFill | Получить дополнительное место и заполнить его |
| ShrinkBegin / Center / End | Использовать меньший размер с указанным выравниванием |
| Maximize | Дополнительный приоритет/использование максимума в поддерживающем контейнере; не синоним FullRect |
У A и B включён ExpandFill, но A ограничена 140. Освободившееся место получает B; C не расширяется. SizeFlagsStretchRatio — относительный вес, не точный процент итоговой ширины: минимумы, максимумы и промежутки участвуют в расчёте. В BoxContainer Maximize усиливает приоритет растяжения у Expand; минимумы других элементов и maximum всё равно учитываются. Смысл флагов проверяйте для конкретного контейнера.
Прокрутка без разрастания окна
ScrollContainer получает доступную высоту через ExpandFill. Внутри — одна колонка с двадцатью элементами; её минимум по высоте больше видимой области. Горизонтальная прокрутка отключена, колонка заполняет доступную ширину. Если положить в список Control с несжимаемым текстом шире окна, отключение полосы не исправит содержимое: разрешите перенос или измените структуру.
EnsureControlVisible() требует потомка этого ScrollContainer и актуальных прямоугольников. После динамического изменения дерева дождитесь сортировки контейнеров. RevealLast откладывает обращение; не считайте Size сразу после AddChild окончательным результатом раскладки. FollowFocus удобен для клавиатуры, но не задаёт весь маршрут фокуса.
Диагностика
| Проблема | Причина / решение |
|---|---|
| Position сам возвращается | Им управляет родительский контейнер |
| Прямой дочерний элемент не раскладывается | Это должен быть видимый Control без TopLevel; после изменения дождитесь сортировки |
| Fill не растягивает строку | Fill заполняет выделенную долю; распределение свободного места включает Expand |
| Два элемента не делят место 1:2 | Учитываются минимумы, максимумы и остальные участники |
| Скрытый элемент оставляет место | Проверьте, какой именно Control скрыт, и дождитесь сортировки |
| Прокрутки нет, а окно растёт | Слишком большой минимум у ScrollContainer или неверные SizeFlags его родителей |
| Переход к пункту не работает сразу | Раскладка ещё не пересчитана либо объект не потомок ScrollContainer |