I have an undecorated JavaFX Stage, and my own minimize, maximize & close buttons. But unfortunately clicking the taskbar icon in Windows 7 does not automatically minimize the stage - compared to the decorated behaviour.
Is there a way to minimize an undecorated stage with pure Java code, by clicking the taskbar icon? If not how can I do this with, say, JNA?
EDIT: OK, I've been trying to solve this with JNA, but having done next to none C/C++/JNA, I have a bit trouble setting this up. I'd be grateful if someone helped me to put the pieces together..
Here's my code so far:
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;
}
}
EDIT 2: I eventually got further on with this one, but as soon as I re-set the WNDPROC, my undecorated window didn't respond to any events.. I'm offering a bounty of 100 reputation for a self-contained example with a working solution. Windows (7+) only is OK, I do not even know how this behaves on other platforms.
EDIT 3: Well, I kind of gave up with this one.. I got everything set up correctly, and received the events, but had problems figuring out the correct event to listen for..
Since there's been some interest in the question, if anyone wants to attempt to continue with this, here's my final code (it hopefully should "work" out-of-box):
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);
}
}
}