javajavafxsplitpane

JavaFX splitpane - Prevent unwanted divider position changes


I'm trying to find a way to prevent divider positions from changing when one of the dividers is constrained by a maxWidth property.

In the example I'm clicking a button to minimise a panel, and want the central pane to resize to accommodate the extra space available, but the extra space is instead shared between the two other panels.

App at run

After clicking left button, right divider has moved

    @Override
public void start(Stage stage) {
    BooleanProperty clicked1 = new SimpleBooleanProperty(false);
    BooleanProperty clicked2 = new SimpleBooleanProperty(false);

    Button button1 = new Button("Click");
    button1.setOnAction(event -> clicked1.set(!clicked1.get()));
    button1.setMaxWidth(800);

    Button button2 = new Button("Click");
    button2.setOnAction(event -> clicked2.set(!clicked2.get()));
    button2.setMaxWidth(800);

    StackPane pane1  = new StackPane(button1);
    StackPane pane2 = new StackPane();
    StackPane pane3  = new StackPane(button2);

    pane1.maxWidthProperty().bind(Bindings.createDoubleBinding(() -> clicked1.get() ? 40.0 : Double.MAX_VALUE, clicked1));
    pane3.maxWidthProperty().bind(Bindings.createDoubleBinding(() -> clicked2.get() ? 40.0 : Double.MAX_VALUE, clicked2));

    SplitPane splitPane = new SplitPane(pane1, pane2, pane3);
    splitPane.setPrefSize(600, 400);
    SplitPane.setResizableWithParent(pane1, false);
    SplitPane.setResizableWithParent(pane3, false);
    splitPane.setDividerPositions(0.25, 0.75);

    stage.setScene(new Scene(splitPane));
    stage.show();
}

Solution

  • The solution for your issue is to compute and set the divider positions based on modified pane widths.

    Please find below the code for this logic. Included inline comments about what to do.

    import javafx.application.Application;
    import javafx.beans.property.BooleanProperty;
    import javafx.beans.property.SimpleBooleanProperty;
    import javafx.scene.Scene;
    import javafx.scene.control.Button;
    import javafx.scene.control.SplitPane;
    import javafx.scene.layout.StackPane;
    import javafx.stage.Stage;
    
    public class SplitPaneIssue extends Application {
    
        private double COMPRESSED_WIDTH = 40.0;
        private double D1_DEFAULT_POSITION = 0.25;
        private double D2_DEFAULT_POSITION = 0.75;
    
        @Override
        public void start(Stage stage) {
            BooleanProperty clicked1 = new SimpleBooleanProperty();
            BooleanProperty clicked2 = new SimpleBooleanProperty();
    
            Button button1 = new Button("Click");
            button1.setOnAction(event -> clicked1.set(!clicked1.get()));
            button1.setMaxWidth(Double.MAX_VALUE);
    
            Button button2 = new Button("Click");
            button2.setOnAction(event -> clicked2.set(!clicked2.get()));
            button2.setMaxWidth(Double.MAX_VALUE);
    
            StackPane pane1 = new StackPane(button1);
            StackPane pane2 = new StackPane();
            StackPane pane3 = new StackPane(button2);
    
            SplitPane splitPane = new SplitPane(pane1, pane2, pane3);
            splitPane.setPrefSize(600, 400);
            SplitPane.setResizableWithParent(pane1, false);
            SplitPane.setResizableWithParent(pane3, false);
            splitPane.setDividerPositions(D1_DEFAULT_POSITION, D2_DEFAULT_POSITION);
    
            clicked1.addListener((obs, old, val) -> {
                // First compute the total width of all panes and the percentage value per pixel
                double percentPerPx = 1 / (pane1.getWidth() + pane2.getWidth() + pane3.getWidth());
                // Set the max width based on the toggle value
                pane1.setMaxWidth(val ? COMPRESSED_WIDTH : Double.MAX_VALUE);
    
                // Now compute the divider positions with the new width.
                double d1 = val ? COMPRESSED_WIDTH * percentPerPx : D1_DEFAULT_POSITION;
                // Computing the width percentage for the pane3 and subtracting from 1 will retain its position.
                double d2 = 1 - (pane3.getWidth() * percentPerPx);
                splitPane.setDividerPositions(round(d1), round(d2));
            });
            clicked2.addListener((obs, old, val) -> {
                double percentPerPx = 1 / (pane1.getWidth() + pane2.getWidth() + pane3.getWidth());
                pane3.setMaxWidth(val ? COMPRESSED_WIDTH : Double.MAX_VALUE);
                double d1 = pane1.getWidth() * percentPerPx;
                double d2 = val ? 1 - (COMPRESSED_WIDTH * percentPerPx) : D2_DEFAULT_POSITION;
                splitPane.setDividerPositions(round(d1), round(d2));
            });
            stage.setScene(new Scene(splitPane));
            stage.show();
        }
    
        private double round(double value) {
            long factor = (long) Math.pow(10, 2);
            value = value * factor;
            long tmp = Math.round(value);
            return (double) tmp / factor;
        }
    }