FX - ImageView の大きさを他のノードと連動させる


ウィンドウサイズを変更したときに、 ImageView の表示も大きくしたり小さくしたりしたい、そんな願望を叶えようと四苦八苦したときのメモ。
色々試行錯誤した結果なので、正しさは保障できない。

Stage と連動させた場合

実装

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

<?import javafx.scene.image.ImageView?>
<?import javafx.scene.layout.BorderPane?>

<BorderPane fx:controller="sample.javafx.MainController" xmlns="http://javafx.com/javafx/8.0.121" xmlns:fx="http://javafx.com/fxml/1">
   <center>
      <ImageView fx:id="imageView" fitHeight="150.0" fitWidth="200.0" pickOnBounds="true" preserveRatio="true" BorderPane.alignment="CENTER" />
   </center>
</BorderPane>

MainController.java
package sample.javafx;

import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.stage.Stage;

import java.net.URL;
import java.nio.file.Paths;
import java.util.ResourceBundle;

public class MainController implements Initializable {
    @FXML
    private ImageView imageView;

    public void initStage(Stage stage) {
        stage.setWidth(300);
        stage.setHeight(200);

        imageView.fitWidthProperty().bind(stage.widthProperty());
        imageView.fitHeightProperty().bind(stage.heightProperty());
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        Image image = new Image(Paths.get("./image/shirakawago.jpg").toUri().toString());
        imageView.setImage(image);
    }
}
Main.java
package sample.javafx;

import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;

public class Main extends Application {
    public static void main(String[] args) {
        Application.launch(args);
    }

    @Override
    public void start(Stage primaryStage) throws Exception {
        FXMLLoader loader = new FXMLLoader(this.getClass().getResource("/main.fxml"));

        Parent root = loader.load();

        MainController controller = loader.getController();
        controller.initStage(primaryStage);

        Scene scene = new Scene(root);
        primaryStage.setScene(scene);
        primaryStage.show();
    }
}

実行結果

説明

  • ImageViewfitWidth プロパティと fitHeight プロパティを、 Stagewidth プロパティと height プロパティにバインドさせる
  • 注意なのが、 Stagewidthheight にあらかじめ値を設定しておく必要があるという点
    • 何も設定していないと、 Stagewidthheight は初期値が NaN になる
    • その width, height プロパティに ImageViewfitWidth, fitHeight プロパティをバインドすると、どうやら設定している Image のオリジナルサイズで初期化されてしまうっぽい
  • ただ、よく見てみるとウィンドウサイズを小さくしたときに画像の下部分が隠れてしまっている(右下の女性の姿を見ると分かりやすい)

height | Window (JavaFX 8)

この値には、オペレーティング・システムで追加できる、タイトル・バーなどのすべての装飾が含まれます

  • ということなので、おそらくタイトルバーの高さだけ画像が隠れてしまっているっぽい

シーングラフのルートに合わせる

実装

MainController.java
package sample.javafx;

import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.image.Image;
import javafx.scene.image.ImageView;
import javafx.scene.layout.BorderPane;
import javafx.stage.Stage;

import java.net.URL;
import java.nio.file.Paths;
import java.util.ResourceBundle;

public class MainController implements Initializable {
    @FXML
    private BorderPane borderPane;
    @FXML
    private ImageView imageView;

    public void initStage(Stage stage) {
        stage.setWidth(300);
        stage.setHeight(200);
    }

    @Override
    public void initialize(URL location, ResourceBundle resources) {
        Image image = new Image(Paths.get("./image/shirakawago.jpg").toUri().toString());
        imageView.setImage(image);

        imageView.fitWidthProperty().bind(borderPane.widthProperty());
        imageView.fitHeightProperty().bind(borderPane.heightProperty());
    }
}

実行結果

説明

  • シーングラフのルートである BorderPanewidth, height プロパティと ImageViewfitWidth, fitHeight プロパティをバインドしておく
  • こちらも Stagewidth, height に初期値を設定しておかないと、 Image のオリジナルサイズになってしまうっぽい
  • ウィンドウサイズを小さくしても画像の下が隠れることもなく、うまくいっているっぽい

ルート以外のノードと連動させる

他のノードが入ってきた場合

上述の方法は、あくまでシーングラフ内に他のノードが一切ない状態なので、いわゆるイメージビューアみたいなものしか実現できない。
試しに、シーングラフ内に他のノードを追加してみる。

実装

実行結果

説明

  • ラベルのサイズだけ領域が使用されるため、ルートノードのサイズと ImageView のサイズを連動させると、画像を表示する範囲が足りなくなる

とりあえず画像表示領域用にノードを追加してみる

実装

  • ImageView を囲むように新しくノード(BorderPane)を追加
    • width, height 関係はとりあえず全てデフォルトの USE_COMPUTED_SIZE にしている
  • ImageView は、この新しく追加した BorderPanewidth, height プロパティと連動するように実装しておく

実行結果

説明

  • ちょっと分かりにくいが、画像がオリジナルサイズで表示されていて、左上の部分だけが見えている
  • なぜ BorderPane がルートノードのときはOKで、子供のノードになるとこういう動きになるのか、正直なところ自分も原理は理解できていない

非ルートノードに連動させる

実装

実行結果

説明

  • Min WidthMin Height を、それぞれ 0 に設定する
    • おそらく、デフォルトの USE_COMPUTED_SIZE にしていると、領域の最小サイズが子要素のサイズ(ここでは ImageView のサイズ)と一致してしまうので、最小サイズ = 画像のサイズになってしまい、1つ前のような動きになったのではないかと想像している(あくまで想像)
    • ただ、そうだとすると BorderPane がルートノードのときも条件は同じはずなので、そっちで同様の状態にならなかった理由はよくわからない
  • とりあえず、非ルートノードとサイズを連動させる場合は、 Min Width, Min HeightUSE_COMPUTED_SIZE, USE_PREF_SIZE 以外の何らかの値にしておけば、良い感じになるっぽい

まとめ

  • 共通
    • Stagewidth, height を設定しておく
    • ImageViewfitWidth, fitHeight プロパティを、連動させたいノードの width, height プロパティにバインドする
  • 連動させるノードがルートノードでない場合
    • ノードの Min Width, Min HeightUSE_COMPUTED_SIZE, USE_PREF_SIZSE 以外の何らかの値にしておく