Search code examples
javauser-interfacejavafxscaling

JavaFX AnchorPane scaling



I'm having trouble scaling components inside of the anchor pane relating to the size of the anchor pane.
What I want to achieve is a rectangle 50% x 50% the size of the anchor pane positioned at the center.

I'm currently trying to achieve this with this code:

public class HelloApplication extends Application {

    @Override
    public void start(Stage stage) throws IOException {
        AnchorPane centerPane = new AnchorPane();

        Rectangle rect = new Rectangle();
        rect.setFill(Color.BLUE);

        AnchorPane.setTopAnchor(rect, .25 * centerPane.getHeight());
        AnchorPane.setBottomAnchor(rect, .25 * centerPane.getHeight());
        AnchorPane.setLeftAnchor(rect, .25 * centerPane.getHeight());
        AnchorPane.setRightAnchor(rect, .25 * centerPane.getHeight());

        Scene scene = new Scene(centerPane, 320, 240);

        stage.setTitle("Anchor Pane Scaling test");
        stage.setScene(scene);
        stage.show();
    }

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

However I get no rectangle displayed.

I want to know if there's a way to fix this approach or if there's a better way of scaling Nodes inside panes in general that I should try.


Solution

  • There are two reasons your code doesn't work:

    1. Rectangle (and all Shape) instances are not resizable. This means a parent (such as AnchorPane) can not resize these nodes (specifically, Rectangle.resize() is a no-op).
    2. You are calling centerPane.getHeight() before the pane has been laid out. This means its height (and width) will be zero at that time. You really need to re-do the layout any time that width and height change.

    JavaFX supports a collection of Pane subclasses which manage layout. (See, for example, this tutorial, or the API docs) for details.) Typically, if you require a layout strategy that is not supported by one of these layout panes, then you should define your own by subclassing Region or Pane. You should, at a minimum, override the layoutChildren() method, along with the methods to compute the min/pref/max width and height.

    Whether or not your actual use case warrants the amount of coding needed to do this is not clear from your question. However, here is a quick example of a container pane that centers its content and has a configurable value for determining what (linear) proportion of its size its content should occupy.

    package org.jamesd.examples.centeringpane;
    
    import javafx.beans.property.DoubleProperty;
    import javafx.beans.property.SimpleDoubleProperty;
    import javafx.geometry.Orientation;
    import javafx.scene.Node;
    import javafx.scene.layout.Region;
    
    public class CenteringPane extends Region {
    
        private final Node content ;
        private DoubleProperty sizeProportion = new SimpleDoubleProperty(1);
    
        public CenteringPane(Node content) {
            this.content = content;
            getChildren().add(content);
            sizeProportion.addListener(obs -> requestLayout());
        }
    
        @Override
        protected void layoutChildren() {
            if (content.isManaged()) {
                double w = getWidth();
                double h = getHeight();
                double targetWidth = (w - snappedLeftInset() - snappedRightInset()) * getSizeProportion();
                double targetHeight = (h - snappedTopInset() - snappedBottomInset()) * getSizeProportion();
                double x = (w - targetWidth) / 2;
                double y = (h - targetHeight) / 2;
                content.resizeRelocate(snapPositionX(x), snapPositionY(y), snapSizeX(targetWidth), snapSizeY(targetHeight));
            }
        }
    
        public double getSizeProportion() {
            return sizeProportion.get();
        }
    
        public DoubleProperty sizeProportionProperty() {
            return sizeProportion;
        }
    
        public void setSizeProportion(double sizeProportion) {
            this.sizeProportion.set(sizeProportion);
        }
    
        @Override
        protected double computePrefWidth(double height) {
            return snappedLeftInset() + snappedRightInset() + content.prefWidth(height) / getSizeProportion();
        }
    
        @Override
        protected double computePrefHeight(double width) {
            return snappedTopInset() + snappedBottomInset() + content.prefHeight(width) / getSizeProportion();
        }
    
        @Override
        protected double computeMinWidth(double height) {
            return snappedLeftInset() + snappedRightInset() + content.minWidth(height) / getSizeProportion();
        }
    
        @Override
        protected double computeMinHeight(double width) {
            return snappedTopInset() + snappedBottomInset() + content.minHeight(width) / getSizeProportion();
        }
    
        @Override
        protected double computeMaxWidth(double height) {
            return snappedLeftInset() + snappedRightInset() + content.maxWidth(height) / getSizeProportion();
        }
    
        @Override
        protected double computeMaxHeight(double width) {
            return snappedTopInset() + snappedBottomInset() + content.maxHeight(width) / getSizeProportion();
        }
    
        @Override
        public Orientation getContentBias() {
            return content.getContentBias();
        }
    }
    

    Here is a quick test case:

    package org.jamesd.examples.centeringpane;
    
    import javafx.application.Application;
    import javafx.geometry.Insets;
    import javafx.geometry.Pos;
    import javafx.scene.Scene;
    import javafx.scene.control.Slider;
    import javafx.scene.layout.Background;
    import javafx.scene.layout.BorderPane;
    import javafx.scene.layout.HBox;
    import javafx.scene.layout.Pane;
    import javafx.scene.paint.Color;
    import javafx.stage.Stage;
    
    public class HelloApplication extends Application {
        @Override
        public void start(Stage stage) {
            Pane pane = new Pane();
            pane.setBackground(Background.fill(Color.BLUE));
            CenteringPane centeringPane = new CenteringPane(pane);
    
            BorderPane root = new BorderPane(centeringPane);
            Slider slider = new Slider(0, 1, 0.5);
    
            centeringPane.sizeProportionProperty().bind(slider.valueProperty());
            HBox controls = new HBox(5, slider);
            controls.setPadding(new Insets(10));
            controls.setAlignment(Pos.CENTER);
            root.setTop(controls);
            Scene scene = new Scene(root, 800, 500);
            stage.setScene(scene);
            stage.show();
        }
    
        public static void main(String[] args) {
            launch();
        }
    }
    

    enter image description here