I am redoing a project i did with processing as i wanted to get some practice in JavaFX as i am going to need it in uni next year and it will give me some more freedom with the ui than processing does. This means i am new to doing stuff with javafx, sorry.
I am trying to have a container in my main window that holds some panes. On these panes i'll later draw on indiviudally. I went with a FlowPane and the smaller panes are just, well, panes. I wanted that if the size of the window changes, more panes get created (deleted) in a way that always the perfect amount of panes is visible on the container (This is a bit hard to explain, but have a look at the pictures linked below, you will then get the idea). This is working quite fine 99% of the time but sometimes the panes overflow the flowpane. I tried a lot to fix that (pretty much brute-forced it lol) but i could not get it better than this.
Undesired Behavior
Desired Behavior
Github of the project if you want to try it yourself
Code of the controller:
public class MainWindowController {
private int sizeOfOneGame = 100; //In pixel
//Space between the frame of the Pane that holds all games and the games
private int[] paddingAroundGames = {30, 30, 30, 30}; //Top, Right, Bottom, Left
//Space between two games
private int[] gapBetweenGames = {10, 10}; //X, Y
@FXML
FlowPane flowPaneGames;
public void initialize(){
//Sets space between two games in the flowPane
flowPaneGames.setHgap(gapBetweenGames[0]);
flowPaneGames.setVgap(gapBetweenGames[1]);
//Sets space between all games and the border of the flowPane
flowPaneGames.setPadding(new Insets(paddingAroundGames[0], paddingAroundGames[1], paddingAroundGames[2], paddingAroundGames[3]));
//Draws one frame around the flowPane
flowPaneGames.setStyle("-fx-border-color: black");
//Aligns the panes to the center
flowPaneGames.setAlignment(Pos.BASELINE_CENTER);
//Adds listeners to the width and height of the flowPane holding the games -> Adjusts shown Panes on size change
flowPaneGames.widthProperty().addListener(e -> flowPaneGamesSizeChanged());
flowPaneGames.heightProperty().addListener(e -> flowPaneGamesSizeChanged());
}
private void flowPaneGamesSizeChanged(){
//TODO: Sometimes some panes are bigger than the flowPane
//Available space for games x and y
int totalSpaceX = (int) (flowPaneGames.getWidth() - paddingAroundGames[1] - paddingAroundGames[3] + gapBetweenGames[0]);
int totalSpaceY = (int) (flowPaneGames.getHeight() - paddingAroundGames[0] - paddingAroundGames[2] + gapBetweenGames[1]);
int totatlSizeOfOneGameX = sizeOfOneGame + gapBetweenGames[0];
int totatlSizeOfOneGameY = sizeOfOneGame + gapBetweenGames[1];
int totalSpaceForGamesX = (int) (((double) totalSpaceX) / ((double) totatlSizeOfOneGameX));
int totalSpaceForGamesY = (int) (((double) totalSpaceY) / ((double) totatlSizeOfOneGameY));
//Total amount of games
int totalSpaceForGames = totalSpaceForGamesX * totalSpaceForGamesY;
System.out.println("Width: " + flowPaneGames.getWidth());
System.out.println("Height: " + flowPaneGames.getHeight());
//We have more games shown that there should be we remove the last ones
while (flowPaneGames.getChildren().size() > totalSpaceForGames) {
//We remove the last game
flowPaneGames.getChildren().remove(flowPaneGames.getChildren().size() - 1);
}
//While we have less games shown that there should be we add new ones
while (flowPaneGames.getChildren().size() < totalSpaceForGames) {
flowPaneGames.getChildren().add(generateNewPane());
}
}
private Pane generateNewPane(){
//Generates a new pane and returns it
Pane pane = new Pane();
pane.setPrefSize(sizeOfOneGame, sizeOfOneGame);
pane.setStyle("-fx-border-color: black");
return pane;
}
}
I would be really happy if someone could help me fix this as (even it occurs only in a few cases) it really bothers me.
Sidenote: I already asked this question here but i did hardly effort in the question which resulted in hardly any answers, i tried to do it better this time. Ppl were also complaining that i don't really follow the naming conventions for java, i tried to listen to that and hope that the code is better to read now.
The reason for the problem is that the border width of the container is not taken into account. To take the border width of the container into account, totalSpaceX
and totalSpaceY
must be determined as follows:
int totalSpaceX = (int) (flowPaneGames.getWidth() - paddingAroundGames[1] - paddingAroundGames[3] + gapBetweenGames[0] - flowPaneGames.getBorder().getStrokes().get(0).getWidths().getLeft() - flowPaneGames.getBorder().getStrokes().get(0).getWidths().getRight());
int totalSpaceY = (int) (flowPaneGames.getHeight() - paddingAroundGames[0] - paddingAroundGames[2] + gapBetweenGames[1] - flowPaneGames.getBorder().getStrokes().get(0).getWidths().getTop() - flowPaneGames.getBorder().getStrokes().get(0).getWidths().getBottom());
The figure on the left shows a container height and width of 492
each, taking the border width of the container into account. The border width of the container is 1
by default. Exactly 4 child panes fit into the width: 492 = 2 x border width of container (= 1) + 2 * padding (= 30) + 3 * gap (= 10) + 4 * child pane width (= 100)
. The same applies to the height.
If the width and/or height is reduced by 1
, 1
child pane each is omitted in the width and/or height. The figure in the middle shows a container height and width of 491
each with consideration of the border width of the container.
Without taking the border width of the container into account, more space is assumed than is actually available and too many child panes are created. The figure on the right shows a container height and width of 491
each without taking the border width into account (i.e. it shows what the current code does).
EDIT:
Following a suggestion from @Slaw, totalSpaceX
and totalSpaceY
can be calculated more easily by using Region#snappedXXXInset()
:
int totalSpaceX = (int) (flowPaneGames.getWidth() - flowPaneGames.snappedLeftInset() - flowPaneGames.snappedRightInset() + gapBetweenGames[0]);
int totalSpaceY = (int) (flowPaneGames.getHeight() - flowPaneGames.snappedTopInset() - flowPaneGames.snappedBottomInset() + gapBetweenGames[1]);
Also snapToPixel
should be set (which is the default).