javajavafxtabletmulti-touch

JavaFX no TouchEvents


On my (Windows) Surface Go 2 tablet I'm not able to retrieve any (multitouch) TouchEvents. I tried it with several Java/FX versions. Even with JavaFX 17 (and older versions) no TouchEvents events are generated only mouse events. Is there some sort of configuration parameter needed?

package fx;

import javafx.application.Application;
import javafx.collections.ListChangeListener;
import javafx.scene.Scene;
import javafx.scene.control.ComboBox;
import javafx.scene.input.MouseEvent;
import javafx.scene.input.TouchEvent;
import javafx.scene.layout.Pane;
import javafx.stage.PopupWindow;
import javafx.stage.Stage;
import javafx.stage.Window;

public class test {

    public static void main(String[] args) {
        System.setProperty("com.sun.javafx.touch", "true");
        System.setProperty("com.sun.javafx.isEmbedded", "true");   
        Application.launch(JFXApp.class, args);
    }

    public static class JFXApp extends Application implements ListChangeListener<Window> {

        @Override
        public void start(Stage primaryStage) {
//            primaryStage.addEventFilter(TouchEvent.ANY, e -> System.out.println("touch event: " + e.getEventType()));
//            primaryStage.addEventFilter(MouseEvent.ANY, e -> System.out.println("mouse event: " + e.getEventType()));
            final ComboBox<String> comboBox = new ComboBox<>();
            comboBox.getItems().addAll("Test1", "Test2", "Test3");
            Scene scene = new Scene(new Pane(comboBox));
            scene.addEventFilter(TouchEvent.ANY, e -> System.out.println("scene touch event: " + e.getEventType()));
            scene.addEventFilter(MouseEvent.ANY, e -> System.out.println("scene mouse event: " + e.getEventType()));
            primaryStage.setScene(scene);
            primaryStage.setWidth(800);
            primaryStage.setHeight(800);
            primaryStage.show();
            Window.getWindows().addListener(this);
        }

        @Override
        public void onChanged(Change<? extends Window> c) {
            if (!c.next()) return;
            for (Window w : c.getAddedSubList()) {
                if (w instanceof PopupWindow) {
                    w.addEventFilter(TouchEvent.ANY,
                            e -> System.out.println("touch event (PopupWindow): " + e.getEventType()));
                    w.addEventFilter(MouseEvent.ANY,
                            e -> System.out.println("mouse event (PopupWindow): " + e.getEventType()));
                    Window.getWindows().removeListener(this);
                }
            }
        }
    }
}

Solution

  • I get TouchEvents on Java/JavaFX 16 using a Microsoft SurfaceBook 2 laptop running Windows.

    Sample source

    import javafx.application.Application;
    import javafx.scene.Scene;
    import javafx.scene.input.TouchEvent;
    import javafx.scene.layout.Pane;
    import javafx.stage.Stage;
    
    public class TouchTest extends Application {
    
        public static void main(String[] args) {
            launch(args);
        }
    
        @Override
        public void start(Stage stage) {
            System.out.println("java.version: " + System.getProperty("java.version"));
            System.out.println("javafx.runtime.version: " + System.getProperties().get("javafx.runtime.version"));
    
            Scene scene = new Scene(new Pane(), 800, 800);
            scene.addEventFilter(TouchEvent.ANY, System.out::println);
    
            stage.setScene(scene);
            stage.show();
        }
    }
    

    Execution instructions

    Using Java 16 and JavaFX 16, run the sample program and touch the empty pane displayed (by touching with your finger to the the touchscreen, not using the trackpad and pressing it).

    Program output

    C:\Users\send2\.jdks\temurin-16.0.2\bin\java.exe "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2019.3.2\lib\idea_rt.jar=56576:C:\Program Files\JetBrains\IntelliJ IDEA Community Edition 2019.3.2\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\send2\.m2\repository\org\openjfx\javafx-controls\16\javafx-controls-16.jar;C:\Users\send2\.m2\repository\org\openjfx\javafx-graphics\16\javafx-graphics-16.jar;C:\Users\send2\.m2\repository\org\openjfx\javafx-base\16\javafx-base-16.jar;C:\Users\send2\.m2\repository\org\openjfx\javafx-fxml\16\javafx-fxml-16.jar -p C:\Users\send2\.m2\repository\org\openjfx\javafx-base\16\javafx-base-16-win.jar;C:\Users\send2\.m2\repository\org\openjfx\javafx-graphics\16\javafx-graphics-16-win.jar;C:\dev\fxdemo\target\classes;C:\Users\send2\.m2\repository\org\openjfx\javafx-controls\16\javafx-controls-16-win.jar;C:\Users\send2\.m2\repository\org\openjfx\javafx-fxml\16\javafx-fxml-16-win.jar -m org.jewelsea.fxdemo/org.jewelsea.fxdemo.TouchTest
    java.version: 16.0.2
    javafx.runtime.version: 16+8
    TouchEvent [source = javafx.scene.Scene@3968dc9e, target = Pane@295c8184[styleClass=root], eventType = TOUCH_PRESSED, consumed = false, touchCount = 1, eventSetId = 1, touchPoint = TouchPoint [state = PRESSED, id = 1, target = Pane@295c8184[styleClass=root], x = 394.5, y = 330.5, z = 0.0, pickResult = PickResult [node = Pane@295c8184[styleClass=root], point = Point3D [x = 394.5, y = 330.5, z = 0.0], distance = 1492.820323027551]]
    TouchEvent [source = javafx.scene.Scene@3968dc9e, target = Pane@295c8184[styleClass=root], eventType = TOUCH_MOVED, consumed = false, touchCount = 1, eventSetId = 2, touchPoint = TouchPoint [state = MOVED, id = 1, target = Pane@295c8184[styleClass=root], x = 394.0, y = 331.0, z = 0.0, pickResult = PickResult [node = Pane@295c8184[styleClass=root], point = Point3D [x = 394.0, y = 331.0, z = 0.0], distance = 1492.820323027551]]
    TouchEvent [source = javafx.scene.Scene@3968dc9e, target = Pane@295c8184[styleClass=root], eventType = TOUCH_STATIONARY, consumed = false, touchCount = 1, eventSetId = 3, touchPoint = TouchPoint [state = STATIONARY, id = 1, target = Pane@295c8184[styleClass=root], x = 394.0, y = 331.0, z = 0.0, pickResult = PickResult [node = Pane@295c8184[styleClass=root], point = Point3D [x = 394.0, y = 331.0, z = 0.0], distance = 1492.820323027551]]
    TouchEvent [source = javafx.scene.Scene@3968dc9e, target = Pane@295c8184[styleClass=root], eventType = TOUCH_STATIONARY, consumed = false, touchCount = 1, eventSetId = 4, touchPoint = TouchPoint [state = STATIONARY, id = 1, target = Pane@295c8184[styleClass=root], x = 394.0, y = 331.0, z = 0.0, pickResult = PickResult [node = Pane@295c8184[styleClass=root], point = Point3D [x = 394.0, y = 331.0, z = 0.0], distance = 1492.820323027551]]
    TouchEvent [source = javafx.scene.Scene@3968dc9e, target = Pane@295c8184[styleClass=root], eventType = TOUCH_RELEASED, consumed = false, touchCount = 1, eventSetId = 5, touchPoint = TouchPoint [state = RELEASED, id = 1, target = Pane@295c8184[styleClass=root], x = 394.0, y = 331.0, z = 0.0, pickResult = PickResult [node = Pane@295c8184[styleClass=root], point = Point3D [x = 394.0, y = 331.0, z = 0.0], distance = 1492.820323027551]]
    

    The execution line is just provided for informational purposes, it was auto-generated by my IDE (Intellij Idea) when I used its internal run function to start the app. I am sure it would work fine outside of the IDE with a different execution command, but using the same JRE/JavaFX version.

    Example worked on JavaFX 16, not 17.0.0.1

    If you use the current version of JavaFX 17, the above example will not receive touch events (they are mapped to mouse events), on my setup of a Surface Book 2 running Windows. This is explained in Jose's comment on the question:

    Right after the release of JavaFX 16, this issue JDK-8249737 was fixed. It changed the way touch events were treated on Windows: not all events are direct events. Before this fix, touch events reported to scene, but now indirect events bail out: link, and treated as mouse events.

    This also goes (mostly) for the old Oracle TouchEvent tutorial sample code found here:

    The multi-touch folder dragging portion of that code works on my setup for Java 16.

    The dragging a ball to a box portion of that code did not work on Java 16 for me. It seemed to register the initial touch but I could not work out a touch or a touch and drag sequence which got the ball to another box, so it appeared broken.

    For Java 17 nothing in the touch event sample responded to touch events (or mouse events).

    FAQ

    for MacOS it would be nice that the Trackpad event also generate touch events.

    Using a Mac trackpad does not generate touch events (tested on JavaFX 17). Unlike the windows touch handling, I don't think this Mac code was updated for JavaFX 17, so it may have always been that way. I don't have a touch screen to try it out.

    For the iOS implementation it appears from examining the code that it would generate touch events when you touch the iOS device's screen (but I haven't got a setup to run JavaFX iOS to test it).

    It seems that there are multiple issues.

    Yes, I agree.

    I also think that some of the issues are platform and device-specific, and could end up being a bit nuanced and not entirely straightforward.

    The info I provided is pretty much the limit of my knowledge on this subject, so I can't offer much more help, unfortunately.

    When I test it with bell-sw or Azul Full-JDK 16, it doesn't work

    It is kind of strange that it works with the gluon JavaFX 16 distribution, but not azul or bell-sw bundled distributions, as one would think they would have the same functionality, but that is about as much as could say about that.

    I guess the makers of those bundled JDK/JavaFX distributions could be contacted or an issue report filed against them for this. But then, as the functionality changed for Java 17, I am not sure it makes a lot of difference going forward, at least currently.

    When I use Oracle Java 17 and use the JavaFX 16 download from gluonhq.com/products/javafx it does work! But JavaFX 17 doesn't work in any case.

    Yep, that is the same result as I got using the modular JavaFX versions sourced from Maven central.

    or must the touchevents be retrieved in a different way?

    I don't think there is another way to retrieve them short of hacking the JavaFX core. The implementation is in the JavaFX library code and not configurable via public API.

    For some platforms (e.g. iOS) it looks like the TouchEvents are generated and routed through the standard event filters.

    But for other platforms, there is specific code in there to ignore the TouchEvents under certain scenarios. For the 17.0.0.1 build, at least, TouchEvents are being deliberately ignored when detected in the JavaFX framework. They are not routed to the user JavaFX application code when a touch screen on a Windows OS device is touched with your finger. Note, under such scenarios, the touches are treated instead as MouseEvents and actioned accordingly (AFAICT).

    So, you would need to customize and change the core JavaFX code to get the touch events on your Windows OS platform, either by hacking the JavaFX library code yourself or having a change integrated into the library for a future JavaFX version.

    Thoughts on the JavaFX TouchEvent implementation (and changing it)

    Perhaps the issue has already been thoroughly considered and the platform code in JavaFX 17.0.0.1 is working as designed. Perhaps the thought is that the current behavior, while not ideal is a reasonable compromise. Perhaps the outcome could be a documentation update to better describe the TouchEvent behaviour rather than a functional update to change it.

    But IMO, from current behavior, there appear to be problems with the design and implementation of the TouchEvent processing feature in JavaFX when it is running on certain platforms (such as Windows OS). I do not know exactly how to categorize or address those problems.

    If you wish to work with the JavaFX developers on this, I advise that you join the openjfx-dev mailing list and describe the issue there (linking back to this question) and start a discussion on what could be done about it.