No Bugs, No Life

読んだ本や、プログラミング、システム開発等のねたを中心に。文章を書く練習なので少し硬派に書くつもりだけど、どうなることやら。

JavaFX レイアウトの適用と画像付きButton

MementoWeaver開発記
メインとなるロジックも大分実装できてきたので、気分を変えてJavaFXのscene周りを少しお色直ししてみる。
具体的にはMainMenu画面へのレイアウトの適用と、Imageを表示したButtonの実装を行う。
SceneBuilderだけでは実現できなかったところもあるのでメモ。(2013/03/26訂正)

使用前 vs 使用後

結論からいうと使用前と使用後はこんな感じ。ボタンの数が増えてしまっているのは不問(SUC見直しで増えた)。

使用前

f:id:kazyury:20130325221915p:plain

使用後

f:id:kazyury:20130325224724p:plain
うん、自画自賛だがだいぶそれっぽくなった。
ちなみにアイコンはVeryIcon.com(Very Icon, Free Icons, PNG ICO Icons,Vista Icons Search AND Download)様よりダウンロードしました。
アイコン作者様の情報は以下のとおりです。フリーの素敵なアイコンを公開いただきありがとうございます*1
Icon Author: Everaldo / Yellowicon
HomePage: http://www.everaldo.com
License: Free for non-commercial use.

レイアウトの適用

使用前の問題点

大きくは以下3点を改善することを目標とした。

  • AnchorPaneに直接ボタンを配置しただけで特にレイアウトの指定は行っていなかったので、画面をリサイズするとすごく残念なことになっていた
  • MWを構成する画面全体的に、画面下部にExitボタンを配置しているが、ボタンの配置場所などが微妙にずれていて気持ち悪かった。
  • ボタンしか配置しない画面であるのに、ボタンが小さい。また、メニュー画面のくせにボタンの文字列(Text)によりボタンのサイズが微妙に異なるのが気持ち悪い。

レイアウト適用の方針

レイアウトは以下のように適用した。

  1. シーン全体をVBoxで上下に2分割
  2. VBoxの上半分には、配置するボタン数に応じたGridPaneを配置*2
  3. VBoxの下半分には、HBoxを配置
  4. HBoxの大きさは、Pref Height=30を設定
  5. GridPaneはVGrowをALWAYSに設定。加えて、Max Width/Max HeightともにMAX_VALUEを設定
  6. GridPaneの各グリッドにButtonを配置。各ButtonのMax Width/Max HeightともにMAX_VALUEを設定
  7. HBoxにButtonを配置。HBoxのAlignmentをCENTER_RIGHTに設定。

適用した結果のシーングラフは、SceneBuilder上では以下のようになる。
f:id:kazyury:20130325225128p:plain
その他、GridPaneのHgap/Vgap,Padding、HBoxのspacing, Padding辺りをちょいちょいと変更して完成。

感想

今までGUI(VisualuRuby,Ruby-Gnome2,Swing)を書くときに、真面目にレイアウトマネージャを使ったことが無かったが、JavaFXのように配置するUI部品がツリー構造で表現されていてそれをサポートするGUIビルダがあると、レイアウトなんかの設定も簡単にできると得心。
翻って、_whyのSHOES(Shoes! The easiest little GUI toolkit, for Ruby.)なんかは(向こうはブロックのネストかもしれないが)先見の明だったのかと感心*3*4

ボタンへのImage描画

コードからの画像ボタン生成

JavaからButtonを生成するのならば、JavaFX Ensembleのサンプルコード(GraphicButton)のように行ける。

    private static final Image ICON_48 = new Image(GraphicButton.class.getResourceAsStream("icon-48x48.png"));
    private void init(Stage primaryStage) {
        Group root = new Group();
        primaryStage.setResizable(false);
        primaryStage.setScene(new Scene(root, 400,100));
        ImageView imageView = new ImageView(ICON_48);
        Button button = new Button("button", imageView);
        button.setContentDisplay(ContentDisplay.LEFT);
        root.getChildren().add(button);
    }

SceneBuilderを用いたButtonへのImageView埋め込み

(2013/03/26訂正のため追記)
昨日のエントリでは大嘘をこいていたようなので、訂正。
結論からいうとSceneBuilderからButtonにImageViewを埋め込むことが可能だった。
以下、大まかな手順

Buttonを配置する

f:id:kazyury:20130327000304p:plain
まぁ適当に。
左下の階層ペインにButtonが追加される。

Buttonの子としてImageViewを追加する

左上のライブラリペインでImageViewを選択して、階層ペインのButtonにImageViewをドラッグする。
f:id:kazyury:20130327000509p:plain

ImageViewのFit Width/Fit Height、imageパスを指定する

Fit Width/Fit Heightはお好みで。レイアウトタブ(右ペイン)で指定できる。
imageパスはImageViewのプロパティタブ(右ペイン)で指定できる。
f:id:kazyury:20130327000744p:plain

FXML(≠SceneBuilder)を用いた画像ボタン生成

(2013/03/26訂正)今回はSceneBuilderで生成したFXMLを用いる方式としているので、出来ればnew Buttonとかやりたくない。
が、SceneBuilderからは表示する画像を指定する箇所が見つけられない。
Google先生に聞いてみてもSceneBuilderではサポートしていないとのこと。

仕方が無いからFXMLを編集して、Buttonの子孫としてImageを定義することにする。

<?import javafx.scene.image.*?>
 :(略)
<Button fx:id="manageTag" (略)>
  <graphic>
    <ImageView>
      <image>
        <Image url="@icons/manage_tag.png"/>
      </image>
    </ImageView>
  </graphic>
</Button>

なお、最初のimport文を追加せずにいて、画像が表示されないと悩んでいたことは内緒。
また、Imageのurlの指定の仕方で若干はまったが、@がカレントディレクトリを指し示しているという記述が、Oracleの文書に記載されていた。

The location resolution operator (represented by an "@" prefix to the attribute value) is used to specify that an attribute value should be treated as a location relative to the current file rather than a simple string.

Introduction to FXML | JavaFX 2.2

「ロケーション解決オペレータ("@"プレフィックスで表現される属性値)は、属性値がカレントファイルの相対ロケーションとして扱われるべきことを示すために使用されます」(訳 id:kazyury 誤訳あったらゴメンナサイ)
今回のケースであれば、MainMenu.fxmlのパスはnobugs/nolife/mw/ui/fxmlなので、nobugs/nolife/mw/ui/fxml/icons/manage_tag.png を配置しておけば上記の指定でOK

*1:日本語で書いても無駄だろうけど。

*2:今回はたまたまデフォルトの2x3

*3:実際にSHOESを使ったことが無いのだけど、簡単なUIだったらいいかもね。いつか試すかもしれないような気がすると思わなくもない。

*4:今見たら_whyはもう噛んでいないのかも。