Drag and Drop on JavaFX Part 6 (FileDroppable)
前々回の 『 Drag and Drop on JavaFX Part 5 (FileDroppable) 』の続き...
今回は 前々回 紹介した FileDroppable.fx のちょっとした解説 + α です。
- ファイルをドロップできるのは Component にだけ...
- Node から取得できる Component は SwingScenePanel だけ... (たぶん...)
- SwingScenePanel は コアAPI では取得できない...
- Component に 設定できる DropTarget は 一つだけ...
- DropTarget に 設定できる DropTargetListener は一つだけ...
これらのポイントさえ把握しておけば...
前々回のサンプルに
ブラウザ等から画像をドロップする機能を追加するのもとっても簡単です。
以下のサンプルは...
- ローカルディスク上の画像ファイル
- ブラウザ、iPhoto (他のアプリも可?) のイメージ (今回追加)
※ ローカルファイルにアクセスするので、セキュリティの警告がでます。
では、以下 簡単な解説です。1. ファイルをドロップできるのは Component にだけ...
残念ながら、JavaFX 自体には ファイルの
Drag and Drop の機能は含まれていませんが...
以下の Java で提供されているクラスを
使えば、JavaFX でも簡単に
ファイルの Drag and Drop を実現できる
のです。
Drag and Drop の詳細については こちら を
参照してみてください。
ところが ここで一つ問題が...
ファイルは Component にしかドロップできないのです。
JavaFX の Node は Component ではないので、
そのままではファイルをドロップすることが
できません。
javafx.ext.swing パッケージのクラスであれば、Component の
サブクラスの JComponent を取得するための
API があるので良いのですが...
やはり、どの Node でも ファイルをドロップしたくなるのが、
人情というものでしょう。
なんとか Node から Component を取得できないだろうか?
2. Node から取得できる Component は SwingScenePanel だけ...
色々調べていたら、どんな Node にも関連づけられている
Component が一つだけありました。
それは SwingScenePanel というクラスです。
SwingScenePanel は 以下のような階層を持つクラスです。
java.awt.Component +- java.awt.Container +- javax.swing.JComponent +- com.sun.scenario.scenegraph.JSGPanel +- com.sun.javafx.tk.swing.SwingScene$SwingScenePanel
SwingScenePanel は JComponent のサブクラスでもあるので ファイルのドロップ以外の機能も 利用できそうですね。
では、SwingScenePanel はどのように取得すれば良いのでしょうか?
3. SwingScenePanel は コアAPI では取得できない...
上記のように SwingScenePanel は コアAPI の
クラスではありません。
もちろん、Node から取得するための API も
コアAPI では提供されていないのでが...
以下のようにリフレクションで
内部? API を利用すれば、
SwingScenePanel を取得できるのです。
function getPanel(node: Node) { var context = FXLocal.getContext(); // SGNode var _nodeClass = context.findClass("javafx.scene.Node"); var _getPGNode = _nodeClass.getFunction("impl_getPGNode"); var _sgNode = _getPGNode.invoke(context.mirrorOf(node)) as FXLocal.ObjectValue; // SwingScenePanel var _sgNodeClass = context.findClass("com.sun.scenario.scenegraph.SGNode"); var _getPanel = _sgNodeClass.getFunction("getPanel"); return (_getPanel.invoke(_sgNode) as FXLocal.ObjectValue).asObject(); }
上記のようにリフレクションを使わないと、
コンパイルエラーになってしまうのです。
もちろん、コンパイルさえ通れば問題なく、
実行できるのですが...
どうやら、内部 API のクラスは 実行時にしか
参照できないようです。
Component のサブクラスの SwingScenePanel
が取得できたら、
後は Java の API を利用
して簡単に実装できるはずなのですが...
また、ここでも問題が...
4. Component に 設定できる DropTarget は 一つだけ...
DropTarget は一つの Component に一つしか
設定できないのです。
実は Node から取得できる SwingScenePanel
は 名称からも推測できる通り Scene に関
するクラスのようで...
同じ Scene に属する Node から
取得できる SwingScenePanel は全て同一の
インスタンスなのです。
つまり、SwingScenePanel に設定した DropTarget 一つで
該当する全ての Node の処理を実装しなくてはならないという訳です。
5. DropTarget に 設定できる DropTargetListener は一つだけ...
DropTarget には addDropTargetListener(...) で DropTargetListener の実装を
追加することができるので...
Node 一つに対し DropTargetListener を一つずつ追加していこうと思ったのですが...
なんと addDropTargetListener(...) なのに 複数の DropTargetListener を追加すると...
TooManyListenersException がスローされるのです。なんで...
Javadoc によると そういう仕様ということ
なので、仕方なく自分で実装することに...
興味のある方は、『 Drag and Drop on JavaFX Part 5 (FileDroppable) 』
のソースを参照してみてください。