torutkのブログ

ソフトウェア・エンジニアのブログ

JavaFXのSwingNodeがJavaFXのイベントをSwingのイベントに変換しているあたり(続々)

昨日の日記(JavaFXのSwingNodeがJavaFXのイベントをSwingのイベントに変換しているあたり(続) - torutkのブログ)で、Windows OS上ではタッチパネル操作を扱うアプリケーションはRegisterTouchWindow APIを呼ぶこと、Javaでは、JavaFXのネイティブコードにこのAPIを呼ぶ実装が存在することを記載しました。

ところが、JavaFXにおいて、タッチパネル操作を考慮せずマウスイベント処理だけを実装しているGUIコントロールをタッチパネルがある環境で実行すると、タッチ操作とマウス操作を区別することができず、意図しない挙動をすることがあります。特に、SwingコンポーネントJavaFXに貼ると、顕著に発生します。

この問題を回避する方法の1つとして、Windows OSにJavaFXアプリケーションをタッチパネル操作を扱わないレガシーアプリケーションとして認識させるということが考えられます。具体的には、UnregisterTouchWindow APIを呼ぶという方法です。

UnregisterTouchWindowのAPI仕様をMSDNで調べると次の形式でした。

BOOL WINAPI UnregisterTouchWindow(
  __in  HWND hWnd
);

ライブラリは、User32.dllに含まれます。

https://msdn.microsoft.com/ja-jp/library/windows/desktop/Dd317335.aspx

JavaからWindows OSのAPIを呼ぶことになります。現時点でJavaからネイティブコードを呼び出す主要な方法には、Java標準のJNI、オープンソースのライブラリJNA、JNRと3つがあります。JNIはC/C++開発が必要になります。JNAとJNRはJava開発だけで済みます。今回はJNAを使って実現してみました。JNAについては、以前の日記(id:torutk:20121020)で軽く紹介をしています。

    interface User32 extends StdCallLibrary {
        User32 INSTANCE = (User32) Native.loadLibrary("user32", User32.class);
        boolean UnregisterTouchWindow(HWND hwnd);
    }

JNAを使って、UnregisterTouchWindow を呼ぶコードを記述します。ここでの課題は、Javaアプリケーションでウィンドウのハンドル(HWND)をどう取り出すかです。
探してみたところ、ハック的ですが取得する方法がありました。

http://www16118uj.sakura.ne.jp/?p=1172

    private static Pointer getWindowPointer(Window window) {
        Pointer hwnd = null;
        try {
            TKStage tkStage = window.impl_getPeer();
            Method m = tkStage.getClass().getDeclaredMethod("getPlatformWindow");
            m.setAccessible(true);
            final Object platformWindow = m.invoke(tkStage);
            m = platformWindow.getClass().getMethod("getNativeHandle");
            hwnd = new Pointer((Long)m.invoke(platformWindow));
        } catch (Throwable e) {
            logger.log(Level.SEVERE, "Error. failed to get window handle", e);
        }
        return hwnd;
    }

StageクラスはWindowクラスを継承しているので、Stageを指定してHWNDを取得します。取得のタイミングは、Stageの表示完了後とします。

 stage.setOnShown(e -> {
            HWND hwnd = new HWND(getWindowPointer(stage));
            User32 user32 = User32.INSTANCE;
            user32.UnregisterTouchWindow(hwnd);
        });

一応これで、JavaFXアプリケーションをタッチパネル操作非対応とWindows OSに指定し、従来の操作イベントを受け取るようになりした。