torutkのブログ

ソフトウェア・エンジニアのブログ

JavaFXアプリケーションでユーザーが編集可能なCSSファイルを置くには

問題

JavaFXアプリケーションを作成する際に、画面のレイアウトをFXMLで記述、色やフォントなどの見栄えをCSSファイルに記述し、FXMLのルートノードにCSSファイルを指定しました。

模式的なソースファイル構成は次のようになります。

myapp
  +-- src
        +-- mypackage
              +-- MyApp.java
              +-- MyApp.fxml
              +-- MyApp.css

MyApp.javaの中でFXMLファイルを読み込みます。

Parent root = FXMLLoader.load(getClass().getResource("MyApp.fxml"));
Scene scene = new Scene(root);

MyApp.fxml の中でCSSファイルを指定します。

<HBox stylesheets="@MyApp.css" xmlns=...>

このアプリケーションをビルドすると、CSSファイルは他のクラスファイル等と一緒にJARファイルまたはjlinkでモジュールファイルに含まれます。

このアプリケーションを実行するマシンに配布したあとに、画面の見栄えをカスタマイズしようとCSSファイルを取り出して編集、再組み込みすることは簡単ではありません。
JARファイルならzipアーカイブツールでCSSファイルを取り出し編集後再度JARファイルに戻すことは可能です(かなり面倒です)。
jlinkのモジュールファイルの場合、zipではないので取り出し編集は困難です。

そこで、ビルド時にJARファイルまたはモジュールファイルに組み込まれるデフォルトの設定を記述したCSSファイルとは別に、独立したCSSファイルをアプリケーション実行時に読み込み反映する方法を探ってみました。

sceneに別CSSファイルを設定するも反映されず

カレントディレクトリにMyCustom.cssファイルが存在すればそれをSceneのスタイルシートとして設定するコードを追加しました。

Path path = Paths.get("MyCustom.css");
if (Files.exists(path)) {
    scene.getStylesheets().add(path.toUri().toString());
}

しかし、これではMyCustom.cssに記述したカスタム設定が反映されませんでした。

このコードでは、SceneとそのrootノードのHBoxにそれぞれ別のスタイルシートが設定されます。

Scene         ---> MyCustom.css
  +-- HBox    ---> MyApp.css

試行錯誤と資料調査をしたところ、JavaFXのドキュメント「JavaFX CSS Reference Guide」に次の記述を見つけました。

Style sheets from a Parent instance are considered to be more specific than those styles from Scene style sheets.

つまり、Sceneのスタイルシートよりもその子ノード(rootノード)のスタイルシートの方が優先されてしまいます

sceneのrootノードにスタイルシートを設定する

そこで、Sceneのrootノードに対してスタイルシートを設定するようにしました。

Path path = Paths.get("MyCustom.css");
if (Files.exists(path)) {
    scene.getRoot().getStylesheets().add(path.toUri().toString());
}

これで、アプリケーションを実行するときのカレントディレクトリにMyCustom.cssファイルが存在すれば、その内容が反映されるという仕組みができました。