如何在JavaFX中创建MouseTransparent Stage

yjghlzjz  于 12个月前  发布在  Java
关注(0)|答案(1)|浏览(295)

我一直想制作一个在屏幕中间显示十字准线的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);

    }

个字符
运行配置:


的数据

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

0aydgbwb

0aydgbwb1#

node mouseTransparent属性只是让节点鼠标在JavaFX应用程序的上下文中是透明的,而不涉及JavaFX应用程序和窗口系统的其他部分。要做到这一点,你需要在本机窗口系统中更改窗口样式。

Windows 11解决方案

这是一个仅限Windows的解决方案,基于以下想法:

使用VM args运行:

--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)参数 * 而不是程序参数 *。VM参数需要在 * 要运行的类之前 * 提供。选择Modify options | Add VM options,然后在“VM选项”框中添加VM参数,如以下答案所示:

替代方案

这个建议并没有破坏模块化,并依赖于应用程序代码中的JNA访问(尽管我没有尝试过)。
除非你改变窗口设置,类似于上面为Windows定义的那样,否则显示JavaFX内容的窗口将拦截鼠标操作。也许你可以做一些棘手的事情,比如捕获鼠标输入,然后暂时隐藏舞台,并使用Robot触发鼠标操作,也许与一些Platform.runLater调用结合使用,但这有点黑客。

附加信息

正如Slaw在评论中指出的那样:
因为我发誓让舞台透明/无装饰,让场景透明,让鼠标下的所有节点都是鼠标透明或没有背景/填充(甚至部分,例如,透明像素的图像),让你可以与过去舞台后面的任何东西进行交互。至少,我相信它适用于Windows 10(尽管在其他平台上不适用,例如macOS)。
我用Windows 11 Pro和JavaFX 21.0.1检查了一下,它的工作原理和Slaw记忆中的差不多,如果你点击舞台的透明区域,鼠标会和舞台下的窗口进行交互。
但是,如果您单击舞台的非透明区域,则鼠标操作不会在舞台下的窗口中注册,除非使用本答案中提供的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,否则它将无法工作。有关--add-opens开关的更多信息,请参阅java手册页。

相关问题