JavaFX Scrollpane Draw At Centre

August 31, 2021

I'm creating a JavaFX application which has a ScrollPane containing a Canvas. I wanted to render an image at the centre of the viewport, regardless of the content scroll/panned position.

I created a helper method that takes the scrollpane and the content and returns a Point2D containing the centre point of the currently visible area of the Canvas.

package com.example.infrastructure.utilities; import javafx.geometry.Bounds; import javafx.geometry.Point2D; import javafx.scene.Node; import javafx.scene.control.ScrollPane; public class ScrollHelper { public static Point2D getViewportCentre(ScrollPane scrollPane, Node content) { Bounds viewport = scrollPane.getViewportBounds(); Bounds contentBounds = content.getLayoutBounds(); // horizontal parameters double horizontalMin = scrollPane.getHmin(); double horizontalMax = scrollPane.getHmax(); double horizontalValue = scrollPane.getHvalue(); double contentWidth = contentBounds.getWidth(); double viewportWidth = viewport.getWidth(); // vertical parameters double verticalMin = scrollPane.getVmin(); double verticalMax = scrollPane.getVmax(); double verticalValue = scrollPane.getVvalue(); double contentHeight = contentBounds.getHeight(); double viewportHeight = viewport.getHeight(); double horizontalOffset = Math.max(0, contentWidth - viewportWidth) * (horizontalValue - horizontalMin) / (horizontalMax - horizontalMin); double verticalOffset = Math.max(0, contentHeight - viewportHeight) * (verticalValue - verticalMin) / (verticalMax - verticalMin); return new Point2D(horizontalOffset + viewportWidth / 2, verticalOffset + viewportHeight / 2); } }

The hvalue and vvalue are percentage values representing the scroll position. The method calculates the vertical and horizontal offset of the viewport, then adds half the viewport width and height to find the centre point.

When testing the method I got an error java.lang.IllegalStateException: Toolkit not initialized, indicating that the JavaFx runtime was not initialized correctly, so I needed to make use of a framework for testing JavaFX applications in JUnit5.

I added the dependency to by gradle.build....

dependencies { .... testImplementation('org.testfx:testfx-junit5:4.0.16-alpha') }

Then for the test, I used the JUnit5 ExtendWidth attribute to initialize the JavaFX runtime. For testing I picked some easy values to work with, a scrollpane viewport size of 100x100 with a canvas size of 200x200. The scene contains a Group as the root so that I can call applyCss and layout methods to calculate the layout before I test the result.

package com.example.infrastructure.utilities; import javafx.geometry.Point2D; import javafx.scene.Group; import javafx.scene.Scene; import javafx.scene.canvas.Canvas; import javafx.scene.control.ScrollPane; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.testfx.framework.junit5.ApplicationExtension; import static org.assertj.core.api.Assertions.assertThat; @ExtendWith(ApplicationExtension.class) class ScrollHelperTest { @Test void getViewportCentre_No_Scroll_ReturnsExpectedPoint() { Group root = new Group(); Scene scene = new Scene(root, 320, 240); ScrollPane scrollPane = new ScrollPane(); scrollPane.setPrefViewportWidth(100); scrollPane.setPrefViewportHeight(100); scrollPane.setHvalue(0); scrollPane.setVvalue(0); root.getChildren().add(scrollPane); Canvas content = new Canvas(); content.setWidth(200); content.setHeight(200); scrollPane.setContent(content); root.applyCss(); root.layout(); Point2D point = ScrollHelper.getViewportCentre(scrollPane, content); assertThat(point.getX()).isEqualTo(50); assertThat(point.getY()).isEqualTo(50); } @Test void getViewportCentre_WhenScrolled_ReturnsExpectedPoint() { Group root = new Group(); Scene scene = new Scene(root, 100, 100); ScrollPane scrollPane = new ScrollPane(); scrollPane.setPrefViewportWidth(100); scrollPane.setPrefViewportHeight(100); // scroll to a certain position scrollPane.setHvalue(0.5); scrollPane.setVvalue(0.5); root.getChildren().add(scrollPane); Canvas content = new Canvas(); content.setWidth(200); content.setHeight(200); scrollPane.setContent(content); root.applyCss(); root.layout(); Point2D point = ScrollHelper.getViewportCentre(scrollPane, content); assertThat(point.getX()).isEqualTo(100); assertThat(point.getY()).isEqualTo(100); } }

To Lean more about JavaFX, read the docs here https://openjfx.io/

Sharing is caring

Stay In Touch

Connect with Me

© 2021, Created by me using Gatsby