我一直想开发一款在屏幕中央显示十字准线的JavaFX应用程序,但每当我在ImageView上悬停时,我就无法执行后台任务,比如它会阻止我的鼠标事件.

我试过用Node#setMouseTransparent,但不太管用,Scene.setFill(null)也是一样

这是我现在拥有的代码:

    private void setStageProperties() {
        Screen screen = Screen.getPrimary();
        Rectangle2D bounds = screen.getBounds();

        stage.setWidth(bounds.getWidth());
        stage.setHeight(bounds.getHeight());

        Scene scene = new Scene(this);
        scene.setFill(null);
        stage.setScene(scene);
        stage.setAlwaysOnTop(true);

        this.primary = new Stage();
        primary.initStyle(StageStyle.UTILITY);
        primary.setOpacity(0);
        primary.setHeight(0);
        primary.setWidth(0);
        primary.show();

        stage.initOwner(primary);
        stage.initStyle(StageStyle.TRANSPARENT);


        double centerX = bounds.getMinX() + bounds.getWidth() / 2;
        double centerY = bounds.getMinY() + bounds.getHeight() / 2;

        stage.setX(centerX - stage.getWidth() / 2);
        stage.setY(centerY - stage.getHeight() / 2);

    }
    public CrosshairScene() {
        this.stage = new Stage();
        this.crosshairImage = new ImageView("crosshair.png");
        this.crosshairImage.setPickOnBounds(false);
        this.setMouseTransparent(true);
        this.setCenter(crosshairImage);
        this.setStageProperties();
        this.setStyle("-fx-background-color: null;");
    }

运行配置:

run config

--add-opens javafx.graphics/javafx.stage=com.example.demo --add-opens javafx.graphics/com.sun.javafx.tk.quantum=com.example.demo

推荐答案

node mouseTransparent property只是使 node 鼠标在JavaFX应用程序的上下文中是透明的,与JavaFX应用程序和窗口系统的其余部分无关.要做到这一点,需要在本机窗口系统中更改窗口样式.

Windows 11的解决方案

以下是一个仅适用于Windows的解决方案,它基于以下项目的 idea :

使用VM参数运行:

--add-opens javafx.graphics/javafx.stage=com.example.demo --add-opens javafx.graphics/com.sun.javafx.tk.quantum=com.example.demo 

src/main/java/module-info.java

module com.example.demo {
    requires javafx.controls;
    requires com.sun.jna;
    requires com.sun.jna.platform;

    exports com.example.demo;
}

src/main/java/com/example/demo/TransparentApplication.java

package com.example.demo;

import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.*;
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.scene.shape.SVGPath;
import javafx.stage.*;

import java.lang.reflect.Method;

public class TransparentApplication extends Application {
    private static final String CROSSHAIR_SVG_PATH =
            """
            M 14,8 A 6,6 0 0 1 8,14 6,6 0 0 1 2,8 6,6 0 0 1 8,2 6,6 0 0 1 14,8 Z M 8 0 L 8 6.5 M 0 8 L 6.5 8 M 8 9.5 L 8 16 M 9.5 8 L 16 8
            """;

    @Override
    public void start(Stage stage) {
        StackPane layout = new StackPane(
                new Group(
                        createCrosshair()
                )
        );
        layout.setBackground(Background.fill(Color.TRANSPARENT));
        layout.setMouseTransparent(true);

        Scene scene = new Scene(layout, Color.TRANSPARENT);

        stage.initStyle(StageStyle.TRANSPARENT);
        stage.setAlwaysOnTop(true);
        stage.setScene(scene);
        stage.show();

        makeMouseTransparent(stage);
    }

    private static Node createCrosshair() {
        SVGPath path = new SVGPath();
        path.setContent(CROSSHAIR_SVG_PATH);
        path.setFill(Color.TRANSPARENT);
        path.setStroke(
                Color.BLUEVIOLET.deriveColor(
                        0, 1, 1, .6
                )
        );
        path.setScaleX(10);
        path.setScaleY(10);

        return path;
    }

    private static void makeMouseTransparent(Stage stage) {
        WinDef.HWND hwnd = getNativeHandleForStage(stage);
        int wl = User32.INSTANCE.GetWindowLong(hwnd, WinUser.GWL_EXSTYLE);
        wl = wl | WinUser.WS_EX_LAYERED | WinUser.WS_EX_TRANSPARENT;
        User32.INSTANCE.SetWindowLong(hwnd, WinUser.GWL_EXSTYLE, wl);
    }

    private static WinDef.HWND getNativeHandleForStage(Stage stage) {
        try {
            final Method getPeer = Window.class.getDeclaredMethod("getPeer", (Class<?>[]) null);
            getPeer.setAccessible(true);
            final Object tkStage = getPeer.invoke(stage);
            final Method getRawHandle = tkStage.getClass().getMethod("getRawHandle");
            getRawHandle.setAccessible(true);
            final Pointer pointer = new Pointer((Long) getRawHandle.invoke(tkStage));
            return new WinDef.HWND(pointer);
        } catch (Exception ex) {
            System.err.println("Unable to determine native handle for window");
            ex.printStackTrace();
            return null;
        }
    }

    public static void main(String[] args) {
        launch();
    }
}

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.example.demo</groupId>
    <artifactId>TransparentApp</artifactId>
    <version>1.0-SNAPSHOT</version>
    <name>TransparentApp</name>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>net.java.dev.jna</groupId>
            <artifactId>jna-platform</artifactId>
            <version>5.14.0</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>21.0.1</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    <source>21</source>
                    <target>21</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>

执行命令

从项目根目录的命令行运行的命令示例.为Windows 11和JDK 21提供,并假设您已经运行了mvn clean install来构建应用程序.

set JAVA_HOME=%userprofile%\.jdks\openjdk-21.0.1
set PATH=%PATH%;%JAVA_HOME%\bin
set M2=%userprofile%\.m2\repository

java --add-opens javafx.graphics/javafx.stage=com.example.demo --add-opens javafx.graphics/com.sun.javafx.tk.quantum=com.example.demo -p %M2%\net\java\dev\jna\jna\5.14.0\jna-5.14.0.jar;%M2%\org\openjfx\javafx-base\21.0.1\javafx-base-21.0.1-win.jar;%M2%\net\java\dev\jna\jna-platform\5.14.0\jna-platform-5.14.0.jar;%M2%\org\openjfx\javafx-graphics\21.0.1\javafx-graphics-21.0.1-win.jar;%M2%\org\openjfx\javafx-controls\21.0.1\javafx-controls-21.0.1-win.jar;target\classes -m com.example.demo/com.example.demo.TransparentApplication

这些命令仅用于测试目的.通常,您可以在IDE运行配置中配置VM参数.或者,使用jlinkjpackage(可能通过Maven或Gradle的构建工具插件)链接和打包应用程序,并在应用程序打包中包含启动脚本中指定的VM参数,然后通过双击即可运行安装的应用程序.

从IntelliJ IDEA中跑出来

提供虚拟机(VM)参数not program arguments.需要向before要运行的类提供VM参数. Select Modify options | Add VM options,然后在显示"VM Options"的框中添加VM参数,如以下答案所示:

替代解决方案

此建议不会 destruct 模块化,并依赖于从应用程序代码访问JNA(尽管我没有try 过).

除非您更改窗口设置,否则显示JavaFX内容的窗口将拦截鼠标操作.也许你可以做一些棘手的事情,比如捕获鼠标输入,然后暂时隐藏stage,并使用Robot触发鼠标动作,也许与一些Platform.runLater调用结合,但这有点黑客.

Additional info

正如斯劳在 comments 中指出的那样:

因为我发誓让stage变得透明/没有装饰,让场景变得透明,让鼠标下的所有 node 要么是鼠标透明的,要么是没有背景/填充的(甚至是部分,例如,带有透明像素的图像),这些都允许你与过go stage背后的任何东西互动.至少,我相信它适用于Windows 10(尽管不适用于MacOS等其他平台).

我判断了Windows 11 Pro和JavaFX 21.0.1. 它的工作原理基本上和Slaw记忆中的一样. 如果你点击了stage的透明区域,鼠标会与stage下的窗口进行交互.

但是,如果您单击了stage的非透明区域,则鼠标操作不会在stage下方的窗口中注册,除非使用了此答案中提供的JNA代码.

常见问题解答

错误:

module javafx.graphics does not "opens javafx.stage" to module com.example.demo

这意味着你的VM参数是错误的.

当您运行应用程序时,未采用此VM参数:

--add-opens javafx.graphics/javafx.stage=com.example.demo 

在本例中,com.example.demo是模块名称,而不是包名称.除非您的模块也被命名为com.example.demo,否则它将无法工作.有关详细信息,请参阅java man page for the --add-opens switch.

Java相关问答推荐

Cucumber TestNG Assert失败,出现java. lang. Numbercycle异常

Springdoc Whitelabel Error Page with Spring V3

屏蔽字母数字代码的Java正则表达式

使用JdkClientHttpRequestFactory通过Spring RestClient和Wiemock读取时达到EOF

是否在允许数组元素为空时阻止 idea 为空性警告?

Spring Boot Maven包

Java Mooc.fi Part 12_01.Hideout -返回和删除方法

如何在JavaFX中处理多个按钮

是否为计划任务补偿系统睡眠?

在Ubuntu 23.10上使用mp3创建JavaFX MediaPlayer时出错

Regex以查找不包含捕获组的行

在ECLIPSE上的M1 Pro上运行JavaFX的问题

在Java中使用StorageReference将数据从Firebase存储添加到数组列表

如何在右击时 Select 新行?

使用@ExceptionHandler的GlobalExceptionHandler还是来自服务器的REST应答的ResponseEntity?

整数->;双取消框,但双->;int不';t开箱.为什么?

为什么Instant没有从UTC转换为PostgreSQL的时区?

如何使用外部函数从Java中获取C++ struct 的返回值&;内存API

java.util.LinkedList()是如何成为MutableList的实例的?

Spring Integration SFTP 连接失败 - 无法协商 kex 算法的密钥交换