このエントリは、JavaFX Advent Calendar 2017の24日目です。昨日は櫻庭 (@skrb / id:skrb) さんの「ControlsFX 準備編」、明日は櫻庭さんが今年のJavaFX Advent Calendarを締めてくださいます。
今年のわたしは、HTMLEditorを使ったWebオーサリングツールが作れないものかと試行錯誤していました。
最初のエントリはこちら。何も考えずにHTMLEditorで何でもできる?と思って挑戦して挫折したものです。
次に、青江さんからJDK 9でHTMLEditorSkinが公開APIになったから細かい制御ができるよと伺って試してみた結果です。確かにHTMLEditorSkinの存在自体は偉大なのですが、現状機能不足でちょっと力不足だった、というのがこちらのエントリです。
最終手段、リフレクションでHTMLEditorSkinの内部を直接叩こうという試みが今回のエントリになります。なお、リフレクションを使うと言った途端、@khasnuma (@btnrouge) が自作のリフレクション・ユーティリティを勧めてきたのですが…
(ごめんなさい、元ネタよく知らないの…)
方法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にチャレンジしたときのほうがおもしろかったかな?
一応、今回までのところをGitHubにアップロードしておきます。現状、これ以上の発展は望めそうにないですけど、ご参考までに。
追補
昨日の櫻庭さんのエントリにもありましたが、Java 9でリフレクションを使用する場合には、VM引数に --illegal-access=warn を付けましょう。わたしも1つ、勉強になりました。