JavaFX 画面サイズが一定以下の場合はスクロールバーを出し、一定以上の場合は中のnodeをなるべく最大化する


今回のゴール

JavaFxをしようしてGUIアプリを作成する際にListViewを使用しましたが、以下のような要望がありました。

  1. ListViewはなるべく大きく表示したい
    • → windowの大きさに連動して大きさが自動的に変化する
  2. windowサイズがある一定を下回った場合は、ListViewの大きさ変更をやめ、かつ、windowのスクロール表示にしたい。

そんな要望を満たすのに、それなりに苦労したので結果のメモ。

最終的に目指した動きはこんな感じ。
青枠部分がListViewで、ボタンのある位置よりも画面サイズが小さくなった場合にスクロールバーが登場し、横に部品があることを示す、といった具合。

環境

java1.8
JavaFX Scene Builder 2.0

まずは結果から

最終的な部品の構成は以下の通りで実現できた。

  • ScrollPane
    • AnchorPane
      • TextField
      • Button
      • ListView

各部品の設定要点

ScrollPane

  • Properties - Hbar/Vbar PolicyAS_NEEDEDとする。
  • Layout - Fit To Width/Heightをtrue()とする。

AnchorPane

  • Layout - Min Width/Heightを、スクロールバーの表示を開始させたいwindowサイズに設定する。

ListView

  • Layout - Anchor Pane Constrainsの4辺について全て固定値として設定する。

実現までの道のりステップバイステップ

Windowサイズに連動するListViewの設定

以下の組み合わせにより、「Windowサイズに連動してListViewの大きさが変化する」を実現できる。

AnchorPaneとListView

使用するのはAnchorPane
AnchorPaneとは

AnchorPane allows the edges of child nodes to be anchored to an offset from the anchor pane's edges.

中に含まれたnodeについて、親であるAnchorPaneの端からの距離を固定することができる、とのこと。

SceneBuliderでAnchorPaneの子要素にControlを追加すると、LayoutにAnchor Pane Constraintsが表示される。

この4つのテキストボックスに入力した値(距離)で個要素の端とAnchorPaneの端とが固定される。
下の値のみ固定したのがこれ。

一辺のみを固定すると、コントロールの大きさは変更されず、表示位置が変更される。

上下を固定したのがこれ。

上下を固定すると、コントロールの大きさよりも上下からの距離が優先され、コントロールの大きさが伸び縮みするのがわかる。

今回、ListViewの大きさをなるべく大きく表示させるためには、AnchorPaneに内包させるListViewについて、Anchor Pane Constraintsの4つの辺全てについて、固定値とすれば良いことがわかった。

ScrollPane内のAnchorPane

ScrollPaneにはFitTo[Width|Height]Propertyがある。
SceneBuilderではこのプロパティを
LayoutFit To WidthFit To Heightで設定する。

このFit To Width/Heightの意味をドキュメントで確認すると

public final BooleanProperty fitToWidthProperty

If true and if the contained node is a Resizable, then the node will be kept resized to match the width of the ScrollPane's viewport. If the contained node is not a Resizable, this value is ignored.

(適当な訳)

trueのとき、中に含まれるnodeがRisizableであれば、nodeはScrollPaneのviewportの幅に連動して大きさが変化する。含まれるnodeがResizableでなければこの値は無視される。

ということで、ScrollPaneの中にAnchorPaneを入れ、Fit To Width/Heighttrueにすることで、ScrollPane(今回の場合はWindowサイズ)の大きさを変化に連動してAnchorPaneの大きさが変化するようになった。
(AnchorPaneは、というか、JavaFXのコントロールは全てResizeable)

Windowの大きさが一定以下だった場合の措置

次は「一定の幅よりもWindowの大きさが小さくなった場合は、スクロールバーを表示する」。

ScrollPaneのFitTo[Width|Height]Propertytrueとした場合、内部のnodeの大きさはScrollPaneの大きさに連動して変化するが、内部のnodeのMin [Width|Height]Max [Width|Height]を超えては大きさは変化しない様子。

そこで、ScrollPaneの中のAnchorPaneのMin Widthに、Buttonが隠れない大きさを設定すると、、、できた。

結果として出来上がったfxml

目指す姿.fxml
<?xml version="1.0" encoding="UTF-8"?>

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

<ScrollPane fitToHeight="true" fitToWidth="true" maxHeight="-Infinity" maxWidth="-Infinity" minHeight="-Infinity" minWidth="-Infinity" prefHeight="400.0" prefWidth="600.0" xmlns:fx="http://javafx.com/fxml/1" xmlns="http://javafx.com/javafx/8">
  <content>
    <AnchorPane minHeight="0.0" minWidth="0.0" prefHeight="343.0" prefWidth="486.0">
         <children>
            <TextField layoutX="14.0" layoutY="7.0" prefHeight="27.0" prefWidth="247.0" />
            <ListView layoutX="-4.0" layoutY="41.0" prefHeight="357.0" prefWidth="598.0" AnchorPane.bottomAnchor="0.0" AnchorPane.leftAnchor="0.0" AnchorPane.rightAnchor="0.0" AnchorPane.topAnchor="40.0" />
            <Button layoutX="272.0" layoutY="7.0" mnemonicParsing="false" text="Button" />
         </children></AnchorPane>
  </content>
</ScrollPane>