in order to achieve something like a minimap, I used a StackPane
to layer a Pane
on top of an ImageView
. I bound the the scaling x- and y-properties of the StackPane
to a Slider
in order to achieve a zooming effect. I added a few Circle
objects to the Pane
. These circles should of course also be zoomable (which they are), but in this case more importantly, they should stay at their relative position when scaling happens. The following picture shows what the GUI looks like so far.
As of yet, I have distinguished two problematic cases.
In this more simple case, everything works fine, except for the fact that zooming in on the image seemingly makes it exceed the bounds of the StackPane
. As a result, you cannot scroll to the image's border anymore. Check the next screenshot to see what I mean.
To make the StackPane
grow appropriately, I bound its size properties to the zoom factor multiplied with the image's size. However, I am pretty sure that this approach somehow messes with the underlying coordinate structure, because now the circle 'leaves' its position at a scale factor > 1. Said behaviour is depicted in the last screenshot.
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.Slider;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.scene.layout.StackPane;
import javafx.scene.layout.VBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Circle;
import javafx.stage.Stage;
public class ScalingProblem extends Application {
private static final String IMAGE_URL = "https://st2.depositphotos.com/3034795/10774/i/950/depositphotos_107745620-stock-photo-labyrinth-or-maze.jpg";
@Override
public void start(Stage primaryStage) throws Exception {
// Prepare the main part of the GUI.
Image backgroundImage = new Image(IMAGE_URL);
ImageView imageView = new ImageView(backgroundImage);
Circle circle = new Circle(40, Color.ORANGE);
circle.setStroke(Color.BLUE);
circle.setStrokeWidth(5);
Pane layer = new Pane(circle);
StackPane stackPane = new StackPane(imageView, layer);
ScrollPane scrollPane = new ScrollPane(stackPane);
scrollPane.setPannable(true);
// Prepare the controls on the left side.
Slider xSlider = new Slider();
Slider ySlider = new Slider();
Slider radiusSlider = new Slider();
Slider zoomSlider = new Slider();
VBox vBox = new VBox(xSlider, ySlider, radiusSlider, zoomSlider);
vBox.setSpacing(10);
vBox.setPrefWidth(200);
// Adjust the sliders.
adjustSlider(xSlider, 0, 1000, 50);
adjustSlider(ySlider, 0, 1000, 50);
adjustSlider(radiusSlider, 0, 100, 15);
adjustSlider(zoomSlider, 0, 3, 1);
zoomSlider.setMajorTickUnit(1);
zoomSlider.setMinorTickCount(1);
zoomSlider.setShowTickMarks(true);
zoomSlider.setShowTickLabels(true);
// Do all necessary binding.
circle.centerXProperty().bind(xSlider.valueProperty());
circle.centerYProperty().bind(ySlider.valueProperty());
circle.radiusProperty().bind(radiusSlider.valueProperty());
stackPane.scaleXProperty().bind(zoomSlider.valueProperty());
stackPane.scaleYProperty().bind(zoomSlider.valueProperty());
/*
* So far, I always needed to make the StackPane's size grow / shrink depending on the zoom level. If the
* following two lines are commented out, zooming in will make the ImageView exceed the bounds of the StackPane.
* As a result, the StackPane does not 'grow' appropriately so that you cannot pan the view to see the edges of
* the background.
*/
stackPane.prefWidthProperty().bind(zoomSlider.valueProperty().multiply(backgroundImage.getWidth()));
stackPane.prefHeightProperty().bind(zoomSlider.valueProperty().multiply(backgroundImage.getHeight()));
// Piece the GUI together.
BorderPane root = new BorderPane();
root.setCenter(scrollPane);
root.setLeft(vBox);
root.setPrefHeight(300);
root.setPrefWidth(500);
// Show the stage.
primaryStage.setScene(new Scene(root));
primaryStage.show();
}
private void adjustSlider(Slider slider, double min, double max, double start) {
slider.setMin(min);
slider.setMax(max);
slider.setValue(start);
}
public static void main(String[] args) {
launch();
}
}
ScrollPane
uses the unmodified layout bounds to determine the content size. This does not include the scaling transformation. To prevent the content from growing beyond the scrollable area, wrap the scaled node in a Group
.
Modify the ScrollPane
creation like this:
ScrollPane scrollPane = new ScrollPane(new Group(stackPane));
and remove the binding to the prefWidth
/prefHeight
, i.e. delete the following lines:
stackPane.prefWidthProperty().bind(zoomSlider.valueProperty().multiply(backgroundImage.getWidth()));
stackPane.prefHeightProperty().bind(zoomSlider.valueProperty().multiply(backgroundImage.getHeight()));