I wrap some content into a ScrollPane because I want a horizontal scroll bar if the content does not fit on screen.
As long as the scroll bar is not needed, everything is fine:

Yet, when the scroll bar is shown, it (vertically) hides parts of the content:

How can I prevent this behavior? The content should always be shown completely. I tried to use fitToHeight="true", yet this did not help.
Following some example FXML (the multiple layers of HBox and VBox are added to mimic my real application's structure):
<BorderPane>
<top>
<ScrollPane vbarPolicy="NEVER" fitToHeight="true">
<HBox>
<VBox>
<TitledPane text="Title">
<HBox spacing="100.0">
<Text text="Test1 Test2 Test3 Test4"></Text>
<Text text="Test1 Test2 Test3 Test4"></Text>
<Text text="Test1 Test2 Test3 Test4"></Text>
<Text text="Test1 Test2 Test3 Test4"></Text>
<Text text="Test1 Test2 Test3 Test4"></Text>
<Text text="Test1 Test2 Test3 Test4"></Text>
<Text text="Test1 Test2 Test3 Test4"></Text>
<Text text="Test1 Test2 Test3 Test4"></Text>
<Text text="Test1 Test2 Test3 Test4"></Text>
<Text text="Test1 Test2 Test3 Test4"></Text>
<Text text="Test1 Test2 Test3 Test4"></Text>
</HBox>
</TitledPane>
</VBox>
</HBox>
</ScrollPane>
</top>
<center>
</center>
<bottom>
</bottom>
</BorderPane>
Looks like a bug (reported) in ScrollPaneSkin: its computePrefHeight method doesn't take the scrollBar's height into account if the policy is AS_NEEDED and the scrollBar is visible.
So the workaround is a custom skin that does ;) Note, that doing so isn't quite enough if the policy is changed from ALWAYS to AS_NEEDED (at the time of calling computeXX, the bar is visible - not quite sure why), so we are listening to changes in the policy and hide the bar .. rude but effective.
The custom skin (beware: not formally testet!) and a driver to play with:
public class ScrollPaneSizing extends Application{
public static class DebugScrollPaneSkin extends ScrollPaneSkin {
public DebugScrollPaneSkin(ScrollPane scroll) {
super(scroll);
registerChangeListener(scroll.hbarPolicyProperty(), p -> {
// rude .. but visibility is updated in layout anyway
getHorizontalScrollBar().setVisible(false);
});
}
@Override
protected double computePrefHeight(double x, double topInset,
double rightInset, double bottomInset, double leftInset) {
double computed = super.computePrefHeight(x, topInset, rightInset, bottomInset, leftInset);
if (getSkinnable().getHbarPolicy() == ScrollBarPolicy.AS_NEEDED && getHorizontalScrollBar().isVisible()) {
// this is fine when horizontal bar is shown/hidden due to resizing
// not quite okay while toggling the policy
// the actual visibilty is updated in layoutChildren?
computed += getHorizontalScrollBar().prefHeight(-1);
}
return computed;
}
}
private Parent createContent() {
HBox inner = new HBox(new Text("somehing horizontal and again again ........"));
TitledPane titled = new TitledPane("my title", inner);
ScrollPane scroll = new ScrollPane(titled) {
@Override
protected Skin<?> createDefaultSkin() {
return new DebugScrollPaneSkin(this);
}
};
scroll.setVbarPolicy(NEVER);
scroll.setHbarPolicy(ALWAYS);
// scroll.setFitToHeight(true);
Button policy = new Button("toggle HBarPolicy");
policy.setOnAction(e -> {
ScrollBarPolicy p = scroll.getHbarPolicy();
scroll.setHbarPolicy(p == ALWAYS ? AS_NEEDED : ALWAYS);
});
HBox buttons = new HBox(10, policy);
BorderPane content = new BorderPane();
content.setTop(scroll);
content.setBottom(buttons);
return content;
}
@Override
public void start(Stage stage) throws Exception {
stage.setScene(new Scene(createContent(), 400, 200));
stage.setTitle(FXUtils.version());
stage.show();
}
public static void main(String[] args) {
launch(args);
}
@SuppressWarnings("unused")
private static final Logger LOG = Logger
.getLogger(ScrollPaneSizing.class.getName());
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With