Я пытаюсь сделать очень простую вещь в JavaFX. Я хочу, чтобы окно с VBox с кнопками слева, HBox с кнопками сверху, а остальная часть была заполнена сеткой, содержащей переменное количество прямоугольников.
Я помещаю три элемента в BorderBox и вычисляю ширину каждого прямоугольника в соответствии с размером GridPane, но когда я запускаю приложение, мне всегда приходится изменять размер окна, перетаскивая его из правого нижнего угла, чтобы отобразить всю GridPane. (вне поля зрения около 3 рядов и 3 столбцов).
В этом примере пусть окно будет иметь фиксированный размер, как указано. Кроме того, когда установлены сцены и ширина/высота блоков, когда я вызываю GridPane.getHeight() и GridPane.getWidth(), это показывает мне, что это квадратично (660x660, что не должно иметь значения, потому что способ расчета, даже если это не так, он должен, по крайней мере, заполниться до нижней или правой стороны и не перелиться).
Есть идеи, как это можно исправить?
@Override
public void start(Stage primaryStage) throws Exception {
//primaryStage.setResizable(false);
BorderPane mainPane = new BorderPane();
HBox topBox = new HBox(10);
VBox leftBox = new VBox(10);
GridPane gridPane = new GridPane();
mainPane.setTop(topBox);
mainPane.setLeft(leftBox);
mainPane.setCenter(gridPane);
leftBox.setBorder(new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID,null,new BorderWidths(1))));
topBox.setBorder(new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID,null,new BorderWidths(1))));
leftBox.setPrefWidth(180);
topBox.setPrefHeight(40);
//TopBox
Button tb1 = new Button("TB 1");
Button tb2 = new Button("TB 2");
topBox.getChildren().addAll(tb1,tb2);
//LeftBox
Button lb1 = new Button("LB 1");
Button lb2 = new Button("LB 2");
Button lb3 = new Button("LB 3");
leftBox.getChildren().addAll(lb1,lb2,lb3);
Scene mainScene = new Scene(mainPane, 840,700);
primaryStage.setScene(mainScene);
primaryStage.show();
int rectangleAmountPerRowCol = 50;
double rectangleWidth = Math.min(gridPane.getWidth()/rectangleAmountPerRowCol,gridPane.getHeight()/rectangleAmountPerRowCol);
for(int k = 0; k < rectangleAmountPerRowCol; k++) {
for (int i = 0; i < rectangleAmountPerRowCol; i++) {
Rectangle r = new Rectangle(rectangleWidth, rectangleWidth);
r.setStroke(Color.RED);
r.setStrokeWidth(0.1);
gridPane.add(r, i, k);
}
}
}
Я пытался изменить размеры объектов, пытался привязать размер сетки к другим компонентам, пытался работать с Column и RowConstraints, но безрезультатно. Я просто не понимаю, почему на моем показании, например. ширина GridPane и ширина левого VBox точно равны ширине окна, но при запуске этого не происходит.
Большое спасибо! пронзенный
Возможно, вы захотите поместить свои прямоугольники в TilePane, что автоматически сделает их одинакового размера.
Или замените прямоугольники на Pane и используйте ограничения панели сетки, чтобы указать им заполнять пространство так, как вы хотите.
@VGR будет TilePane изменять размер Shape экземпляров?




Классы Shape, такие как Rectangle, не имеют возможности изменения размера, поэтому обычно они не особенно хорошо сочетаются с панелями макета. Поскольку их размер нельзя изменить, панель макета не будет контролировать их размер, и вам придется каким-то образом устанавливать размер прямоугольника самостоятельно. Это, в свою очередь, требует, чтобы размер прямоугольника был установлен в нужный момент во время прохода макета, чтобы он правильно использовал размер содержащей его панели сетки.
Как правило, лучше размещать узлы изменяемого размера (например, подклассы Region и Control) на панелях макета, а затем использовать API панели макета, чтобы настроить способ изменения размера узла на панели макета.
Вот пример использования панелей вместо прямоугольников в качестве дочерних узлов панели сетки. Вызывая setHgrow(...) и setVgrow(), вы можете заставить панели заполнить столько свободного места, сколько имеется в ячейке сетки, заставив панель сетки занять все доступное ей пространство:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class GridPaneTest extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
//primaryStage.setResizable(false);
BorderPane mainPane = new BorderPane();
HBox topBox = new HBox(10);
VBox leftBox = new VBox(10);
GridPane gridPane = new GridPane();
mainPane.setTop(topBox);
mainPane.setLeft(leftBox);
mainPane.setCenter(gridPane);
leftBox.setBorder(new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID,null,new BorderWidths(1))));
topBox.setBorder(new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID,null,new BorderWidths(1))));
leftBox.setPrefWidth(180);
topBox.setPrefHeight(40);
//TopBox
Button tb1 = new Button("TB 1");
Button tb2 = new Button("TB 2");
topBox.getChildren().addAll(tb1,tb2);
//LeftBox
Button lb1 = new Button("LB 1");
Button lb2 = new Button("LB 2");
Button lb3 = new Button("LB 3");
leftBox.getChildren().addAll(lb1,lb2,lb3);
int rectangleAmountPerRowCol = 50;
for(int k = 0; k < rectangleAmountPerRowCol; k++) {
for (int i = 0; i < rectangleAmountPerRowCol; i++) {
Pane pane = new Pane();
pane.setStyle("""
-fx-background-color: red, black;
-fx-background-insets: 0, 0.1;"""
);
GridPane.setHgrow(pane, Priority.ALWAYS);
GridPane.setVgrow(pane, Priority.ALWAYS);
gridPane.add(pane, i, k);
}
}
Scene mainScene = new Scene(mainPane, 840,700);
primaryStage.setScene(mainScene);
primaryStage.show();
}
}
Если по какой-то причине вам действительно нужны прямоугольники (хотя трудно увидеть вариант использования, который соответствует прямоугольнику, а панель — нет), вероятно, лучший подход — поместить каждый прямоугольник в панель, использовать API, чтобы заставить каждую панель быть желаемый размер, а затем используйте привязку, чтобы сделать размер прямоугольника таким же, как размер панели:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;
public class GridPaneTest extends Application {
@Override
public void start(Stage primaryStage) throws Exception {
//primaryStage.setResizable(false);
BorderPane mainPane = new BorderPane();
HBox topBox = new HBox(10);
VBox leftBox = new VBox(10);
GridPane gridPane = new GridPane();
mainPane.setTop(topBox);
mainPane.setLeft(leftBox);
mainPane.setCenter(gridPane);
leftBox.setBorder(new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID,null,new BorderWidths(1))));
topBox.setBorder(new Border(new BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID,null,new BorderWidths(1))));
leftBox.setPrefWidth(180);
topBox.setPrefHeight(40);
//TopBox
Button tb1 = new Button("TB 1");
Button tb2 = new Button("TB 2");
topBox.getChildren().addAll(tb1,tb2);
//LeftBox
Button lb1 = new Button("LB 1");
Button lb2 = new Button("LB 2");
Button lb3 = new Button("LB 3");
leftBox.getChildren().addAll(lb1,lb2,lb3);
int rectangleAmountPerRowCol = 50;
for(int k = 0; k < rectangleAmountPerRowCol; k++) {
for (int i = 0; i < rectangleAmountPerRowCol; i++) {
Rectangle r = new Rectangle();
r.setStroke(Color.RED);
r.setStrokeWidth(0.1);
Pane pane = new Pane(r);
r.widthProperty().bind(pane.widthProperty());
r.heightProperty().bind(pane.heightProperty());
GridPane.setHgrow(pane, Priority.ALWAYS);
GridPane.setVgrow(pane, Priority.ALWAYS);
gridPane.add(pane, i, k);
}
}
Scene mainScene = new Scene(mainPane, 840,700);
primaryStage.setScene(mainScene);
primaryStage.show();
}
}
Мне не удалось заставить ваш пример Rectangle работать. Всего один огромный прямоугольник, заполняющий экран.
@DaveB У меня это работает нормально. Обратите внимание, что ширина обводки небольшая (скопирована из OP), поэтому может потребоваться большое разрешение, чтобы увидеть края прямоугольников. Попробуйте увеличить ширину обводки до чего-то более разумного.
Хотя @James_D прав в том, что прямоугольники плохо сочетаются с макетами и изменениями размеров, это можно сделать.
Вот пример, в котором прямоугольники просто используются в GridPane:
class Example1App : Application() {
override fun start(stage: Stage) {
stage.scene = Scene(createContent(), 400.0, 400.0)
stage.show()
}
private fun createContent(): Region = BorderPane().apply {
val sideWidthProperty: DoubleProperty = SimpleDoubleProperty()
val topHeightProperty: DoubleProperty = SimpleDoubleProperty()
val sceneHeightProperty: ObservableValue<Double> =
sceneProperty().flatMap { it.heightProperty().asObject() }.orElse(0.0)
val sceneWidthProperty: ObservableValue<Double> =
sceneProperty().flatMap { it.widthProperty().asObject() }.orElse(0.0)
top = HBox(10.0, Button("TB 1"), Button("TB 2")).apply {
border = Border(BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, null, BorderWidths(1.0)))
prefHeight = 40.0
topHeightProperty.bind(heightProperty())
}
left = VBox(10.0, Button("LB 1"), Button("LB 2"), Button("LB 3")).apply {
border = Border(BorderStroke(Color.BLACK, BorderStrokeStyle.SOLID, null, BorderWidths(1.0)))
prefWidth = 180.0
sideWidthProperty.bind(widthProperty())
}
center = StackPane(GridPane().apply {
val rectangleAmountPerRowCol = 50
val sizeBinding = Bindings.createDoubleBinding(
{
calcSize(
sceneHeightProperty.value,
sceneWidthProperty.value,
sideWidthProperty.value,
topHeightProperty.value,
rectangleAmountPerRowCol
)
},
sceneWidthProperty,
sceneHeightProperty,
sideWidthProperty,
topHeightProperty
)
for (k in 0 until rectangleAmountPerRowCol) {
for (i in 0 until rectangleAmountPerRowCol) {
add(Rectangle().apply {
widthProperty().bind(sizeBinding)
heightProperty().bind(sizeBinding)
stroke = Color.RED
strokeWidth = 0.1
}, i, k)
}
}
})
}
private fun calcSize(
sceneHeight: Double?,
sceneWidth: Double,
sideWidth: Double,
topHeight: Double,
rowSize: Int
): Double = sceneHeight?.let {
(min(sceneWidth - sideWidth, sceneHeight - topHeight) / rowSize) - 1.0
} ?: 0.0
}
fun main() = Application.launch(Example1App::class.java)
Kotlin, потому что Java сейчас мне кажется слишком болезненным, но идеи в любом случае одни и те же. Это JavaFX 19+, потому что вы ObservableValue.flatMap() использовали.
По сути, это то же самое, что и макет ОП, только немного подчищенный, чтобы его было проще.
Определено 4 DoubleProperties. Первые два привязаны к ширине и высоте верхнего и левого боковых блоков. Третье — это flatMap() свойства Scene высоты и ширины.
Есть Binding, который вычисляет размер Rectangles, беря высоту и ширину Scene и вычитая текущие размеры верхней и левой панелей, а затем разделяя на количество Rectangles. Немного отрезано, чтобы учесть пробелы между Rectangles.
Первоначально я проектировал только GridPane по высоте и ширине, но GridPane растет, заполняя Scene, если он увеличивается, но не сжимается обратно, когда он уменьшается в размере. Итак, это не сработало.
Привязка расчета зависит от тех 4 DoubleProperty, определенных в начале кода макета. Это кажется неудобным (и так оно и есть), потому что большинство классов макета просто превысят размеры Scene, если их содержимое слишком велико, и вы не сможете заставить их сжиматься только потому, что Scene стал меньше. Следовательно, размер Rectangle необходимо привязать к размеру Scene, используя математические вычисления для учета области, не являющейся GridPane.
На самом деле это работает очень хорошо. Rectangles растут и уменьшаются, как и следовало ожидать.
На момент расчета ширины в SceneGraph еще ничего нет, а ширина GridPane равна нулю. Создайте прямоугольники и привяжите свойства высоты и ширины к свойствам ширины и высоты GridPane. Тогда это должно сработать.