【发布时间】:2015-01-14 08:44:51
【问题描述】:
我有一个未装饰的 JavaFX Stage,以及我自己的最小化、最大化和关闭按钮。但不幸的是,在 Windows 7 中单击任务栏图标并不会自动最小化阶段 - 与装饰行为相比。
有没有办法通过单击任务栏图标来最小化纯 Java 代码的未修饰阶段?如果不是,我该如何使用 JNA 来做到这一点?
编辑: 好的,我一直在尝试用 JNA 解决这个问题,但是几乎没有做过 C/C++/JNA,我在设置它时遇到了一些麻烦。如果有人帮助我将这些碎片拼凑在一起,我将不胜感激。
到目前为止,这是我的代码:
public final class Utils {
static {
if (PlatformUtil.isWin7OrLater()) {
Native.register("shell32");
Native.register("user32");
}
}
// Apparently, this is the event I am after
public static final int WM_ACTIVATEAPP = 0x1C;
public static void registerMinimizeHandler(Stage stage) {
// Hacky way to get a pointer to JavaFX Window
Pointer pointer = getWindowPointer(stage);
WinDef.HWND hwnd = new WinDef.HWND(pointer);
// Here's my minimize/activate handler
WinUser.WindowProc windowProc = new MinimizeHandler(stage);
Pointer magicPointer = ... set this to point to windowProc?
// This.. apparently, re-sets the WndProc? But how do I get the "magicPointer" that is "attached" to the windowProc?
User32.INSTANCE.SetWindowLong(hwnd, User32.GWL_WNDPROC, magicPointer);
}
}
private static class MinimizeHandler implements WinUser.WindowProc {
private Stage stage;
private MinimizeHandler(Stage stage) {
this.stage = stage;
}
@Override
public WinDef.LRESULT callback(WinDef.HWND hWnd, int uMsg, WinDef.WPARAM wParam, WinDef.LPARAM lParam) {
if (uMsg == WM_ACTIVATEAPP) {
System.out.println("ACTIVATE");
}
return User32.INSTANCE.DefWindowProc(hWnd, uMsg, wParam, lParam);
}
}
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;
}
}
编辑 2: 我最终在这个方面更进一步,但是一旦我重新设置 WNDPROC,我未装饰的窗口就没有响应任何事件。我提供了一个具有工作解决方案的独立示例的 100 声望奖励。仅限 Windows (7+) 可以,我什至不知道这在其他平台上的行为如何。
编辑 3: 好吧,我有点放弃了这个..我正确设置了所有内容,并接收了事件,但是在找出要监听的正确事件时遇到了问题..
由于有人对这个问题感兴趣,如果有人想尝试继续这个问题,这是我的最终代码(希望它应该“开箱即用”):
public final class Utils {
static interface ExtUser32 extends StdCallLibrary, User32 {
ExtUser32 INSTANCE = (ExtUser32) Native.loadLibrary(
"user32",
ExtUser32.class,
W32APIOptions.DEFAULT_OPTIONS);
WinDef.LRESULT CallWindowProcW(
Pointer lpWndProc,
Pointer hWnd,
int msg,
WinDef.WPARAM wParam,
WinDef.LPARAM lParam);
int SetWindowLong(HWND hWnd, int nIndex, com.sun.jna.Callback wndProc) throws LastErrorException;
}
// Some possible event types
public static final int WM_ACTIVATE = 0x0006;
public static final int WM_ACTIVATEAPP = 0x1C;
public static final int WM_NCACTIVATE = 0x0086;
public static void registerMinimizeHandler(Stage stage) {
Pointer pointer = getWindowPointer(stage);
WinDef.HWND hwnd = new WinDef.HWND(pointer);
long old = ExtUser32.INSTANCE.GetWindowLong(hwnd, User32.GWL_WNDPROC);
MinimizeHandler handler = new MinimizeHandler(stage, old);
ExtUser32.INSTANCE.SetWindowLong(hwnd, User32.GWL_WNDPROC, handler);
}
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;
}
}
private static class MinimizeHandler implements WinUser.WindowProc, StdCallLibrary.StdCallCallback {
private Pointer mPrevWndProc32;
private Stage stage;
private MinimizeHandler(Stage stage, long oldPtr) {
this.stage = stage;
mPrevWndProc32 = new Pointer(oldPtr);
// Set up an event pump to deliver messages for JavaFX to handle
Thread thread = new Thread() {
@Override
public void run() {
int result;
WinUser.MSG msg = new WinUser.MSG();
while ((result = User32.INSTANCE.GetMessage(msg, null, 0, 0)) != 0) {
if (result == -1) {
System.err.println("error in get message");
break;
}
else {
System.out.println("got message: " + result);
User32.INSTANCE.TranslateMessage(msg);
User32.INSTANCE.DispatchMessage(msg);
}
}
}
};
thread.start();
}
@Override
public WinDef.LRESULT callback(WinDef.HWND hWnd, int uMsg, WinDef.WPARAM wParam, WinDef.LPARAM lParam) {
if (uMsg == WM_ACTIVATEAPP) {
// Window deactivated (wParam == 0)... Here's where I got stuck and gave up,
// this is probably not the best event to listen to.. the app
// does indeed get iconified now by pressing the task-bar button, but it
// instantly restores afterwards..
if (wParam.intValue() == 0) {
stage.setIconified(true);
}
return new WinDef.LRESULT(0);
}
// Let JavaFX handle other events
return ExtUser32.INSTANCE.CallWindowProcW(
mPrevWndProc32,
hWnd.getPointer(),
uMsg,
wParam,
lParam);
}
}
}
【问题讨论】:
-
您可能需要启动事件泵。查看this example code 中的
GetMessage()循环。一旦您的 Java 代码运行事件泵,您的窗口和事件挂钩应该开始正确接收消息。 -
您还可以利用 JNA 的
Native.getWindowHandle()来获取本机窗口句柄,而不是您正在使用的基于反射的查找。不过,这取决于 JavaFX 的东西如何处理它的本地对等点。 -
@technomage 感谢您的 cmets。我创建了一个新线程并在其中启动了这个事件泵,现在窗口确实响应了事件!但是,当我单击我的应用程序的任务栏图标时,永远不会调用 CallWindowProcW。但是,它在许多其他事件上被调用:例如当我从自己的最大化按钮最大化应用程序时,我将通过回调打印消息。我如何实际捕获“任务栏点击事件”?
-
在我认为是最新版本的 JNA 中,没有 Native.getWindowHandle() 方法 - 只有 Native.getWindowPointer(),它适用于 AWT-windows。我不知道,但我怀疑 JavaFX 与 AWT 无关。由于我的名誉受损,我似乎无法再投票给 cmets 了 :(
-
对不起,我错了,事件确实被触发了!我认为我现在唯一要做的就是我需要弄清楚如何使用 JNA 读取 LPARAM 参数(无论是真还是假)。让我们看看..