0
votes

I want to have a rectangular grid with square cells, which can be zoomed and dragged (like you would a map). So I put it in a ScrollPane, altough it behaves rather weirdly at certain sizes. The size of the grid will change dynamically.

On the picture there is a grid of size 20x20. Grid of size 20x20

  1. Not all gaps are shown inside the grid. Notice that it should be 20 squares but isn't. Both Vgap and Hgap is 1.
  2. The grid itself is larger than the content, which is not good, because when it will be zoomed I could drag beyond the border of the grid.
  3. I would like to center the GridPane inside the ScrollPane when it is smaller than the scrollpane. (Gridpane alignment="CENTER" is apparently not doing anything)

Minimal, Complete, and Verifiable example

FXML:

<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.layout.GridPane?>

<ScrollPane hbarPolicy="NEVER" hvalue="0.5" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" pannable="true" prefHeight="400.0" prefWidth="600.0" vbarPolicy="NEVER" vvalue="0.5" xmlns="http://javafx.com/javafx/9" xmlns:fx="http://javafx.com/fxml/1" fx:controller="bug.app.Bug">
    <GridPane fx:id="gridPane" alignment="CENTER" hgap="1.0" style="-fx-background-color: red;" vgap="1.0">
    </GridPane>
</ScrollPane>

Controller + Main:

package bug.app;

import javafx.application.Application;
import javafx.beans.binding.DoubleBinding;
import javafx.beans.property.DoubleProperty;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.geometry.Insets;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

public class Bug extends Application {
    private static final int HEIGHT = 20;
    private static final int WIDTH = 20;
    private static final double MIN_CELL_SIZE = 2;
    @FXML private GridPane gridPane;
    private DoubleProperty zoomRatio = new SimpleDoubleProperty(5);

    @FXML
    private void initialize() {
        final DoubleBinding cellSizeBinding = zoomRatio.multiply(MIN_CELL_SIZE);

        List<Node> cells = new ArrayList<>(HEIGHT * WIDTH);
        for (int row = 0; row < HEIGHT; row++) {
            for (int col = 0; col < WIDTH; col++) {
                final Pane cell = new Pane();
                cell.setBackground(new Background(new BackgroundFill(Color.YELLOW, CornerRadii.EMPTY, Insets.EMPTY)));

                cell.setMinSize(MIN_CELL_SIZE, MIN_CELL_SIZE);
                cell.setMaxWidth(Region.USE_PREF_SIZE);
                cell.setMaxHeight(Region.USE_PREF_SIZE);
                cell.prefWidthProperty().bind(cellSizeBinding);
                cell.prefHeightProperty().bind(cellSizeBinding);

                GridPane.setConstraints(cell, col, row, 1, 1);

                cells.add(cell);
            }
        }
        gridPane.getChildren().clear();
        gridPane.getChildren().addAll(cells);
    }

    public static void main(String[] args) {
        launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws IOException {
        Parent root = FXMLLoader.load(
                getClass().getResource("/view/bugView.fxml"));
        final Scene scene = new Scene(root, 600, 400);

        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

NB: I chose a Pane as a Cell because I want to subclass it and eventually fill it (with probably Shapes?) with different colours dividing the square into 1-4 parts. Imagine two or four triangles forming a square. I would also like to resize this elegantly (AFAIK Shapes are not resizable), maybe somehow binding the size of the inner Shapes to the Panes' size will do the magic. I am not that far yet, but feel free to advise on this too.

2

2 Answers

0
votes

wrap the gridpane inside a stackpane, this will center the gridpane

0
votes

Problems 1 & 2

I found a workaround for issues 1. and 2. with omitting Vgap and Hgap, because apparently it is buggy.

Instead of using the gap property of the GridPane I set the margin on each cell:

GridPane.setMargin(cell, new Insets(0.5));

This however rounds the margin to 1 because of the default snapToPixel="true" on GridPane and thus resulting to gaps of 2px. This is unconvenient, because I want the squares to be as small as 2x2px, so the 2px gap is too much. I shall probably set snapToPixel to false on the GridPane. Needs more testing.

Problem 3

I tried wrapping the GridPane in a StackPane as suggested by @Silas Gyeme.

Simply wrapping it in a StackPane was not enough. I had to set both fitToHeight="true" & fitToWidth="true" on the ScrollPane.

Also had to bind the cell's minSize to cellSizeBinding, because it was resizing the squares when I shrinked the window smaller.

cell.minWidthProperty().bind(cellSizeBinding);
cell.minHeightProperty().bind(cellSizeBinding);

EDIT: I tried omitting the StackPane and I do not actually need it! Only setting fitToHeight and fitToWidth is enough, the GridPane will stretch well.