Reputation: 9989
I am encountering an issue with SplitPane dividers if the content has multiline FlowPane. There is no issue if the FlowPane rendered in one row. If the FlowPane has more than one row then there is a shift in the content part.
The more the no of rows, the greater the shift is.
To demonstrate the issue, below is quick a demo. The demo contains three vertical splitPanes, where each SplitPane has FlowPane with different no. of rows. (1st splitPane - 1row, 2nd SplitPane - 2rows, 3rd SplitPane - 3rows)
When resizing the splitPane with 1 FlowPane row, there is no issue, everything works fine. Whereas if I resize the second splitPane, the content is shifting from its desired place leaving a void space in SplitPane. When resizing the third splitPane, the space is even much bigger.
I believe this should be some issue in SplitPane-FlowPane calculations (Or I might be wrong as well). But at this stage rather than trying to figure the root cause (which will be somewhere inside JavaFX source code), I am more desperate in fixing this with some work around.
I tried few ways by binding the heights, setting some Region constants, etc. But none worked. All the height calculations of FlowPane are indeed correct.
Do any of you have any suggestions on how I can fix this.
Note: The issue can be reproduced in all versions of JavaFX
import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.geometry.Orientation;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.ScrollPane;
import javafx.scene.control.SplitPane;
import javafx.scene.control.ToolBar;
import javafx.scene.layout.FlowPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
@SuppressWarnings("javadoc")
public class SplitPaneDividerIssueDemo extends Application {
/**
* FlowPane for debugging purpose.
*/
class SimpleFlowPane extends FlowPane {
@Override
protected double computeMaxHeight(final double width) {
final double height = super.computeMaxHeight(width);
// Debugging the first FlowPane in each SplitPane
if (isFirst()) {
System.out.println("Computed max height for " + getId() + " :: " + height);
}
return height;
}
@Override
protected double computeMinHeight(final double width) {
final double height = super.computeMinHeight(width);
if (isFirst()) {
System.out.println("Computed min height for " + getId() + " :: " + height);
}
return height;
}
@Override
protected double computePrefHeight(final double width) {
final double height = super.computePrefHeight(width);
if (isFirst()) {
System.out.println("Computed pref height for " + getId() + " :: " + height);
}
return height;
}
private boolean isFirst() {
return getId().endsWith("-1");
}
}
private int splitId = 1;
private int flowId = 1;
public static void main(final String... a) {
Application.launch(a);
}
@Override
public void start(final Stage primaryStage) throws Exception {
final HBox root = new HBox(buildSplitPane(10), buildSplitPane(20), buildSplitPane(30));
root.setSpacing(10);
final Scene scene = new Scene(root, 1250, 700);
primaryStage.setScene(scene);
primaryStage.setTitle("SplitPane Divider Issue");
primaryStage.show();
}
private VBox buildContent(final int count) {
final Button button = new Button("Button");
final FlowPane flowPane = new SimpleFlowPane();
flowPane.setId("flow-" + splitId + "-" + flowId);
flowPane.setVgap(5);
flowPane.setHgap(5);
for (int i = 0; i < count; i++) {
flowPane.getChildren().add(new Button("" + i));
}
final ScrollPane scroll = new ScrollPane();
VBox.setVgrow(scroll, Priority.ALWAYS);
final ToolBar toolBar = new ToolBar();
toolBar.getItems().add(new Button("Test"));
final VBox pane = new VBox();
pane.setPadding(new Insets(5));
pane.setSpacing(5);
pane.setStyle("-fx-background-color:yellow;-fx-border-width:1px;-fx-border-color:red;");
pane.getChildren().addAll(button, flowPane, scroll, toolBar);
pane.parentProperty().addListener((obs,old,content)->{
if(content!=null){
content.layoutYProperty().addListener((obs1,old1,layoutY)->{
System.out.println("LayoutY of content having "+flowPane.getId()+" :: "+layoutY);
});
}
});
flowId++;
return pane;
}
private SplitPane buildSplitPane(final int count) {
final SplitPane splitPane = new SplitPane();
splitPane.setStyle("-fx-background-color:green;");
splitPane.setOrientation(Orientation.VERTICAL);
splitPane.setDividerPositions(.36, .70);
splitPane.getItems().addAll(buildContent(count), buildContent(count), buildContent(count));
HBox.setHgrow(splitPane, Priority.ALWAYS);
splitId++;
flowId = 1;
return splitPane;
}
}
Upvotes: 5
Views: 131
Reputation: 378
The problem is within the minHeight of a FlowPane since it is oriented horizontally making that minHeight very dynamic. It appears to be designed where the minHeight is changed as it grows and shrinks in width. When you condense the parent vertically, the VBox calculates its minHeight as the "top/bottom insets plus the sum of each child's min height plus spacing between each child" according to the docs. Apparently there is some problem where a FlowPane's parent cannot account for its minHeight.
An HBox calculates its minHeight as the "top/bottom insets plus the largest of the children's min heights." So, if you wrap the FlowPane in an HBox, that HBox minHeight will be bound to the height of the FlowPane, and then place that HBox in the VBox where the FlowPane should be.
HBox flowPaneContainer = new HBox();
flowPaneContainer.getChildren().add(flowPane);
pane.getChildren().addAll(button, flowPaneContainer, scroll, toolBar);
EDIT: This is fine if your stage size is fixed. If your application is resizable, then more will have to be done because the flowPane minHeight will change, changing the HBox minHeight, and will then result in the same problem because there won't be enough room for everything inside every VBox.
With resizable apps, I normally handle this by wrapping each section of a SplitPane in a ScrollPane.
Upvotes: 0