ひらおかゆみのなげやりブログ

もう、なげやりです…

JavaFXのHTMLEditorの機能と限界

このエントリは、JavaFX Advent Calendar 2017の24日目です。昨日は櫻庭 (@skrb / id:skrb) さんの「ControlsFX 準備編」、明日は櫻庭さんが今年のJavaFX Advent Calendarを締めてくださいます。

今年のわたしは、HTMLEditorを使ったWebオーサリングツールが作れないものかと試行錯誤していました。

最初のエントリはこちら。何も考えずにHTMLEditorで何でもできる?と思って挑戦して挫折したものです。

blog.yumix.net

次に、青江さんからJDK 9でHTMLEditorSkinが公開APIになったから細かい制御ができるよと伺って試してみた結果です。確かにHTMLEditorSkinの存在自体は偉大なのですが、現状機能不足でちょっと力不足だった、というのがこちらのエントリです。

blog.yumix.net

最終手段、リフレクションでHTMLEditorSkinの内部を直接叩こうという試みが今回のエントリになります。なお、リフレクションを使うと言った途端、@khasnuma (@btnrouge) が自作のリフレクション・ユーティリティを勧めてきたのですが…

f:id:yumix_h:20171224032809p:plain

(ごめんなさい、元ネタよく知らないの…)

方法1: リフレクションでHTMLEditorSkin.executeCommand()を呼び出す

HTMLEditorのコマンドは、最終的にはprivateメソッドexecuteCommand()を呼び出しているようです。ということは、リフレクションでexecuteCommand()を呼び出せば何とかなりそう、というのが最初に考えたこと。

ただ、実際にやってみるとうまくゆきませんでした。

private boolean executeCommand(HTMLEditorSkin.Command command) throws Exception {
    Method method = HTMLEditorSkin.class.getDeclaredMethod("executeCommand", String.class, String.class);
    method.setAccessible(true);
    return (Boolean) method.invoke(htmlEditor.getSkin(), Command.COPY.getCommand(), null);
}

@FXML
public void handleCut(ActionEvent event) throws Exception {
    executeCommand(HTMLEditorSkin.Command.CUT);
} 

これはCutコマンドを直接executeCommandでメニューから呼び出した時のコードです。実行結果は…選択範囲をCutするとクリップボードには入るのですが、Cutした文字列が画面から消えない。

CutボタンはHTMLEditorSkin.cutButtonフィールドに対応していて、そのonActionイベントにはexecuteCommandともう1つ処理が含まれていました。たぶん、選択範囲をクリップボードに入れた後に画面から消す処理が別に存在しているのだと思います。

方法2: ボタンのonActionイベントをリフレクションで呼び出す

メニュー選択時にボタンをクリックするアクションをシミュレートすれば同じ結果が得られるはず、ということで試してみました。

private void fireButtonEvent(String buttonName) throws Exception {
    Field field = HTMLEditorSkin.class.getDeclaredField(buttonName);
    field.setAccessible(true);
    Button button = (Button) field.get(htmlEditor.getSkin());
    button.fireEvent(new ActionEvent());
}
    
@FXML
public void handleCut(ActionEvent event) throws Exception {
    fireButtonEvent("cutButton");
} 

これはすんなり通りました。この例はCutのみですが、CopyやPasteについても正しく動作します。リフレクションの警告は鬱陶しいけれど…これでOKとします。

課題

HTMLEditorには他にも様々なボタンが用意されています。ボタン(+ undocumentedなショートカットキー; 一般的なものと同じですが)を使用している限りは、HTMLの編集に関しては必要最低限の機能は揃っているようです。ただし、HTMLEditorSkinでできることが少ない、頑張ってリフレクションを使っても限度がある、ドキュメントに画像を埋め込めない、など過剰な期待は禁物なコントロールであると実感しました(本当なら1回のエントリで完結するはずだったんですよ!)。

わたし的には、おととしのWebViewにチャレンジしたときのほうがおもしろかったかな?

blog.yumix.net

一応、今回までのところをGitHubにアップロードしておきます。現状、これ以上の発展は望めそうにないですけど、ご参考までに。

github.com

追補

昨日の櫻庭さんのエントリにもありましたが、Java 9でリフレクションを使用する場合には、VM引数に --illegal-access=warn を付けましょう。わたしも1つ、勉強になりました。

JavaFXのHTMLEditorSkinはどこまで使えるか?

このエントリは、JavaFX Advent Calendar 2017の22日目です。昨日は@sk44_さんの「業務で JavaFX をちょっとだけ使ってみた」でした。

前回の私のエントリを公開した後、青江 (@aoetk) さんからアドバイスをいただきました。

よし、これでいける!

早速Cutメニューのハンドラを作ってみたのですが、なぜか動かない。こういう時はまずドキュメントを見よう、ということでjavafx.scene.skin.HTMLEditorSkinの項目を見てみます(それにしてもJDK 9のAPIドキュメントは使い勝手が微妙…)。

Special-case handling for certain commands. Over time this may be extended to handle additional commands. The current list of supported commands is:

  • BOLD
  • ITALIC
  • UNDERLINE

 現時点でサポートされているのはBOLD、ITALIC、UNDERLINEだけ、とはっきり書いてありますね。

JavaFX 9では標準コントロールのSkinが公開されたため、コントロールの見栄えを(CSSでできる以上に)カスタマイズできるようになりました。HTMLEditorのスキンであるHTMLEditorSkinもその1つ。ただし、現時点でHTMLEditorSkinにできることは、

  • レイアウトの微調整
  • BOLD、ITALIC、UNDERLINEボタンの制御

だけでした。ほかのボタンの制御も早くサポートされるといいな。

ここで終わるのも悔しい(&12/24に書くことがなくなる)ので、HTMLEditorSkinの内部にリフレクションAPIでアクセスして、どうにかできないものか試してみます。

CDI 2.0をJava SEから使ってみる

このエントリは、Java Advent Calendar 2017の17日目(遅刻&代行)です。本当は19日目担当でしたが、何日も前に書き終えて下書きのまま忘れていました。@ukiuniさん、お手数をおかけしてすみません。

以前からWeld SEという、CDIをJava SEでも使えるようにしてくれるライブラリを存じいていたのですが、秋に登場したJava EE 8に含まれるCDI 2.0にはJava SEでも使用できるAPIがあると耳にしたので、せっかくなので試してみました。わたしのCDIに対する理解は浅くて、

  • インスタンスの代入をよろしくやってくれる仕組み
  • インスタンスの生存期間をある程度制御できる仕組み

くらいにしか思っていないのですが、とりあえず試してみます。

準備

Java SEでCDI 2.0を使えるようにするためには、pom.xmlにちょこっと追記が必要です。

<dependency>
  <groupId>javax.enterprise</groupId>
  <artifactId>cdi-api</artifactId>
  <version>2.0</version>
</dependency>
<dependency>
  <groupId>org.jboss.weld.se</groupId>
  <artifactId>weld-se-shaded</artifactId>
  <version>3.0.0.Final</version>
</dependency>

最初のはCDI 2.0のAPIですね。2番目は名前からしてWeld SEそのものと思われます。たぶんですが、Weld SEが基となってCDI 2.0のJava SE向け機能が作られたのだと思います。

実はそれだけでは動かなくて、META-INF/beans.xmlを作成してあげないといけないようです。わたしの環境 (Eclipse Oxygen 1a) では CDI 2.0 用のbeans.xmlは生成できないのですが、CDI 1.1用のものを使用しても特に問題はなさそうです。

そしていよいよJavaのプログラミングですが、今回は簡単にメッセージを返すだけのCDI Beanを順に呼び出すものを作成してみました。

  • Bootstrap - メインクラス、CDIの初期化とEntryPointのインスタンス生成を行う
  • EntryPoint - HelloMessageからメッセージを集めて返すCDI Bean
  • Hello - "Hello"を返すDependentスコープのCDI Bean
  • Message - "Greeting"を返すApplicationスコープのCDI Bean

ソースコードはGitHubにアップロードしましたので、そちらを参照してください。

github.com

CDIがJava SEでも使えるようになり、Java EEで培われたテクニックが使えるようになることはとても素晴らしいことだと思います。以前からWeld SEに注目していたわたしとしては、この新しいAPIに多いな期待を寄せています。

さて、最後にCDIをJava SEで使用する上での注意点です。当然ですが、Java EEで当たり前のように使える機能の一部はJava SEではサポートされません。一例をあげると、スコープを規定するアノテーションは、Java SEでは@Dependentと@ApplicationScopedしか使用できません。@Singletonが使用できなかったのは意外ですが、@RequestScopedや@SessionScopedなどはWebシステム固有のスコープだったため、サポートされなくても仕方ないと割り切っています(必要とされる場面も思い浮かびませんし)。また、Java EE 7以降はbeans.xmlを省略可能ですが、前述の通りJava SEではbeans.xmlが必須です。適当に書いても動きそうなので、プロジェクトを作成したら忘れないうちに入れておきましょう。

その他、CDIに関する注意事項はだいたいJava SE環境でも当てはまるようです。

 

JavaFXのHTMLEditorはどこまで使えるか?

このエントリは、JavaFX Advent Calendar 2017 の 16 日目です。昨日は平田あづみ(@planet_az / id:planet-az)でした。明日は@opengl_8080さんです。

JavaFXにはHTMLEditorというそのものずばりなコントロールが付属しています。HTMLの編集と入出力、印刷ができるようです。これがあればホームページビルダーみたいなソフトを簡単に自作できるのでは?ということでちょっと試してみました。

とりあえず作ってみたのがこれです。Scene Builderの最新版にメニュー付き画面のテンプレートが用意されているので、それを使ってみました。

f:id:yumix_h:20171213003612p:plain

見た目だけなら割と本格派です。実際にワードパット並みの編集機能は持っています。何となくですが使えそうな気にはさせてくれます。

ただ、このHTMLEditorには1つ問題があって、カット・コピー&ペーストやらフォントやら段落書式やら編集操作はいろいろできるのですが、それらをプログラムから制御することができません。例えば、メニューからカット・コピー&ペーストを行おうにも、メニューから呼び出すためのインタフェースがHTMLEditorには備わっていません。

悔しいので深く追っていくと、HTMLEditorは処理のほとんどをHTMLEditorSkinというクラスに委ねていることがわかります。いわゆるhas-Aの関係です。さらにHTMLEditorSkinを見ていくと、内部でWebViewクラスとWebPageクラスを使っていることがわかります。WebPageはWebView内部から呼び出されるクラスで、HTMLEditorSkinからも直接呼び出されます。どうやらHTMLEditorはHTMLの編集にWebViewの内部機能(たぶんWebKit)を用いているようです。HTMLEditorが生成するHTMLはこんな感じになります。

f:id:yumix_h:20171213005838p:plain

見づらい…でも、自動生成したHTMLにインデントや改行を求めてはいけませんよね…

HTMLEditorの様々なボタンは、HTMLEditorSkinがprivateフィールドで保持しているようです。リフレクションを使ってprivateフィールドに強引にアクセスするのはSIer辞めてPayaraに移った例の人が大好きな手法ですが(あの人確か4~5年くらいリフレクションのユーティリティをスクラップ&ビルドしてたはず)、わたしは今のところやるつもりはないです。

HTMLEditorはJava 9からjavafx.webモジュールに含まれているのですが、なぜjavafx.webに分類されたのか、今回の結果でわかったような気がします。

今回作成した(正確には作成途中というべきか?)アプリケーションは、GitHubにアップロードしています。

github.com

OpenJFXのWikiからJavaFX 9の新機能を俯瞰する

このエントリは、JavaFX Advent Calendar 2017 の 14 日目です。昨日は高橋 @boochnich さんの「JavaFXアプリケーションのファイル構成(FXML, CSS, properties)」でした。明日は平田あづみ id:planet-az / @planet_az です。

ここ数年、JavaFXのアドベントカレンダーは櫻庭 @skrb さんが立てていらして、わたしも毎年それに便乗させていただいていたのですが、今年はそれがなかった。それもそのはず、今年の櫻庭さんはなんと一人アドベントカレンダーに挑戦中!

adventar.org

例えば、12/12は本わらび餅など和菓子特集…ああ、食べたくなってきた。

sunnypartlysweets.hatenadiary.jp

毎年JavaFX Advent Calendarを支えてきた偉大なるトライアングル(櫻庭さん、青江さん、高橋さん)の一角が今年は不在とあっては、さすがに力不足だったのかな?わたしの力ではお三方に遠く及ばないのはわかっているのですが。

今日は、本当は@khasunuma(@btnrouge)に頼もうと思っていたのですが、あいにく風邪で寝込んでいて書けないらしい(でもJava EE Advent Calendar 2017用の記事は数日分のストックがあるから大丈夫だとか)ので、自分で書くことにしました。

さて、ここからが本題です。

JavaFXは現在OpenJFXというプロジェクトでオープンソース・ソフトウェアとして開発が行われています。その中のWikiで、OpenJFX 9の新機能について触れられています。以下、そのリストを全文引用します。

Milestones

TBD

JEPs

  • 8076423: JEP 253: Prepare JavaFX UI Controls & CSS APIs for Modularization
  •  8043352: JEP 257: Update JavaFX/Media to Newer Version of GStreamer

Minor enhancements:

  • Public API to replace heavily-used internal API no longer accessible with Jigsaw
  • Better Hi-DPI support (with API control) on Mac, Windows, Linux

  • Update WebKit to newer version

これだけです。少ないですね。少ないので全部順にみてゆきましょう。

JEP 253というのは、具体的にはJavaFX 9で追加されたjavafx.scene.control.skinパッケージのことを指します。このパッケージには主要なコントロールのSkinが定義されていて、コントロールの見た目を細かくカスタマイズできるようになります。詳細はこちら。実は昨年のJavaFX Advent Calendarのエントリです。

nodamushi.hatenablog.com

JEP 257は、javafx.mediaモジュール(javafx.scene.mediaパッケージ)が内部で利用しているGStreamerというライブラリが新しくなっているのでアップデートしようというものです。それまではかなり古いバージョンのGStreamerを使用しており、バージョンアップに際して相応の修正が要求されたようです。

JEP 253/257 とも今年の3月には既に完成していて(開発自体は2015~2016年にかけて実施)、Java 9がリリースされるまで半年くらい温存されていたことになります。

続いて小さな変更です。

Public API to replace heavily-used internal API no longer accessible with Jigsaw は、Jigsawに備えて今まで多用されてきた内部API(内部APIが多用される時点であまりよろしくない気もしますが…)を公開APIで置き換えようというもの。これはJavaFX 9というよりJava 9全体で行われたことですね。

Better Hi-DPI support (with API control) on Mac, Windows, Linux はHiDPI対応が一通り完了したことを意味します。Macはかなり早い時期にHiDPI対応(Retina対応)が行われ、WindowsでもJava 8のUpdate 80以降でJavaFXのHiDPIがなされています。Java 9ではLinuxのGUIでもHiDPI対応となりました。同時期にJEP 263としてAWT & SwingのHiDPI対応もなされています。

最後の Update WebKit to newer version ですがWebViewが使用しているレンダリングエンジンWebKitのバージョンアップです。@khasunumaによると、実はJava 8の最新版(Update 152)と全く同じらしいですが。JEP 239としてJava 8 Update 60でのWebKitバージョンアップが行われていますが、さらに新しいバージョンアップか、それともJEP 239のことかは不明です。

WebKitとGStreamのバージョンアップについては一昨年のAdvent Calendarで青江 id:aoe-tk / @aoetk さんが言及されています。

aoe-tk.hatenablog.com

JavaFX 9の話題は昨年一昨年のAdvent Calendar でも見かけましたし、公式のWikiで変更点を見てもそれほどの量ではなく、Advent Calendarで紹介されたものの結局入らなかった機能がいくつもあります。JavaFX 9の新機能についてはもう語りつくされたと考えると、現時点ではブログに書くほどの話題はないのかもしれません。

こうした現状の中、ブログを書いてくださっている皆様、本当にありがとうございます。

まだブログを書いていらっしゃらない皆様、新機能でなくても、周りがあっと驚くものでなくても、JavaFXのコードを書かなかったとしても、JavaFXの話題であれば何でも構いませんので、ぜひご参加ください。

明日は平田あづみが再びミュージックプレーヤーの作成に挑むそうです。明後日はHTMLEditorコントロールについて私が書きます。

Java 8で作成したJavaFXアプリケーションをJava 9で動かす

このエントリは、JavaFX Advent Calendar 2017 の 4 日目です。昨日は @boochnich さんの「JavaFXとHiDPI」、明日は今のところ未定です。

昨年はわたしの誕生日枠で JavaFX の 3D 機能を使って小惑星のビューワを作成しました。

 

blog.yumix.net

 

今回はこのビューワをJava 9に対応させる方法について調べてみました。

方法1: Java 8 で作成したアプリケーションをそのまま動かす

javacの -source と -target がともに 1.8 (または 8) の場合、特に変更することはありません。互換性は維持されていますのでご安心を。JDK 9の新機能を利用できないデメリットはありますが、とりあえず何もしなくても動作するのはお手軽です。なお、JDK 8は -source / -target に 1.8 と 8 のどちらかでも指定できるようになっていますが、JDK 9 では 1.9 は指定できず 9 のみ使用できます。

方法2: Java 9 のモジュール機能を使用してプロジェクトを修正する

本来はこちらの方法が推奨なのでしょう。まずは、ソースファイルのレイアウトからご覧ください。

f:id:yumix_h:20171203025621p:plain

Java 8 までとの相違点は以下の通りです。

  • src ディレクトリ以下にパッケージをまとめるディレクトリ(この例ではasteroid)を作成する。Eclipseではパッケージ扱いされるのでなんかキモい。
  • 上記のフォルダにJavaのソースファイル等とmodule-info.javaファイルを格納する。

Eclipse Oxygen 1a組み込みのMavenはバージョンが古いのか、挙動不審な点が多々見られました。最新版のMavenをセットアップして、そちらを使うように設定したほうがよさそうです。

訂正:Eclipse 4.7.1aでは、Maven形式のプロジェクトではJava 9のJigsawは使えないようです。EclipseのJavaプロジェクトで作成して正常に動いたプログラムをMaven形式に変換した途端にjava.lang.module.FindExceptionで動作しなくなったので、原因はMavenにあるとみて間違いなさそうです。MavenというツールそのものがJava 9のJigsawに対応できていないのか、それともEclipseのMaven周りの問題なのかは、今のところ不明です。

さて、問題なのは module-info.java をどう書けばよいかです。Jigsawのメリットとして使わないモジュールを除外できるというのがあります(特にjavapackagerでネイティブ版を作成したときは効果大です)。でも、JDK 9のドキュメントにはモジュール間の依存関係は書いてあるのですが、JDK 8までのように「とりあえず全部」が難しくなっています。

JDK 9では何もしないとjava.baseというモジュールが使えます。残念ながらその中にJavaFXのモジュールは含まれていません。そこで、module-info.javaにJavaFXのモジュールを取り込むように設定する必要があります(そのため、JDK 9ではファイルのレイアウトが変わります)。

JavaFXのモジュール依存関係を見てみると、javafx.baseを起点としていくつかのスーパーセットが定義されています。そして一番広範囲に API をカバーしているモジュールは、javafx.fxml、javafx.media、javafx.web、javafx.swingの4種類です。つまりこの4モジュールを取り込めば、JDK 8とほぼ同じ範囲の API が使えるようになります。

とりあえず動けばいいや、と思う方は以下のように module-info.java を記述すればたぶん大丈夫です。

module asteroid {
    requires javafx.web;
requires javafx.fxml;
requires javafx.media;
requires javafx.swing;

モジュール名は、モジュールをまとめるディレクトリ名(今回の例ではasteroid)と一致するようにすればOK。 

モジュールについて感覚がつかめてきたら、取り込むモジュールをもっと範囲の狭いもの(javafx.controlsなど)に絞り、使わないものは除外すると、特にjavapackagerでJDKと一緒にパッケージしたときサイズが小さくなります。

結論: Eclipseの場合、普通のJavaプロジェクト(Maven形式でない)に限り、Java 9で動作させることができるようです。

JavaFX Advent Calendar 2017、もうすぐ始まります

わたしが毎年参加させていただいていた "JavaFX Advent Calendar"、今年はどなたも主宰される様子がなかったので、僭越ながらわたしが主催します。今までの感謝の気持ちが先走って、肝心のネタがまだ見つかっていないのですが。

皆さんぜひ、参加してみてください。

qiita.com

※ただし、平田あづみ(id:planet-az)は義務なのでよろしく。

※あと、@btnrouge (a.k.a. @khasunuma) はさっさと参加表明しなさい。