JavaFX ScrollPane的水平滚动条隐藏了内容。

3
我将一些内容放入ScrollPane中,因为如果内容不能适应屏幕,我希望有一个水平滚动条。
只要不需要滚动条,一切都很好:
但是,当滚动条显示时,它(垂直地)隐藏了部分内容:
如何防止这种行为?内容应始终完全显示。我尝试使用fitToHeight="true",但没有帮助。
以下是一些FXML示例(添加了多个HBox和VBox层,以模仿我的真实应用程序结构):
<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>

你可能正在创建一些不必要的容器,这可能会导致问题。 - SedJ601
@Sedrick 如上所述,在我的实际应用程序中,这些容器是为了正确的可视化而需要的,并且包含的控件比上面给出的示例更多。我只是将它们包含在内,以防它们可能与解决方案有直接联系。 - Markus Weninger
我仍然猜测您正在错误地使用容器。请发布您的原始 FXML - SedJ601
@Sedrick 谢谢你的帮助,但我的原始FXML由多个自定义控件组成,它们本身继承自彼此或JavaFX控件。我认为通过我的控件代码将其膨胀不会增加问题的可读性。 - Markus Weninger
1
@Sedrick 我不这么认为,看起来像是ScrollPaneSkin布局中的一个bug。 - kleopatra
3个回答

4

看起来ScrollPaneSkin中有一个bug(已报告):如果设置的策略是AS_NEEDED并且滚动条可见,它的computePrefHeight方法不会考虑到滚动条的高度。

因此,解决方法是使用自定义皮肤;) 请注意,如果将策略从ALWAYS更改为AS_NEEDED(在调用computeXX的时候,滚动条是可见的——不太确定为什么),这样做还不够。因此,我们正在监听策略的变化并隐藏滚动条.. 粗鲁但有效。

自定义皮肤(请注意:未经正式测试!)和一个可供使用的驱动程序:

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());

}

1
太好了,谢谢!总是很高兴看到你在JavaFX帖子上的回答,它们非常有帮助 :) - Markus Weninger
我只是想告诉你,当我水平调整窗口大小(即拖动应用程序的右边框以增加/减少窗口大小)时,computePrefHeight似乎没有被调用,因此它在我的端上并没有真正起作用。 - Markus Weninger
嗯...会检查一下...你的fx版本是什么? - kleopatra
哦,那就是原因了,我们在Ubuntu上同时使用openjdk-8和openjfx。 - Markus Weninger
我不明白在使用openJDK 8时getHorizontalScrollBar()方法是从哪里来的。编辑:自版本9以来似乎可用。 - lolung
非常漂亮。我也为垂直滚动条添加了相同的功能,现在它的行为就像我们预期的那样。谢谢,真是太棒了!☺ - Timur Baysal

1

因为被接受的答案只适用于9及以上版本,所以我写了一个适用于openJDK 8的版本。

public class ScrollPaneHSkin extends ScrollPane
{

  ScrollBar hbar;

  public ScrollPaneHSkin()
  {
    super();
  }

  public ScrollPaneHSkin(Node content)
  {
    super(content);
  }

  @Override
  protected Skin<?> createDefaultSkin()
  {
    return new HSkin();
  }


  private class HSkin extends ScrollPaneSkin
  {
    HSkin()
    {
      super(ScrollPaneHSkin.this);
      hbarPolicyProperty().addListener((ov, old, current) ->
      
        // rude .. but visibility is updated in layout anyway
        hsb.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 && hsb.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 += hsb.prefHeight(-1);
      }
      return computed;
    }
  }
}

1
你可以通过设置 vbox 的 minHeight 来解决这个问题,使其显示完整的文本;或者,你也可以添加填充来达到同样的效果。

ex.(Padding)

<?xml version="1.0" encoding="UTF-8"?>

<?import java.lang.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<?import javafx.scene.control.ColorPicker?>
<?import javafx.scene.layout.VBox?>

<?import javafx.scene.text.Text?>
<?import javafx.geometry.Insets?>
<BorderPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
    <top>
        <ScrollPane vbarPolicy="NEVER" fitToHeight="true">
            <HBox>
                <VBox spacing="100.0">
                    <TitledPane text="Title">
                        <HBox>
                            <children>
                                <Text text="Test1 Test2 Test3 Test4" />
                                <Text text="Test1 Test2 Test3 Test4" />
                                <Text text="Test1 Test2 Test3 Test4" />
                                <Text text="Test1 Test2 Test3 Test4" />
                                <Text text="Test1 Test2 Test3 Test4" />
                                <Text text="Test1 Test2 Test3 Test4" />
                                <Text text="Test1 Test2 Test3 Test4" />
                                <Text text="Test1 Test2 Test3 Test4" />
                                <Text text="Test1 Test2 Test3 Test4" />
                                <Text text="Test1 Test2 Test3 Test4" />
                                <Text text="Test1 Test2 Test3 Test4" />
                            </children>
                        </HBox>
                    </TitledPane>
                    <padding>
                        <Insets bottom="5.0" top="5.0" />
                    </padding>
                </VBox>
            </HBox>
        </ScrollPane>
    </top>
    <center>

    </center>
    <bottom>

    </bottom>
</BorderPane>

(min Height) - 最小高度
<?xml version="1.0" encoding="UTF-8"?>

<?import javafx.scene.control.ScrollPane?>
<?import javafx.scene.control.TitledPane?>
<?import javafx.scene.layout.BorderPane?>
<?import javafx.scene.layout.HBox?>
<?import javafx.scene.layout.VBox?>
<?import javafx.scene.text.*?>
<BorderPane xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1">
    <top>
        <ScrollPane vbarPolicy="NEVER" fitToHeight="true" minHeight="83.0">
            <HBox>
                <VBox>
                    <TitledPane text="Title">
                        <HBox>
                            <Text text="Test1 Test2 Test3 Test4"/>
                            <Text text="Test1 Test2 Test3 Test4"/>
                            <Text text="Test1 Test2 Test3 Test4"/>
                            <Text text="Test1 Test2 Test3 Test4"/>
                            <Text text="Test1 Test2 Test3 Test4"/>
                            <Text text="Test1 Test2 Test3 Test4"/>
                            <Text text="Test1 Test2 Test3 Test4"/>
                            <Text text="Test1 Test2 Test3 Test4"/>
                            <Text text="Test1 Test2 Test3 Test4"/>
                            <Text text="Test1 Test2 Test3 Test4"/>
                            <Text text="Test1 Test2 Test3 Test4"/>
                        </HBox>
                    </TitledPane>
                </VBox>
            </HBox>
        </ScrollPane>
    </top>
    <center>

    </center>
    <bottom>

    </bottom>
</BorderPane>

谢谢你的回答...但是...这看起来太hacky了... :P 我无法想象JavaFX开发人员没有想出更优雅的解决方案。 - Markus Weninger
@kleopatra 我知道,我知道。 :) 但是你的解决方案看起来很棒,虽然仍然是一种变通方法,但比较少用“hacky” / “非官方”的方法 :D - Markus Weninger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接