I have a 20x20 GridPane
of uniformly sized squares which is representing data from a two-dimensional array of the same size, placed in a ScrollPane
such that one can use it to pan around. The width and height in pixels of the GridPane
are both much greater than the respective dimensions of the ScrollPane
.
My problem is that I have been unable to make a functional method that centers the viewport of the ScrollPane
on the GridPane
square located at a set of designated coordinates.
My understanding of the scrollPane.setHvalue(double)
and scrollPane.setVvalue(double)
which I have been attempting to utilize is that the double value passed into them is the percentage of the axis that it sets the scroll to. However, when I write the code assuming that, it fails to center the view on the intended square, sometimes with the square not even being anywhere on the viewport:
private void centerViewOn(double x, double y){
scrollPane.setHvalue(x/grid.getDimensionX());
scrollPane.setVvalue(y/grid.getDimensionY());
}
Where grid
is an instance of a class whose only relevance to this question is that it has the dimensions of the grid, and double x
and double y
are the x,y coordinates in the GridPane of the square that is intended to be centered.
What am I doing wrong, and how can I fix this?
EDIT: In accordance with James_D's suggestion, I'm including what I made as a Minimal, Complete, and Verifiable example:
private ScrollPane scrollPane;
private GridPane gridPane;
private double dimensionX;
private double dimensionY;
@Override
public void start(Stage primaryStage) {
scrollPane = new ScrollPane();
gridPane = new GridPane();
dimensionX = 20;
dimensionY = 20;
Pane temp;
for(int x = 0; x < dimensionX; ++x){
for(int y = 0; y < dimensionY; ++y){
temp = new Pane();
temp.setPrefSize(100, 100);
temp.setOnMouseClicked( e -> {
centerViewOn(GridPane.getColumnIndex((Pane)e.getSource()), GridPane.getRowIndex((Pane)e.getSource()));
((Pane)e.getSource()).setStyle("-fx-background-color: blue");
});
gridPane.add(temp, x, y);
}
}
gridPane.setGridLinesVisible(true);
scrollPane.setContent(gridPane);
primaryStage.setScene(new Scene(scrollPane));
primaryStage.show();
}
private void centerViewOn(double x, double y){
double viewportWidth = scrollPane.getViewportBounds().getWidth();
double maxHscrollPixels = gridPane.getWidth() - viewportWidth;
double hscrollPixels = viewportWidth / 2 - (x + 0.5) * dimensionX / 20;
scrollPane.setHvalue(hscrollPixels / maxHscrollPixels);
double viewportHeight = scrollPane.getViewportBounds().getHeight();
double maxVscrollPixels = gridPane.getHeight() - viewportHeight;
double vscrollPixels = viewportHeight / 2 - (y + 0.5) * dimensionY / 20;
scrollPane.setVvalue(vscrollPixels / maxVscrollPixels);
}
The scroll pane's horizontal scroll values vary between scrollPane.getHmin()
and scrollPane.getHmax()
. Those are in arbitrary units, with the amount scrolled in pixels being linearly interpolated across the range.
The possible horizontal scroll amount in pixels ranges from zero to (grid pane width - scrollpane viewport width).
So you have
(hvalue - hmin) / (hmax - hmin) = scrollPixels / (gridPaneWidth - viewportWidth)
and so after some algebra you get
hvalue = hmin + (hmax - hmin) * scrollPixels / (gridPaneWidth - viewportWidth)
Assuming each column is the same width, and there are dimensionX
columns, x
columns will have total width x * gridPaneWidth / dimensionX
pixels. Since you want to scroll to the center of the cell, add another half cell, and since you want the center of the cell to be in the center of the viewport, subtract viewportWidth / 2
. So you have:
hscrollPixels = (x + 0.5) * gridPane.getWidth() / dimensionX - viewportWidth / 2
Scrolling vertically is exactly analogous.
The default values for hmin
and hmax
are 0
and 1
, respectively, so you can simplify things a bit if you assume these don't change.
So I think you end up with
private void centerViewOn(double x, double y){
double viewportWidth = scrollPane.getViewportBounds().getWidth();
double maxHscrollPixels = gridPane.getWidth() - viewportWidth;
double hscrollPixels = (x + 0.5) * gridPane.getWidth() / dimensionX - viewportWidth / 2;
scrollPane.setHvalue(hscrollPixels / maxHscrollPixels);
double viewportHeight = scrollPane.getViewportBounds().getHeight();
double maxVscrollPixels = gridPane.getHeight() - viewportHeight;
double vscrollPixels = (y + 0.5) * gridPane.getHeight() / dimensionY - viewportHeight / 2;
scrollPane.setVvalue(vscrollPixels / maxVscrollPixels);
}
Note that not all cells can be scrolled to the center: if they are too close to the edges, they will be scrolled as close as possible to the center, but the scroll pane will not allow blank space (unless the content is smaller than the viewport).
Here's the complete example:
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.layout.GridPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class ScrollToCenter extends Application {
private ScrollPane scrollPane;
private GridPane gridPane;
private double dimensionX;
private double dimensionY;
@Override
public void start(Stage primaryStage) {
scrollPane = new ScrollPane();
gridPane = new GridPane();
dimensionX = 20;
dimensionY = 20;
for(int x = 0; x < dimensionX; ++x){
for(int y = 0; y < dimensionY; ++y){
Pane temp = new Pane();
temp.setPrefSize(100, 100);
temp.setOnMouseClicked( e -> {
centerViewOn(GridPane.getColumnIndex(temp), GridPane.getRowIndex(temp));
temp.setStyle("-fx-background-color: blue");
});
gridPane.add(temp, x, y);
}
}
gridPane.setGridLinesVisible(true);
scrollPane.setContent(gridPane);
primaryStage.setScene(new Scene(scrollPane));
primaryStage.show();
}
private void centerViewOn(double x, double y){
double viewportWidth = scrollPane.getViewportBounds().getWidth();
double maxHscrollPixels = gridPane.getWidth() - viewportWidth;
double hscrollPixels = (x + 0.5) * gridPane.getWidth() / dimensionX - viewportWidth / 2;
scrollPane.setHvalue(hscrollPixels / maxHscrollPixels);
double viewportHeight = scrollPane.getViewportBounds().getHeight();
double maxVscrollPixels = gridPane.getHeight() - viewportHeight;
double vscrollPixels = (y + 0.5) * gridPane.getHeight() / dimensionY - viewportHeight / 2;
scrollPane.setVvalue(vscrollPixels / maxVscrollPixels);
}
public static void main(String[] args) {
launch(args);
}
}