JavaFXの画面にSwingで作ったコンポーネントを乗せることが可能です。そのときは、JavaFXのSwingNodeを置いて、その上にSwingのコンポーネントを乗せます。
(参考)OracleのJavaFXドキュメント「相互運用性 7. JavaFXアプリケーションへのSwingコンテンツの埋込み」
http://docs.oracle.com/javase/jp/8/javafx/interoperability-tutorial/embed-swing.htm
ここで気になるのはマウスやキー操作のイベントです。JavaFXの画面では、各コントロールはJavaFXのイベント(例:javafx.scene.input.MouseEvent)を取ることができます。一方、Swingのコンポーネントはjava.awtのイベント(例:java.awt.event.MouseEvent)を取ります。
SwingNodeのJavadoc(次のURL)を見ると、イベントについて次のように言及されています。
http://docs.oracle.com/javase/jp/8/javafx/api/javafx/embed/swing/SwingNode.html
すべての入力イベントとフォーカス・イベントは、開発者に対して透過的にJComponentインスタンスに転送されます。
JavaFXの入力イベント(クラス階層)は次のようになっています。
javafx.scene.input.InputEvent +-- ContextMenuEvent +-- DragEvent +-- GestureEvent | +-- RotateEvent | +-- ScrollEvent | +-- SwipeEvent | +-- ZoomEvent +-- InputMethodEvent +-- KeyEvent +-- MouseEvent +-- TouchEvent
SwingNodeの実装(JDKに添付されるjavafx-src.zipを解凍すると中にSwingNode.javaが入っています)を調べたところ、イベントについては次のように3種類の入力イベントについて処理をしています。
setEventHandler(MouseEvent.ANY, new SwingMouseEventHandler()); setEventHandler(KeyEvent.ANY, new SwingKeyEventHandler()); setEventHandler(ScrollEvent.SCROLL, new SwingScrollEventHandler());
ここを見ると、JavaFXの入力イベントのうち、一部(MouseEvent、KeyEvent、ScrollEvent)を扱っています。その他の入力イベントはSwingNode上のSwingコンポーネントには届かないと思われます。
次に、上述の対象イベントをどう処理しているのかを調べてみます。
setEventHandlerの2番目の引数に入れている各Handlerクラスは、SwingNodeの内部クラスとして実装されています。
public class SwingNode extends Node { : (中略) private class SwingMouseEventHandler implements EventHandler<MouseEvent> { : (中略) } private class SwingScrollEventHandler implements EventHandler<ScrollEvent> { : (中略) } private class SwingKeyEventHandler implements EventHandler<KeyEvent> { : (中略) } : (中略) }
SwingNode.SwingKeyEventHandler
- 空キーは何もしない
- 上下左右カーソルキー、TABキーはイベントを握りつぶし(フォーカスをはずさないようにする)
- JavaFXのイベントタイプをSwing(AWT)のイベントIDに変換
JavaFX EventType | Swing EventID |
---|---|
KEY_PRESSED | KEY_PRESSED |
KEY_RELEASED | KEY_RELEASED |
KEY_TYPED | KEY_TYPED |
それ以外 | RuntimeExceptionをスロー |
- java.awt.event.KeyEventを生成し、EventQueueにpostEvent
SwingNode.SwingMouseEventHandler
- JavaFXのイベントタイプをSwing(AWT)のイベントIDに変換
JavaFX EventType | Swing EventID |
---|---|
MOUSE_MOVED | MOUSE_MOVED |
MOUSE_PRESSED | MOUSE_PRESSED |
MOUSE_RELEASED | MOUSE_RELEASED |
MOUSE_CLICKED | MOUSE_CLICKED |
MOUSE_ENTERED | MOUUSE_ENTERED |
MOUSE_EXITED | MOUSE_EXITED |
MOUSE_DRAGGED | MOUSE_DRAGGED |
DRAG_DETECTED | 対応無し(無視) |
それ以外 | RuntimeExceptinをスロー |
- イベントを握りつぶし(consume)
- MouseButtonの情報を管理(MOUSE_PRESSED, MOUSE_RELEASED, MOUSE_DRAGED, MOUSE_CLICKEDの関連で)
- 属性情報を調整してjava.awt.event.MouseEventを生成し、EventQueueにpostEvent
SwingNode.SwingScrollEventHandler
- 修飾キーの変換(Altキー、Ctrlキー、Metaキー、Shiftキー)
- Shiftキーが押されておらず、かつdeltaYが0以外ならjava.awt.event.MouseWheelEventを生成
- Shiftキーが押されており、かつdeltaYが0以外ならjava.awt.event.MouseWheelEventを生成
- deltaXが0以外ならjava.awt.event.MouseWheelEventを生成
ScrollEventがくると、deltaXおよびdeltaYの値が0以外ならMouseWheelEventを発生させています。
もし、deltaXとdeltaYがともに0以外でShiftキーが押されていない状態では、2つのMouseWheelEventが発行されるように見えます。SwingNode.javaの該当コードを次に抜粋します。
@Override public void handle(ScrollEvent event) { JLightweightFrame frame = lwFrame; if (frame == null) { return; } int swingModifiers = SwingEvents.fxScrollModsToMouseWheelMods(event); final boolean isShift = (swingModifiers & InputEvent.SHIFT_DOWN_MASK) != 0; // Vertical scroll. if (!isShift && event.getDeltaY() != 0.0) { sendMouseWheelEvent(frame, event.getX(), event.getY(), swingModifiers, event.getDeltaY() / event.getMultiplierY()); } // Horizontal scroll or shirt+vertical scroll. final double delta = isShift && event.getDeltaY() != 0.0 ? event.getDeltaY() / event.getMultiplierY() : event.getDeltaX() / event.getMultiplierX(); if (delta != 0.0) { swingModifiers |= InputEvent.SHIFT_DOWN_MASK; sendMouseWheelEvent(frame, event.getX(), event.getY(), swingModifiers, delta); } }
タッチパネルを持つPCでのイベント
やっかいなのは、タッチパネル操作をおこなったときのMouseWheelEventの発生状況です。
Swingはもともとタッチパネルには対応していないのですが、このSwingNodeの実装では、タッチパネル上でジェスチャー(ピンチ、スワイプ、スクロール)を行った際に発生するScrollEventをことごとく拾ってMouseWheelEventに変換してSwingComponentに処理させています。
この結果、SwingNode上に貼ったSwingコンポーネントでMouseWheelEventを扱っていると、凄い事態になってしまいます。
SwingNode.SwingScrollEventHandlerの実装
この問題は、SwingNode特有の話ではなく、JavaFXコントロールにおいてMouseWheelを扱うものに共通の話となります。
この対応については、以下のWikiに記載しています。
JavaFXとアナログ時計 - ソフトウェアエンジニアリング - Torutk
しかし、上記Wikiページの対応も不完全です。
ScrollEventのJavadocに次の記載があります。
If scrolling inertia is active on the given platform, some SCROLL events with isInertia() returning true can come after SCROLL_FINISHED.
SCROLL_FINISHEDイベントが発生した後も、isInertia()がtrueを返すSCROLLイベントがタッチパネル操作で発生する可能性があります。
なので、isInertia()がtrueであればマウスホイール操作ではないと判断させるコードも追加する必要があります。
追記
@Yucchi_jp さんのブログで、マウスホイール操作によるSCROLLイベントか、タッチ操作によるSCROLLイベントかを判定する別な方法が記載されていました。
http://yucchi.jp/blog/?p=1750
if (sc.getTouchCount() == 0 && !sc.isInertia()) {
ScrollEventクラスのJavadocを見ると、getTouchCountメソッドに次の説明があります。
Gets number of touch points that caused this event. For non-touch source devices as mouse wheel and for inertia events after gesture finish it returns zero.
「マウスホイールなどの非タッチデバイスおよびジェスチャー終了後の慣性イベントについては、0を返却します。」とのことです。
SCROLL_STARTEDとSCROLL_FINISHEDで状態遷移を管理する必要がなく(状態遷移でずれる心配をしないで済む)、SCROLLイベントだけで判定できるので、この方法で判別するのがよさそうです。