Search code examples
javajavafxvertical-alignmentvertical-scrollingscrollpane

Misalignment of two synced ScrollPanes


I try to align the vertical scroll position of two javafx.scene.control.ScrollPanes via

sp1.vvalueProperty().bindBidirectional(sp2.vvalueProperty());

The problem is that one of these ScrollPanes may have a horizontal scroll bar. So the more I scroll down the ScrollPanes, the more they get misaligned (see screenshot). How can I handle this?

javafx scollpane misaligned


Solution

  • It's impossible to do this with 2 ScrollPanes and contents of equal height unless you display the scrollbar in both ScrollPanes:

    Consider the case where the content fits the viewport of the left ScrollPane exactly. The viewPort of the right ScrollPane can be scrolled by the ScrollBar height. Modifying the left ScrollPane is not possible.

    Since the expected result seems to be some kind of scale, you could simply use a Pane with a child you apply transformY to and a clip. The formula to calculate the pixel to be placed at the top, use

    top = vvalue * (contentHeight - viewportHeight)
    

    Example

    private static Label createLabel(int num, boolean mark) {
        Label label = new Label(Integer.toString(num));
        label.setPrefSize(50, 50);
        label.setMaxSize(Double.MAX_VALUE, Region.USE_PREF_SIZE);
        label.setStyle(mark ? "-fx-background-color: #FFFFFF" : "-fx-background-color: #BBBBBB;");
        return label;
    }
    
    @Override
    public void start(Stage primaryStage) throws Exception {
        VBox scale = new VBox();
        scale.setMinHeight(Region.USE_PREF_SIZE);
        GridPane content = new GridPane();
    
        for (int i = 0; i < 40; i++) {
            boolean b = ((i % 2) == 0);
            scale.getChildren().add(createLabel(i, !b));
    
            for (int j = 0; j < 10; j++) {
                content.add(createLabel(i * 10 + j, b), j, i);
            }
        }
    
        AnchorPane scaleContainer = new AnchorPane(scale);
        scaleContainer.setMinWidth(30);
        scaleContainer.setMinHeight(0);
        AnchorPane.setLeftAnchor(scale, 0d);
        AnchorPane.setRightAnchor(scale, 0d);
    
        Rectangle clip = new Rectangle();
        scaleContainer.setClip(clip);
        clip.widthProperty().bind(scaleContainer.widthProperty());
        clip.heightProperty().bind(scaleContainer.heightProperty());
    
        ScrollPane scroll = new ScrollPane(content);
    
        scale.translateYProperty().bind(Bindings.createDoubleBinding(() -> {
            double contentHeight = content.getHeight();
            double viewportHeight = scroll.getViewportBounds().getHeight();
            if (contentHeight <= viewportHeight) {
                return 0d;
            } else {
                return -scroll.getVvalue() * (contentHeight - viewportHeight);
            }
        }, scroll.viewportBoundsProperty(), scroll.vvalueProperty(), content.heightProperty()));
    
        HBox root = new HBox(scaleContainer, scroll);
        root.setPadding(new Insets(10));
    
        Scene scene = new Scene(root, 400, 400);
    
        primaryStage.setScene(scene);
        primaryStage.show();
    }