読者です 読者をやめる 読者になる 読者になる

離婚しました

はい、標題の通りです。昨年結婚してからわずか1年とちょっとなのですが、つい先日結婚生活を終了することになりました。ブログに書くかどうか迷いましたが、昨年結婚報告を書いていたので、こちらでも報告することにしました。

色んな方面から多くのお祝いやお心遣いの言葉を頂いたにも関わらず、このような残念な結果になってしまい、誠に申し訳ありません。

理由についてはここでは語らないことにします。ただ、相当に悩んだ上での決断であり、決断に対しては後悔していません。

これからまた人生を再スタートすることになります。このような情けない結果になってしまいましたが、どうかこれからもよろしくお願いします。

JavaFX9に追加される機能が増えるかもしれません

このエントリは JavaFX Advent Calendar 2015 の 7 日目のエントリです。前日は id:bitter_fox / @ さんによる「 JavaFXを直接実行できるjshellを作った 」でした。 今年の Advent Calendar の自分の担当日はもう少し後だったのですが、空いていたので急遽ホットなネタで埋めることにしました。

先日、JavaFX コミュニティの間で、JavaFX を取り巻く現状について怒りをぶちまけた、以下のブログエントリが話題を呼んでいました。

Should Oracle Spring Clean JavaFX?
https://www.codenameone.com/blog/should-oracle-spring-clean-javafx.html

Java によるモバイルアプリケーション開発プラットフォームである Codename One の開発者である Shai Almog 氏 *1 によるエントリで、JavaFX の現状について、「Swing を置き換えるには到底至っていない」「Oracle 自体が JavaFX にコミットする姿勢を見せていない」とかなり厳しく批判しています。

このエントリは OpenJFX の ML でも話題になり、かなり激しい議論になりました。様々な意見が飛び交いましたが、やはり OracleJavaFX に対するコミットを疑問視する意見はかなり出てきました。
確かにここ最近の Oracle の行動には JavaFX から手を抜き始めているように見られてもおかしくないところが目に付きます。

Raspberry Pi 向け Oracle JDK での JavaFX サポートは停止し *2 、Scene Builder のバイナリ配布も停止してしまいました *3
Java9 と共にリリースされる JavaFX9 の新機能についても、大きな変更は Jigsaw 導入に対応して、これまで com.sun.javafx パッケージにあった API のうち、重要度の高いものを public するというものだけです。確かにこの対応は非常にリソースを割く作業であることは理解できますが、それにしても新しいコンポーネントの追加などが一切無いというのは寂しいものです。

ある程度議論が進んだところで、現在の JavaFX チームのリーダーである Kevin Rushforth 氏がコメントを挟んできました。

http://mail.openjdk.java.net/pipermail/openjfx-dev/2015-December/018320.html

JavaFX はOpenJFX というコミュニティで開発を進めており、意見はオープンに取り入れるつもりであること、決して数が多くは無いけど、JavaFX9 には中々興味深い改善をするつもりだよ、といった内容です。
まず、JavaOne でも発表したことのようですが、JavaFX9 の新機能として以下のようなものをリストアップしていました。

  • A modularized JavaFX (into 6 core modules + deploy, swing interop, swt interop)
  • JEP 253 -- Control Skins & additional CSS APIs (proper support for third-party controls)
  • High DPI enhancements (full support on Windows; add support for Linux)
  • Public API for commonly used methods from internal packages:
    • Nested Event Loop
    • Pulse Listener
    • Platform Startup
    • Text API (HitTest, etc)
    • Static utility functions (under investigation)
  • New versions of WebKit and GStreamer

4 番目はこれまで internal だった API のうち、便利そうなものを public にするというものですが、確かに地味に良さげなものが並んでしますね。Text API とか気になる。

さらに Java9 リリース延期の提案 を受けて、もう少し機能追加しても良いかもとのコメントをしています。候補として次のような機能を挙げています。

  • Provide a JavaFX equivalent for JEP 272 / AWT ‘Desktop’ API
  • Make UI Control Behaviors public
  • UI Control Actions API
  • Public Focus Traversal API
  • JavaFX support for multi-resolution images
  • Draggable tabs
  • Image IO

1 番目は AWT 向けの JEP である JEP 272: Platform-Specific Desktop FeaturesJavaFX 向けにも提供しようというものです。この JEP はプラットフォーム固有のデスクトップ機能をより利用できるようにするというものです。過去には Java6 で一度強化が入っており、タスクトレイへのアクセスなどが可能になったりしましたが、久々にこの分野にテコ入れが入ることになります。

  • Mac のアプリケーションメニューの利用 (かつて Apple Java に含まれていた EAWT では提供されていました) 。
  • Windows タスクバー (Mac ではドック) のジャンプリストへのアクセス。
  • Windows タスクバー (Mac ではドック) でのプログレス表示。

よりネイティブなアプリケーションとして振る舞うことが可能になるので、この強化は是非とも入って欲しいなあと思っています。ましてや近年は javapackager を使ってネイティブアプリっぽく配布することが推奨されていますし。まあ、AWT には入るので、最悪 JavaFX 側に入らなくても何とかなるのではありますが、JavaFX 側から AWT の API を触るときは別スレッドにする必要があって面倒ですし。

3 番目とか 4 番目は Swing にある同名の API と同じかな。Action API は欲しいですねえ。

5 番目は HiDPI 環境におけるビットマップ画像の扱いのことでしょうか。WindowsMac 共に HiDPI 対応が入りましたが、ビットマップ画像の扱いは Mac 式になっており、整数倍にしか対応していません。Mac 式にしたのは恐らく暫定対応でしょうから、ここできちんと対応するということなのでしょう。

最後の Image IO も詳細は不明ですが、このあたりはまだまだ足りないところが多いので期待したいところですねえ。

こんな感じで思わぬところから、今後の機能強化について話が出てきました。幸い (?) Java9 のリリース延期はほぼ確実でしょうから、JavaFX9 ももう少し新しい機能が入った状態でリリースされることにはなりそうです。
まあ、そうは言ってもやっぱり OracleJavaFX に対してリソース抑えているよなあとは思いますが、それはまた別の話で。

明日は id:skrb / @ さんの予定です。

*1:元々は Sun で LWUIT の開発をされていた方のようです

*2:OpenJFX での開発は継続しています。

*3:Gluon が開発を継続し、 ここ でバイナリの配布も行っています。

JavaFX 8u60の新機能 (特にWindowsでのHiDPI対応について)

先日、JDK8 の機能アップデート版である JDK 8u60 がリリースされました。このバージョンのリリースノートは以下です。

http://www.oracle.com/technetwork/java/javase/8u60-relnotes-2620227.html

ですが、このリリースノートにはバンドルされている JavaFX 8u60 についての記述がありません。
以前から、JavaFX については 8u60 は安定性向上がメインのリリースになる、とのアナウンスがあったのですが、機能面でのアップデートが少し入っているようなので、自分が調べた範囲で分かったことをまとめます。

自分が調べた範囲では JavaFX 8u60 に次のアップデートが入っていることが分かりました。

  1. Windows で HiDPI 対応が入っている
  2. WebView の WebKit のバージョンが上がっている

Windows での HiDPI 対応について

まず 1 の Windows における HiDPI 対応についてです。こちらについては自分のブログでも以前に次のようなエントリを書いていました。

aoe-tk.hatenablog.com

そのエントリで「とにもかくにも JavaFXWindows でも Device Independent Pixel (DIP) に対応してくれるのが一番なんですけどねえ。」と結んでいたのですが、まさしくその対応が入ったことになります。

最近増えてきた高解像度端末では、そのままだと文字やアイコンなどが小さくなって読めたものじゃありません。そこで、Windows では物理的なスクリーンのサイズと解像度を見て、125%、150%、200%...と自動的にスケーリングを行うようになります。
私が使っている VAIO Tap 11 は 11 インチのディスプレイで解像度が Full HD (1920 x 1080) の端末なのですが、デフォルトで 125% のスケーリングが掛かっています。なのでこの端末だと、100px の長さのコンテンツが画面上では 125px を使って描画されるようになります (Windows のスケーリング設定を見てくれる処理系である場合) 。

これまでの JavaFX では、基準フォントサイズについては Windows のスケーリング設定を見て描画してくれたのですが *1 、プログラム上のピクセル指定についてはそのような考慮がありませんでした。それが 8u60 ではスケーリング設定の倍数を掛けたピクセル数で描画してくれるようになります。

詳細については OpenJFX の ML の以下のポストを参照してください。

http://mail.openjdk.java.net/pipermail/openjfx-dev/2015-June/017337.html

と言うわけで自分の VAIO Tap 11 でもスケーリングが掛かる!と思っていたのですが、 変わりませんでした
調べてみたところ、 スケーリングの設定が有効になるのは、スケーリングが 150% 以上の場合 という制約が掛かっているようです。
JDK Bug System の次のチケットなどでこの点について議論されていました。

https://bugs.openjdk.java.net/browse/JDK-8129862

https://bugs.openjdk.java.net/browse/JDK-8130748

どうも、125% のスケーリングではフォントがぼやけて描画されてしまう問題を解決できず、125% ではスケーリングを切ることにした模様です。残念。 *2

ただ、起動引数を使って強制的にスケーリングの設定を掛けることはできます。Javaシステムプロパティ glass.win.uiScale を使って、次のように指定します。

$ java -Dglass.win.uiScale=125% -jar Application.jar

これを使って、自分の VAIO Tap 11 で Ensemble.jar を起動してみたスクリーンショットを示します。

f:id:aoe-tk:20150823231117p:plain

きちんと 1.25 倍にされていることが分かりますね。フォントは確かに少しぼやけてましたが、そんなに気にするほどでもなかったかなあ。
次のアコーディオンのサンプルとかも分かり易いと思います。このアコーディオンは高さ、幅をピクセルで指定しています。

f:id:aoe-tk:20150823231356p:plain

HiDPI 対応についてまとめるとこんな感じです。

  • Windows 上でスケーリングの掛かっている環境では、プログラム中のピクセル指定に設定された倍数を掛けた数のピクセル数で描画される。
  • ただしこの設定が有効になるのは 150% 以上から。
  • 強制的に任意のスケーリングを指定したい場合はシステムプロパティ glass.win.uiScale を使って指定する。
  • ビットマップ画像については、Apple 式の規則が適用され、整数倍の大きさの画像を用意しておけば、それが適用される。

詳細については上に示した OpenJFX の ML のポストを参照してください。

WebKit のバージョンアップについて

JavaFX 8u60 では WebView に使っている WebKit のバージョンが上がっています。主にセキュリティフィックスを取り込むためですが、少しだけ機能追加があるようです。

まず WebView の User-Agent の違いを示しておきます。

JavaFX 8u40
Mozilla/5.0 (Macintosh; Intel Mac OS X) AppleWebKit/537.44 (KHTML, like Gecko) JavaFX/8.0 Safari/537.44
JavaFX 8u60
Mozilla/5.0 (Macintosh; Intel Mac OS X) AppleWebKit/538.19 (KHTML, like Gecko) JavaFX/8.0 Safari/538.19

WebKit のバージョンが少し上がっていますね。

HTML5TEST を使ってみると、次のような違いがありました。

  • ECMA Script 6 の Promise に対応している。
  • URL API に対応している...らしい。
    • これどんな API
  • picture 要素に対応していないのに、なぜか srcset 属性には対応しているという結果が出る。
    • これは多分テストが何かおかしい。

大きな違いはありませんが、Promise に対応したというのは大きいですね。
8u40 でのテスト結果は こちら で、8u60 での結果は こちら で参照することができます。

なお、HTML5 で新たに追加されたフォーム要素でカレンダーや色のピッカーがまともに動いていない問題 (私の過去の このエントリ でも触れています) 、は未だに対応していませんねえ。
バグトラッカにも チケットが上がっています が、放置状態です...。

以上、JavaFX 8u60 の新機能について、私が調べた範囲では大体こんなところです。他にも何か情報があったら、教えてもらえると幸いです。

*1:そのため CSS で長さの単位に em を使用することで、HiDPI 対応することは可能でした。

*2:Surface Pro シリーズならば 150% 以上のスケーリングが掛かっているので、デフォルトでスケーリングが有効になっているはずです。誰か試して。

JJUG ナイトセミナー 「Reactive Streams特集」の感想

6/24 に開催された 【東京】JJUG ナイトセミナー 「Reactive Streams特集」 に参加してきました。これはその感想エントリになります。
ここ最近は勉強会に参加しても、それについてのエントリを書くのをサボってましたが (^^;; 、今回の勉強会は自分にとって非常に考えるところが多かった内容でしたので、ちょっと思ったところをだらだらっと並べてみました。

勉強会について

勉強会の内容は 2 部構成になっていました。前半は岡本雄太 (id:okapies) さんによる「Reactive Streams 入門」で、Reactive Streams について、似た言葉である Reactive Programming や Reactive Manifesto と併せて、それぞれの概念の違い、関わりについて分かり易く説明してもらいました。

https://speakerdeck.com/okapies/reactive-streams-ru-men-number-jjug

またこの発表内容について、岡本さん自身が素晴らしいフォローアップエントリを書いてくださっています。 okapies.hateblo.jp

後半はよしだ (@) さんによる「Reactive Streamsを使ってみよう」で、こちらは RxJava を題材に実際のコード例について解説するという内容でした。

http://www.grimrose.org/jjug-2015-reactive-streams/#!index.md

よしださんも自身の発表内容についてのフォローアップエントリをあげています。 http://www.grimrose.org/blog/2015/06/jjug-2015-reactive-streams/www.grimrose.org

勉強会の感想

今、ソフトウェア開発の世界では「Reactive なんちゃら」という言葉がすごく流行っている印象があります。
私が主に目にしていたのは、当日岡本さんが発表していたところの "Reactvie Programming" であったわけですが、次に示すような、一見交わらない世界でそれぞれブームになっている印象があってとても興味深いなと思っていたところでした。

  • GUI 開発の世界
    • 特にデスクトップアプリ、スマートフォンアプリ、Web の SPA のようなリッチクライアントの実装
  • 大規模分散並列処理の世界

そんなところで Typesafe 社らが中心になって、 Reactive Manifesto なる宣言をどーん、と出してきたりして、「あれ、これって流行りの Reactive Programming の話と関係するの?」「でもその割にはターゲットとしているところが違うような...」「ていうか何このメンツ?」と正直混乱していたところでした。

岡本さんは「Reactive なんちゃら」なキーワードとして次の 3 つについて、それぞれの違いについて次のようにとても分かり易く説明してくれました。

  • Reactive Programming は「プログラミングモデル」
  • Reactive Manifesto は「アーキテクチャ
  • Reactive Streams は「ランタイム」

Reactive Programming について

Reactive Programming はデータフローの仕組みを構築するために、関数を DAG としてつなぎ合わせる形で記述し、データの伝搬はランタイムに任せるというプログラミングスタイルです。データの変更を自動的に伝搬させることができる、非同期化、並列化しやすいというメリットがあります。

少し違うところがありますが、このプログラミングスタイル、GUI の世界では「データバインディング」という形でかなり馴染みのある概念だったと思います。
例えば、岡本さんの発表で紹介されたサンプルの式を JavaFXバインディングを使って表現してみると、次のように記述することができます。

   @FXML
   Label answer;
   @FXML
   Spinner<Integer> valueA;
   @FXML
   Spinner<Integer> valueB;

   private void bind() {
       IntegerProperty a = new SimpleIntegerProperty(0);
       a.bind(valueA.valueProperty());
       IntegerBinding a1 = a.add(1); // a1 = a + 1
       IntegerProperty b = new SimpleIntegerProperty(0);
       b.bind(valueB.valueProperty());
       IntegerBinding b1 = b.add(-1).multiply(2); // b1 = (b - 1) * 2
       answer.textProperty().bind(a1.add(b1).asString()); // a1 + b1
   } 

これを動作させると次のように動きます。

全体のコードは Gist にアップしておきました。
https://gist.github.com/aoetk/9c74ee1bb17afb304bff

このように GUI の世界ではこのプログラミングスタイルがかなり前から浸透していましたが、同じようなプログラミングスタイルをもっと他の場所でも使えないか?ということで .NET の世界で Reactive Extensions が登場し、一気に Reactive Programming が流行りだした、というのが私の認識です。
ただ、データバインディングはアプリケーションが扱っているデータの変化に着目していますが、Reactive Programming を GUI に適用する場合はイベントのストリームに着目しているという違いがあるかなと私は考えています。
ちなみに JavaFX の世界でも、イベントストリームに対して Reactive Programming を行うための ReactFX が登場しているようで、機会があれば自分で作るアプリケーションにも使ってみたいな、と思っています。

Reactive Manifesto について

Reactive Manifesto については今回の説明を聞いて、「ああ、これは REST のような、アーキテクチャ原則についてまとめたものだったんだあ」とやっと腑に落ちました。
Reactive Manifesto の中では、昨今見られる大規模システムに共通してみられる設計を備えたシステムを Reactive Systems と名付けています。それらには即応性、弾力性、耐障害性、メッセージ駆動といった特徴を備えているとのことです。
具体例が特に挙げておられず、マーケティング色の濃い文書であるとの説明がありましたが *1 、Microservices というスタイルのアーキテクチャが評価されてきていたり、TwitterFacebook では大量のメッセージを捌くために、非同期でメッセージ駆動ベースのアーキテクチャを構築していますし、昨今のシステム開発においてこのようなアーキテクチャスタイルが求められるようになってきているもの確かだな、と思いました。
成功したパターンから原則を見出しているところも REST と似ていますね。あれも WWW が何故成功したのか?を出発点として出てきたものなので。

Reactive Streams について

で、本題の Reactive Streams ですが、これは Java における Non-Blocking かつ Back Pressure 付きの非同期ストリーム処理を標準化するためのものであるとの説明でした。何と Java9 での標準化を目指しているとのことで、これは驚きました。 *2
非同期ストリーム処理版 JDBC のようなものですが、JDBC とは比較にならないほど対象が広くふわふわとしてるなあと正直思いました。(^^;;

今のところ API 自体はインターフェースが 4 つあるだけのとてもシンプルなものです。まあこれだけ広い対象から最大公約数的に抽出するとこうなっちゃいますかね。
一般的な Publish-Subscribe パターンに加えて、Subscriber 側の処理能力を超えないように、Subscriber 側が Subscription という形で要求を示す (これを Back Pressure と呼ぶそうです) というアーキテクチャになっています。

シンプルではありますが、API を標準化することのメリットは小さくないと思います。今回のよしださんの発表では RxJava と Akka Streams をつなげるサンプルがありましたが、このように異なる世界を同じプログラミングスタイルでつなげられるのは大きいと思いました。

まとめ

だらだらと書き連ねてきましたが、Reactive Programming はプログラミングスタイルに、Reactive Systems はアーキテクチャスタイルについて言及したもので直接的にはつながるものではない (ただし、後者は思想的には前者の影響を受けている) 。Reactive Streams は Reactive Systems のようなシステムを構築する上での道具立てを、Reactive Programming なスタイルで可能にすることを目指したものというのが自分の理解です。
今まで断片的にしか目にしていなかった情報がかなりつながったので、参加してとても良かったと思いました。
素晴らしい発表をしてくださった岡本さん、よしださん、そしてこのような場を設けてくださった JJUG 幹事の皆さん、ありがとうございました。

*1:確かに Typesafe 社の Typesafe Reactive Platform を売り込むものなんだろうなという感じがします

*2:java.util.concurrent パッケージに入れようとしているようです

JavaFXのWebViewの検索を実現するのにもっと簡単な方法がありました

昨年の JavaFX Advent Calendar で次のようなエントリを書きました。 aoe-tk.hatenablog.com

このエントリでは、JavaFX の WebView を使ったアプリケーションに検索機能を実装する方法として、JavaScript のページ検索ライブラリを使う方法を紹介しています。
そこでは jQuery プラグインを使って実装していましたが、そのライブラリの CSSJavaScript オブジェクト定義が WebView で表示しているコンテンツのそれとバッティングする危険性がありました。

ところが、window.find() という非標準の関数があり、JavaFX の WebView がそれをサポートしていることを知りました。 *1
これを使えば特別なライブラリを読み込むこと無く、簡単に Web ページの検索機能を実現することができます。

以前の方法では、WebView 上のドキュメント読み込み完了時に検索のための jQuery プラグインの読み込みや、そのプラグインが要求する CSS クラスの定義の読み込みを行っていましたが、 window.find メソッドを使う場合はその必要はありません。何せ組み込みのメソッドなので。
件のエントリで解説した highlightPage() メソッドの実装を次のように変更します。

private static final String FIND_FUNCTION = "window.find(\"{0}\", false, false, true, false, true, false)";

private void highlightPage(Optional<String> word) {
    if (webEngine.getDocument() != null) {
        final String keyword = word.orElse("");
        if (!keyword.isEmpty()) {
            webEngine.executeScript(MessageFormat.format(FIND_FUNCTION, Encode.forJavaScript(keyword)));
        }
    }
}

単純に JavaScriptwindow.find メソッドを呼び出すように変更しています。
メソッドに渡す文字列に Encode.forJavaScript() というメソッドを噛ましていますが、これは OWASP Java Encoder Project というライブラリが提供する JavaScirpt 特殊文字をエスケープするための関数です。

また window.find メソッドは繰り返し呼ぶと、ページ内の次の一致箇所にフォーカスしてくれるので、次のように検索テキストフィールドのアクションイベントで highlightPage() メソッドを呼び出すようにし、検索フィールドにフォーカスがある状態で Enter キーを叩くと、次の一致箇所にフォーカスするようにしました。

<TextField fx:id="pageSearchBox" onAction="#handleSearchBoxAction" promptText="Find in page" HBox.hgrow="NEVER" />
@FXML
void handleSearchBoxAction(ActionEvent event) {
    highlightPage(Optional.ofNullable(pageSearchBox.getText()));
}

こうすると次のように検索フィールドにフォーカスがある状態での Enter キーをヒットすると、次の検索一致箇所にフォーカスが移動します。
f:id:aoe-tk:20150615001101p:plain

と言うわけで、JavaFX WebView で検索機能を実現する方法の訂正版でした。組み込みのメソッドを利用するので、この方法が一番良いと思われます。

実装の全体を確認されたい方は、私が開発している Social Bookmark Viewer FXBookmarkViewController.java 及び BookmarkView.fxml の実装を参照してください。

*1:この関数は元々 Netscape Navigator が独自に実装していた関数で、Firefoxレンダリングエンジンである Gecko もサポートし、WebKit もサポートしたようです。

JavaFXで2つのListViewのスクロールを同期する方法

とある目的があって (それがなんなのかはまた別途まとめます。) 、JavaFXListViewTableView について、同じ数のアイテムを持つ 2 つの ListView (TableView) のスクロール状態を同期する方法について調べてみました。

やりたいこと

次のように同じレコード数の ListView を並べます。
f:id:aoe-tk:20150522191146p:plain

片方をスクロールさせると、もう片方も一緒にスクロールさせるようにします。 どのスクロール手段 (マウスホイール、スワイプ、スクロールバーの操作) を用いても同期するようにします。
f:id:aoe-tk:20150522191220p:plain

Swing での実現方法

これを実現するにあたって、Swing で行った方法と同じ方法を使えないかを考えました。

Swing では、JListJTable をスクローラブルにするためには、JScrollPane に追加を行う必要があるのですが、この JScrollPane が保持する Model (BoundedRangeModel) を 2 つの JScrollPane で共有することで、簡単にスクロールを同期することが可能になります。
まさしく MVC パターンの利点ですね。

以下のサイトで詳しく説明されているので、参考にしてください。

ateraimemo.com

JavaFX では何もしなくても ListView や TableView はスクロール可能です *1 。ListView (TableView) 自身がスクロールバーを保持しています。
JavaFXScrollBar には value プロパティがあり、このプロパティがスクロール位置を示します。ということは、2 つの ListView (TableView) が保持する ScrollBar の value プロパティをバインドすることで、スクロールを同期させることが可能なはずです。

ListView が保持する ScrollBar の取得

ここで問題が発生します。ListView や TableView には、自身が保持する ScrollBar のインスタンスを取得するための公式の API が存在しないのです。どうやって ScrollBar のインスタンスを取得すればいいのでしょうか?

実は Node には lookuplookupAll という、CSS セレクタで自分の子供として保持している Node インスタンスを探すためのメソッドがあります。
そして、 ListView の CSS リファレンス を見ると、次のような情報が記載されています。
f:id:aoe-tk:20150522192012p:plain

ListView の子 Node の情報が記載されており、それぞれの Node の CSS スタイルクラスの情報が記載されています。 ScrollBar には .scroll-bar というスタイルクラスが設定されていることが分かりますね。これで ScrollBar のインスタンスを取得することができますね!

というわけで、ListView から ScrollBar のインスタンスを取得するためのコードスニペットを以下に示します。水平スクロールバーと垂直スクロールバーの 2 種類があることに注意してください。

private ScrollBar getScrollBar(ListView<String> listView, boolean vertical) {
    Set<Node> nodes = listView.lookupAll(".scroll-bar");
    for (Node node : nodes) {
        if (node instanceof ScrollBar) {
            ScrollBar scrollBar = (ScrollBar) node;
            if (vertical && scrollBar.getOrientation() == Orientation.VERTICAL) {
                return scrollBar;
            } else if (!vertical && scrollBar.getOrientation() == Orientation.HORIZONTAL) {
                return scrollBar;
            }
        }
    }
    throw new IllegalStateException("Not found!");
}

ScrollBar のインスタンスを取得できてしまえれば、後は value プロパティをバインドするだけです。次のようにバインドします。

@FXML
ListView<String> leftListView;

@FXML
ListView<String> rightListView;

private void bindScrollBar() {
    ScrollBar leftVerticalScrollBar = getScrollBar(leftListView, true);
    ScrollBar rightVerticalScrollBar = getScrollBar(rightListView, true);
    leftVerticalScrollBar.valueProperty().bindBidirectional(rightVerticalScrollBar.valueProperty());
    ScrollBar leftHorizontalScrollBar = getScrollBar(leftListView, false);
    ScrollBar rightHorizontalScrollBar = getScrollBar(rightListView, false);
    leftHorizontalScrollBar.valueProperty().bindBidirectional(rightHorizontalScrollBar.valueProperty());
}

これで垂直、水平スクロールが同期されます。

まだ罠がある

これで解決...と思いたいところですが、重大な注意点があります。それは ScrollBar インスタンスの生成タイミングです。 ListView や TableView はコンストラクタで new されたタイミングでは ScrollBar のインスタンスを生成しない のです。

JavaFXコンポーネント ( javafx.scene.control.Control を継承して作ったコンポーネント) はそれがシーングラフに追加され、実際にレンダリングされるタイミングまで見た目の部分を構築する処理を遅延するようになっています。
カスタムのコンポーネントを作成された方はご存じだと思いますが、Control には layoutChildren というフックメソッドがあり、コンポーネントのレイアウトを構築する処理はこのメソッドで行うように指示されています。

JavaFX のグラフィクス描画は保持 (retained) モードと呼ばれる仕組みになっており、描画命令は即座に実行せずまとめておき、まとまったタイミング (これを実行しているのが Pulse と呼ばれる、タイマー起動のイベントです) で一斉に実行します。
この方式は描画処理をまとめて実行するので効率が良い、ディスプレイのリフレッシュと同期できるのでなめらかな動きになるといった利点があり、WPFFlex など最近の GUI ツールキットは大抵この方式になっているようです。 *2

そのような考え方になっているため、コンポーネントのレイアウト処理についてもフックメソッドを用意して、まとめて処理するようになっているということです。

話を戻しますが、このように ListView (TableView) が保持する ScrollBar のインスタンスは生成が遅延されるので、例えば FXML コントローラーの initialize メソッドの処理で ScrollBar インスタンスを取り出そうとしても、この時点ではシーングラフに追加されていないので、null が返ってくることになります。

今回のコード例では次のように ScrollBar を取り出してバインドする処理をくくり出しておきます。 Platform.runLater で処理をくるんでいるのは、次の Pulse 実行後 (そこではレンダリング処理が終わっているので) に呼び出されるようにするためです。

public void requestBind() {
    Platform.runLater(this::bindScrollBar);
}

このメソッドを、FXML 読み込み側で次のように呼び出してもらうようにしました。

FXMLLoader loader = new FXMLLoader(getClass().getResource("sample.fxml"));
Parent root = loader.load();
primaryStage.setTitle("Scroll Test");
primaryStage.setScene(new Scene(root));
primaryStage.show();
Controller controller = loader.getController();
controller.requestBind();

これで、最初に紹介したスクリーンショットのように、2 つの ListView のスクロールを縦も横も同期できるようになります。
全体のコードは Gist にアップしているので、そちらをご覧ください。

https://gist.github.com/aoetk/b771a82d7069cba547b7

まとめ

まとめると次のようになります。

  • JavaFX でスクローラブルなコンテンツのスクロール状態を取得、更新する場合は、Swing と同様、スクロールバーオブジェクトが保持するステートを利用すれば良い。
  • JavaFX の ListView、TableView が保持するスクロールバーを取得するためには、現状 CSS セレクタ経由で取得するしか方法がない。
  • 上記方法でスクロールバーを取得する場合、レイアウト処理が一度行われた状態で取得するよう注意する必要がある。

スクロールバーあるいはスクロール状態を取得するための公開 API がないので、ややアクロバティックな方法になってしまっていますね。スクロール状態の取得、あんまり需要がないのかなあ?

さて、これはある目的を達成するための前調査だったんですよねえ。それについてはまた後ほど。

おまけ

Pulse をはじめとする、JavaFX の描画の仕組みについての話は、以前の JavaFX 勉強会で私の発表でも触れています。

また、「パーフェクトJava」の JavaFX のパートでもコンパクトかつ丁寧に説明されているので、こちらもおすすめです。

改訂2版 パーフェクトJava

改訂2版 パーフェクトJava

*1:というかほとんどの GUI ツールキットではそれが普通ですが...。

*2:反面、細かいチューニングが難しい、描画命令をキャッシュするのでメモリ消費が多くなるというデメリットもあります。

主要IDEのJavaFXプロパティへの対応状況

はじめに

JavaFX 開発を行ったことがある人はご存じだと思いますが、JavaFX ではプロパティに対して既存の JavaBeans のプロパティとは異なる新しい API を導入しています (以降、JavaFX で導入された新しいプロパティ構文のことを「JavaFX プロパティ」と呼ぶことにします) 。
それについては自分のブログでも以前に「 JavaFX Advent Calendar 2012 26日目 GroovyのVetoableを使ったサンプルをJavaFXのバインディングを使って実装してみる 」というエントリを書いていました。

このエントリでも解説していますが、JavaFX では従来の getter、setter とは異なるプロパティの定義を行います。
NetBeansEclipseJavaFX 開発環境である e(fx)clipse では少し前からこのプロパティに対応していたのですが、つい最近、 IntelliJ IDEA でもしれっと JavaFX プロパティの生成に対応していたことに気付きました。
ということで、主要 3 大 IDEJavaFX プロパティへの対応状況についてまとめてみます。

JavaFX プロパティの記述について

まず、IDE がどのように JavaFX プロパティの記述をするべきかについて示します。

String 型の name という名前の読み書き両方が可能なプロパティは次のように記述します。

private StringProperty name = new SimpleStringProperty();

public StringProperty nameProperty() {
    return name;
}

public final String getName() {
    return name.get();
}

public final void setName(String name) {
    this.name.set(name);
}

JavaFX のプロパティは XXProperty 型 ( XX にはラップする値のクラスが入る) でラップします。そしてこの Property 型の get/set メソッドで値のやり取りをします。
外部に公開するときは "(プロパティ名)Property()" という名前のメソッドを宣言します。最低限これができていれば JavaFX の範囲では OK です。
さらに、既存の JavaBeans 仕様に合わせる必要があるときは getter/setter も上記コードのように実装します。サブクラスで置き換えられるのを防ぐために final 宣言するのが一般的です。

リードオンリーなプロパティの定義については、 上に挙げたエントリでも解説したように 二通りの方法がありますが、多くの場合は ReadOnlyXXWrapper 型を使って次のように記述します。

private ReadOnlyStringWrapper name = new ReadOnlyStringWrapper();

public ReadOnlyStringProperty nameProperty() {
    return name.getReadOnlyProperty();
}

public final String getName() {
    return name.get();
}

IntelliJ IDEA

まず、自分が最近実装に気付いた IDEA から見てみましょう。

IDEA の場合、先にフィールドの定義を行ってから、コード生成の Getter and Setter を実行します。
f:id:aoe-tk:20150428011717p:plain

getter/setter 生成の対象が JavaFX プロパティに相当する型であった場合... f:id:aoe-tk:20150428011902p:plain

次のようなコードが生成されます。 f:id:aoe-tk:20150428012414p:plain

ちゃんと JavaFX プロパティの形式に合わせたメソッドが生成されていますね!いつの間にこの機能を実装したんだろう?
ヘルプにはこの機能についての解説は一切ありませんでしたし、今までのリリースノートをひっくり返しても特にこの機能に関する記述は見当たりませんでした。
この機能の存在については 某社のあの人 も、 IntelliJ IDEA エバンジェリストのあの人 も知らないに違いない!

次にリードオンリープロパティについて見てみます。次のようなフィールドを宣言した状態でコード生成をしてみます。 f:id:aoe-tk:20150428013019p:plain

その結果は次の通り。 f:id:aoe-tk:20150428013048p:plain

ああ、残念。対応できてませんね...。本当はこうなって欲しいんです。 f:id:aoe-tk:20150428013201p:plain

ちなみに、フィールドを Wrapper ではなく、ReadOnlyXXProperty 型で宣言した場合は getter のみ生成します。これはバグでしょうね。後で YouTrack に起票しておきますかね。

NetBeans

次は NetBeans です。IDEA の時は予めフィールドを宣言しましたが、NetBeans の場合はフィールドの生成からウィザード形式で作るようになっています。 JavaFX プロジェクトの場合、コード生成リストに「Java FXプロパティの追加」というメニューが追加されています。*1
f:id:aoe-tk:20150428013719p:plain

これを選択すると次のようなダイアログが表示されます。 f:id:aoe-tk:20150428013920p:plain

フィールド名やプロパティの型、初期値などを指定できます。さらに読み書き可能かリードオンリーかも選べます。読み書き可能を選ぶと次のようなコードが生成されます。 f:id:aoe-tk:20150428014039p:plain

リードオンリーにしたい場合はダイアログの内容を次のように入力します。 f:id:aoe-tk:20150428013833p:plain

すると次のようなコードが生成されます。 f:id:aoe-tk:20150428014157p:plain

さすが Oracle が開発を引っ張っている IDE だけあって、ちゃんと対応できていますね。ただ残念なことに、このウィザードは JavaFX プロジェクトでしか出現しません。そのため、Maven で作ったプロジェクトではこのウィザードが出現しないのです。JavaFX 形式のプロパティは JavaFX アプリケーション以外でも利用可能なので、このような余計な制限は撤廃して欲しいですね。

Eclipse (e(fx)clipse)

最後は最も利用者が多いであろう Eclipse です。厳密には EclipseJavaFX 開発機能を追加するプラグインである e(fx)clipse についてですが。

e(fx)clipse の場合、IDEA の場合と同じように、予めフィールドを作成しておいてからコード生成の Generate JavaFX Getters and Setters を選択します。 f:id:aoe-tk:20150428014904p:plain

すると次のようなウィザードが起動され、対象となるフィールドを選択します。 f:id:aoe-tk:20150428015018p:plain

OK を選択すると次のようなコードが生成されます。オプションで final にするかを選択できるのもいいですね。 f:id:aoe-tk:20150428015117p:plain

フィールドが ReadOnlyXXWrapper 型の場合、次のようにちゃんとリードオンリープロパティのためのコードが生成されます。完璧ですね。 f:id:aoe-tk:20150428015304p:plain

というわけで、主要 3 大 IDEJavaFX プロパティへの対応状況についてまとめてみました。IDEA のリードオンリープロパティの対応について問題がありますが、どの IDE も基本的な対応がされていることが分かりますね。
JavaFX プロパティは記述がちょっと面倒だな、と思っていた方もいらっしゃったかも知れませんが、このように IDE のサポートも得られるようになったので、ご安心 (?) ください。

JavaFX で新しい形式のプロパティが導入されたことの意義についてはまた別の機会にまとめたいと思っています!

*1:Java と FX の間にスペースが入っているのはどういうことだ...