Доброго времени суток. В этой статье я расскажу основы работы с классами пакета javafx.scene.layout.* (BorderPane, AnchorPane, StackPane, GridPane, FlowPane, TilePane, HBox, VBox) и их особенностями в пошаговых примера и иллюстрациях. Также в кратце пробежимся по иерархии классов JavaFx.
После установки Eclipse и плагина e(fx)lipse создадим новый проект:
Выбираем JavaFx проект:
Настраиваем проект:
По дефолту Eclipse создаст такую заготовку:
package application;
import javafx.application.Application;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.layout.BorderPane;
public class Main extends Application {
@Override
public void start(Stage primaryStage) {
try {
BorderPane root = new BorderPane();
Scene scene = new Scene(root,400,400);
scene.getStylesheets().add(getClass().getResource("application.css").toExternalForm());
primaryStage.setScene(scene);
primaryStage.show();
} catch(Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
launch(args);
}
}
На сцену добавляется корневой узел, им должен быть класс унаследованный от Parent (узел, который может иметь детей) корневой узел добавляется на сцену (специальный контейнер для всего контента графа узлов), а она в свою очередь на стейжд, который и показывается.
Давайте разберемся с иерархией классов JavaFx:
Самым базовым абстрактным классом является Node — узел в графе сцены, наследникам предоставляются поля для настройки размеров: (minWidth — минимальная ширина, minHeight — минимальная высота, prefWidth — предпочтительная ширина, prefHeight — предпочтительная высота, maxWidth — максимальная ширина, maxHeight — максимальная высота).
Координаты узла: getLayoutX() и getLayoutY()
relocate(double x, double y) — изменяет координаты узла, если необходимо настроить положение каждой координаты по отдельности, используйте setLayoutX(double value) и setLayoutY(double value)
Важно понимать, что выставленные координаты и размеры (pref то есть prefer — предпочтительно) при компоновке, в зависимости от иерархии графа узлов и их настроек могу отличаться от ожидаемых. Примеры приведу чуть позже.
Node не может содержать дочерних элементов это и логично, для этого существует Parent прямой наследник Node, в который можно добавлять и удалять дочерние узлы. Кстати, говоря, есть классы которые представляю узлы, но не содержат дочерние узлы: Camera, Canvas, ImageView, LightBase, MediaView, Parent, Shape, Shape3D, SubScene, SwingNode.
Нас сегодня интересует подгруппа наследников класса Region (базовый класс всех лайуат контейнеров) в частности поговорим о основных потомках класса Pane о панелях, которые чаще всего требуются в повседневной разработке.
BorderPane
Это специальная панель которая располагает свою дочерние узлы верхней(setTop(Node)), нижней(setBottom(Note)), левой(setLeft(Node)), правой(setRight(Node)) и центральной(setCenter(Node)) позициях, добавим несколько кнопок во все эти позиции на BotderPane:
BorderPane root = new BorderPane();
root.setLeft(new Button("Left"));
root.setTop(new Button("Top"));
root.setRight(new Button("Right"));
root.setBottom(new Button("Bottom"));
root.setCenter(new Button("Center"));
У секции top и bottom приоритет, поэтому они сужаю по высоте все остальные:
При попытке задать координаты к примеру у верхней кнопки:
BorderPane root = new BorderPane();
root.setLeft(new Button("Left"));
Button topButton = new Button("Top");
topButton.setLayoutX(20.0);
topButton.setLayoutY(20.0);
root.setTop(topButton);
root.setRight(new Button("Right"));
root.setBottom(new Button("Bottom"));
root.setCenter(new Button("Center"));
Кнопка не изменит своего положения, почему так происходит? При компоновке детей в методе layoutChildren класса BorderPane переопределяется позиция каждого узла с учетом всех настроек. Решений исправить есть несколько, например поместить (обернуть) кнопку в дополнительный подходящий контейнер, например в базовую для всех панелей
Pane
BorderPane root = new BorderPane();
root.setLeft(new Button("Left"));
//
Pane pane = new Pane();
Button topButton = new Button("Top");
topButton.setLayoutX(20.0);
topButton.setLayoutY(20.0);
pane.getChildren().add(topButton);
root.setTop(pane);
//
root.setRight(new Button("Right"));
root.setBottom(new Button("Bottom"));
root.setCenter(new Button("Center"));
Потому как Pane никак не переопределяет положение своих детей, мы получаем нужное смещение:
Можно не оборачивать кнопку, а просто выставить у нее внешний отступ c помощью статического метода setMargin(Node child, Insets value):
BorderPane.setMargin(topButton, new Insets(20.0));
Insets — вспомогательный класс для настройки отступов, конструктор у него перегружен, может принимать один аргумент для все сторон, так и для каждой стороны в отдельности, пока мы настроили для все сторон один отступ, вот что мы получили:
Уберем отступы снизу и справа и получим желаемое смещение кнопки:
BorderPane.setMargin(topButton, new Insets(20.0, 0.0, 0.0, 20.0));
StackPane
Теперь давайте рассмотрим ситуацию в которой панель навязывает детям не только позицию, но и пытается их растянуть по всей ширине и высоте, заполнив всю область контента, заменим BorderPane и добавим туда сначала TextArea и Button.
StackPane root = new StackPane();
root.getChildren().add(new TextArea("TextArea in StackPane"));
root.getChildren().add(new Button("Button in StackPane"));
Scene scene = new Scene(root,250,250);
Кстати, для добавления нескольких узлов в нужном порядке можно использовать метод:
root.getChildren().addAll(Node1, Node2, ....);
Что мы видим, текстовое поле растянулись по нашей панели, а кнопка центрировалась:
Центрирование можно корректироваться с помощью setAlignment(Pos)
root.setAlignment(Pos.BOTTOM_RIGHT);
Сделать внутренний отступ с помощью setPadding(Insets)
root.setPadding(new Insets(10.0));
Можно сделать индивидуальную настройку центрирования и отступов каждого узла с помощью статических методов:
StackPane root = new StackPane();
TextArea textArea = new TextArea("TextArea in StackPane");
StackPane.setMargin(textArea, new Insets(10.0, 0.0, 30.0, 50.0));
Button button = new Button("Button in StackPane");
StackPane.setAlignment(button, Pos.CENTER_RIGHT);
root.getChildren().addAll(textArea, button);
Результат:
AnchorPane
Якорная панель позволяет края дочерних узлов привязывать смещениями к краям панели, рассмотрим пример:
Добавим кнопку и привяжем ее к правому краю:
AnchorPane root = new AnchorPane();
Button button = new Button("Button in AnchorPane");
root.getChildren().add(button);
AnchorPane.setRightAnchor(button, 10.0);
Добавим привязку к нижнему краю:
AnchorPane.setBottomAnchor(button, 10.0);
Теперь в левому краю и верхнему:
AnchorPane.setRightAnchor(button, 10.0);
AnchorPane.setTopAnchor(button, 10.0);
Получаем что края кнопки теперь жестко привязаны к краям панели, если начать тянуть окно, кнопка будет также тянуться.
GridPane
Рассмотрим очень полезную сеточную панель, добавление детей в эту панель необходимо делать через метод add(Node child, int columnIndex, int rowIndex) в котором первый параметр добавляемый узле, второй номер колонки, третий номер строки, вот простой пример:
GridPane root = new GridPane();
// Для отображения сетки
root.setGridLinesVisible(true);
root.add(new Label("0x0"), 0, 0);
root.add(new Label("0x1"), 0, 1);
root.add(new Label("1x1"), 1, 1);
root.add(new Label("1x2"), 1, 2);
root.add(new Label("5x5"), 5, 5);
Мы видим, что узлы можно добавлять в любую ячейку, пустые столбцы и строки создаются автоматически:
Для работы с колонками, есть специальный класс ColumnConstraints, для этого надо создать колонки, настроить их и добавить их в GridPane, в примеру если мы хотим выставить ширины первой колонки 130, а второй 20%:
GridPane root = new GridPane();
root.setGridLinesVisible(true);
root.add(new Label("0x0"), 0, 0);
root.add(new Label("0x1"), 0, 1);
root.add(new Label("1x1"), 1, 1);
root.add(new Label("1x2"), 1, 2);
root.add(new Label("5x5"), 5, 5);
//
ColumnConstraints columnConstraints = new ColumnConstraints();
columnConstraints.setPrefWidth(130.0);
ColumnConstraints columnConstraints1 = new ColumnConstraints();
columnConstraints1.setPercentWidth(20);
root.getColumnConstraints().addAll(columnConstraints, columnConstraints1);
//
Получаем:
Это самые базовые возможности, потребуется написать отдельную статья, для охвата всех тонкостей настройки GridPane.
FlowPane
Давайте сразу к примеру из него сразу станет все понятно, добавим шесть кнопок в FlowPane:
FlowPane root = new FlowPane();
root.getChildren().add(new Button("Button #1"));
root.getChildren().add(new Button("Button #2"));
root.getChildren().add(new Button("Button #3"));
root.getChildren().add(new Button("Button #4"));
root.getChildren().add(new Button("Button #5"));
root.getChildren().add(new Button("Button #6"));
Посмотрим результат, кнопка выводятся одна за другой (по дефолту у FlowPane горизонтальный вывод Orientation.HORIZONTAL)
Теперь при уменьшении окна по ширине, мы видим что дочерние узлы начинают переносится:
И соответственно если выставить вертикальную ориентацию
root.setOrientation(Orientation.VERTICAL);
При при уменьшении высоты окна получаем перенос:
Вертикальные и горизонтальные отступы между элементами можно настроиться с помощью:
root.setVgap(8);
root.setHgap(4);
TilePane
Работа этой панели схожа с работой FlowPane, отличие в том что дочерние узлы помещаются в сетку ячейки которой одинакового размера, приведем предыдущий пример, но в этот раз пусть одна кнопка будет больших размеров, чем остальные:
Как мы видим все узлы теперь находятся в «тайла» одинаковых размеров, растянутых по наибольшему узлу.
HBox и VBox
Это обычные горизонтальный и вертикальный списки, комбинируйте их, чтобы добиться нужного результата:
VBox root = new VBox();
HBox hBox = new HBox();
hBox.getChildren().addAll(new Button("Button#1"), new Button("Button#2"));
Slider slider = new Slider(1.0, 10.0, 4.0);
slider.setShowTickLabels(true);
root.getChildren().add(new Label("Label"));
root.getChildren().addAll(hBox, slider);
Это все наследники класса Pane, спасибо за внимание.
Автор: VADMARK