如何在JavaFX中获取舞台的窗口句柄(hWnd)?

6
我们正在Windows中构建一个JavaFX应用程序,希望能够对Windows 7/8任务栏上的应用程序外观进行一些操作。这需要修改名为“应用程序用户模型ID”的Windows变量。
我们已经通过使用JNA在Swing中成功实现了我们想要的功能,并希望在JavaFX中重复我们的解决方案。不幸的是,为了做到这一点,我们需要能够检索我们应用程序中每个窗口的hWnd(窗口句柄)。可以通过JNA Native.getWindowPointer()方法在Swing / AWT中完成此操作,该方法适用于java.awt.Window,但我无法找到一个很好的方法来处理javafx.stage.Window

有人知道如何获取 StagehWnd 吗?


你能否看一下这个功能请求,并告诉我它是否包含你感兴趣的内容?http://javafx-jira.kenai.com/browse/RT-24249 - Alexander Kirov
@Alexander:这似乎有正确的想法,但我怀疑他们会实现任何能让我们在任务栏上执行所需操作的东西(我们有一个需要使用多个不同图标显示在任务栏上的单个应用程序)。我认为我们确实需要hWnd。谢谢您提供的链接,我已经在那个RFE上添加了评论。 - Xanatos
1
如果您想增加该功能被修复的可能性,可以投票支持它。 - Alexander Kirov
6个回答

5

在项目中添加对JNA的依赖:

<dependency>
  <groupId>net.java.dev.jna</groupId>
  <artifactId>jna-platform</artifactId>
  <version>5.3.1</version>
</dependency>

然后给你的Stage定义一个独特的标题(例如此例中的"MyStage"),然后按如下方式获取Window ID:

WinDef.HWND hwnd = User32.INSTANCE.FindWindow(null, "MyStage");

long wid = Pointer.nativeValue(hwnd.getPointer());

无论使用哪个版本的JavaFX,在Windows上都可以运行此代码。

4
以下代码在我的Windows JavaFX 11上有效(仅在那里需要)。我没有在任何其他版本中进行测试。
它相当脆弱,但在我的情况下可管理,因为我将Java Runtime与应用程序捆绑在一起,所以我总是知道底层内容。
如果您使用Java 9模块,则还需要向您的调用模块打开包: --add-opens javafx.graphics/javafx.stage=com.example--add-opens javafx.graphics/com.sun.javafx.tk.quantum=com.example
package com.example;

import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.WinDef;
import javafx.stage.Stage;
import javafx.stage.Window;
import java.lang.reflect.Method;

public class FXWinUtil {

    public static WinDef.HWND getNativeHandleForStage(Stage stage) {
        try {
            final Method getPeer = Window.class.getDeclaredMethod("getPeer", 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");
            return null;
        }
    }
}

如果您正在使用JNA(如果您在进行类似此类的hackish操作,则很可能),您还可以从WinDef.HWND中受益。


3
这是一个JavaFX2版本(使用Stage而不是Window):

这里是JavaFX2版本的代码:

private static Pointer getWindowPointer(Stage stage) {
    try {
        TKStage tkStage = stage.impl_getPeer();
        Method getPlatformWindow = tkStage.getClass().getDeclaredMethod("getPlatformWindow" );
        getPlatformWindow.setAccessible(true);
        Object platformWindow = getPlatformWindow.invoke(tkStage);
        Method getNativeHandle = platformWindow.getClass().getMethod( "getNativeHandle" );
        getNativeHandle.setAccessible(true);
        Object nativeHandle = getNativeHandle.invoke(platformWindow);
        return new Pointer((Long) nativeHandle);
    } catch (Throwable e) {
        System.err.println("Error getting Window Pointer");
        return null;
    }
}

我认为这个在至少1.8_66版本中已经不再起作用了。"getPlatformWindow"在TKStage中似乎不再存在。 - robross0606
似乎已经移动到WindowStage类。有人知道如何到达那里吗? - robross0606
窗口阶段(WindowStage)是TKStage的子类。该方法通过反射找到,但在调用该方法时出现了InvocationException异常。 - robross0606

2
以下方法展示了如何获取JavaFX Stage(或Window)的本地窗口句柄(hWnd),并将其存储在JNA Pointer对象中:
private static Pointer getWindowPointer(javafx.stage.Window window) {
    Pointer retval = null;
    try {
        Method getPeer = window.getClass().getMethod("impl_getPeer");
        final Object tkStage = getPeer.invoke(window);
        Method getPlatformWindow = tkStage.getClass().getDeclaredMethod("getPlatformWindow");
        getPlatformWindow.setAccessible(true);
        final Object platformWindow = getPlatformWindow.invoke(tkStage);
        Method getNativeHandle = platformWindow.getClass().getMethod("getNativeHandle");
        retval = new Pointer((Long) getNativeHandle.invoke(platformWindow));
    } catch (Throwable t) {
        System.err.println("Error getting Window Pointer");
        t.printStackTrace();
    }
    return retval;
}

这个解决方案是脆弱和通常不可取的,因为它使用反射来访问一堆私有方法。但它完成了工作。希望 Oracle 最终会直接提供对原生窗口句柄的访问,这样我们就不必这样做了。
当然,这段代码只在运行于 MS Windows 时有效。此外,我只在 JavaFX 8 的早期版本中尝试过它(但我怀疑它在 JavaFX 2 上也可以正常工作。编辑:看起来它在 JavaFX 2 中不起作用。)

1
这里是JavaFX 2用户。我使用的是Windows 7,jdk1.7.0_21版本。m.invoke(window)返回值为null。解决方案不起作用。 - mre
添加了一个JavaFX2版本作为单独的答案。顺便说一下。 - Craig Day
1
实际上,这已经是JavaFX2版本了。Window是Stage的超类。 - Xanatos

1
com.sun.glass.ui.Window.getWindows.get(0).getNativeWindow

//

com.sun.glass.ui.Window.getFocusedWindow.getNativeWindow

这对我很有用。没有反射,没有额外的依赖,只是一个简单的调用方法。 - Nohus

1

JavaFX 16 的解决方案,使用 Kotlin 编写(仅使用反射)

fun getPointer(scene: Scene): Long {
    val tkStage = SceneHelper.getPeer(scene)

    val windowStage = tkStage.javaClass.getDeclaredMethod("getWindowStage")
        .apply { isAccessible = true }
        .invoke(tkStage)

    val platformWindow = windowStage.javaClass.getDeclaredMethod("getPlatformWindow")
        .apply { isAccessible = true }
        .invoke(windowStage)
    
    // Use fields 'ptr' and 'delegatePtr' instead of getNativeHandle() to avoid Platform.runLater
    val ptr = Window::class.java.getDeclaredField("ptr")
        .apply { isAccessible = true }[platformWindow] as Long

    val delegatePtr = Window::class.java.getDeclaredField("delegatePtr")
        .apply { isAccessible = true }[platformWindow] as Long

    return if (delegatePtr != 0L) delegatePtr else ptr
}


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接