[英]JavaFX Minimizing Undecorated Stage
我有一個未修飾的 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);
}
}
}
您只需設置適當的窗口樣式即可。 它適用於 XP,但在 Windows 7 32 位中應該沒問題。 我認為(但無法測試)如果您使用 64 位然后更改為 Ptr windows 函數,即。 獲取WindowLongPtr。
import com.sun.jna.Native;
import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.User32;
import com.sun.jna.platform.win32.WinDef.HWND;
import com.sun.jna.platform.win32.WinUser;
import static com.sun.jna.platform.win32.WinUser.GWL_STYLE;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.control.TextArea;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;
public class JNATest extends Application {
public static void main(String[] args) { launch(args); }
@Override
public void start(Stage stage) {
TextArea ta = new TextArea("output\n");
VBox root = new VBox(5,ta);
Scene scene = new Scene(root,800,200);
stage.setTitle("Find this window");
stage.setScene(scene);
stage.show();
//gets this window (stage)
long lhwnd = com.sun.glass.ui.Window.getWindows().get(0).getNativeWindow();
Pointer lpVoid = new Pointer(lhwnd);
//gets the foreground (focused) window
final User32 user32 = User32.INSTANCE;
char[] windowText = new char[512];
HWND hwnd = user32.GetForegroundWindow();
//see what the title is
user32.GetWindowText(hwnd, windowText, 512);
//user32.GetWindowText(new HWND(lpVoid), windowText, 512);//to use the hwnd from stage
String text=(Native.toString(windowText));
//see if it's the same pointer
ta.appendText("HWND java:" + lpVoid + " HWND user32:"+hwnd+" text:"+text+"\n");
//change the window style if it's the right title
if (text.equals(stage.getTitle())){
//the style to change
int WS_DLGFRAME = 0x00400000;//s/b long I think
//not the same constant here??
ta.appendText("windows api:"+WS_DLGFRAME+" JNA: "+WinUser.SM_CXDLGFRAME);
int oldStyle = user32.GetWindowLong(hwnd, GWL_STYLE);
int newStyle = oldStyle & ~0x00400000; //bitwise not WS_DLGFRAME means remove the style
newStyle = newStyle & ~0x00040000;//WS_THICKFRAME
user32.SetWindowLong(hwnd, GWL_STYLE, newStyle);
}
}
}
我的猜測是您將最后 3 行替換為
long oldStyleLong = user32.GetWindowLongPtr(hwnd, GWL_STYLE).longValue();
long newStyleLong = oldStyleLong & ~ 0x00400000l;
user32.SetWindowLongPtr(hwnd, GWL_STYLE, new BaseTSD.LONG_PTR(newStyleLong));
對於 64 位。 我想我的 User32.dll 中沒有這些功能,所以我無法測試它。 那里有很多無關的代碼,主要用於測試或教學。 一旦你弄清楚你想要做什么,就刪除未使用的行。
附: 不要添加newStyle = newStyle & ~0x00020000;//WS_MINIMIZEBOX
。 這是 JavaFX 不用於未修飾的樣式標志之一。 這就是為什么最小化不可用。 也許如果您嘗試設置未裝飾的舞台並添加(使用 |,而不是 &~)最小化框標志,您將獲得相同的結果。 有一些工具可以從任何窗口查找所有樣式標志。
這是使用舞台的 HWND 更改未裝飾舞台的最簡單的代碼量。
public void start(Stage stage) {
Scene scene = new Scene(new Pane(new Label("Hello World")));
stage.initStyle(StageStyle.UNDECORATED);
stage.setTitle("Find this window");
stage.setScene(scene);
stage.show();
long lhwnd = com.sun.glass.ui.Window.getWindows().get(0).getNativeWindow();
Pointer lpVoid = new Pointer(lhwnd);
HWND hwnd = new HWND(lpVoid);
final User32 user32 = User32.INSTANCE;
int oldStyle = user32.GetWindowLong(hwnd, GWL_STYLE);
System.out.println(Integer.toBinaryString(oldStyle));
int newStyle = oldStyle | 0x00020000;//WS_MINIMIZEBOX
System.out.println(Integer.toBinaryString(newStyle));
user32.SetWindowLong(hwnd, GWL_STYLE, newStyle);
}
它會打印前后的樣式標志,以便您可以查看設置的樣式。
這里要注意兩點。
首先,這些庫看起來不像是最新版本的 JNA,截至目前為 5.50,是從 Maven 存儲庫添加的。 我不得不添加 4.2.1 庫。
其次,您可能會遇到此異常,就像我在 Windows 10 和 Java 11 上所做的那樣: 在學習 Java Native Access 時包 com.sun.glass.ui 出錯
解決方案是轉到 IDE 中的 VM 選項(運行 -> 編輯配置...,在 IntelliJ 中)並添加以下內容:
--add-exports
javafx.graphics/com.sun.glass.ui=ALL-UNNAMED
在那之后它應該工作。
我希望看到有人實現本地 Windows 動畫來最小化和取消最小化未裝飾的窗口,但我還沒有仔細搜索過是否已經討論過。 如果我遇到解決方案,我會更新它。
編輯:
根據對 Windows 動畫的進一步研究,看起來可以一起破解解決方案,但我放棄了嘗試在下面實現這個 C# hack。 這似乎更像是一個操作系統問題,而不僅僅是 JavaFX。
通過在 start() 中修改它,我能夠在最小化和動畫的同時使初始窗口保持未裝飾狀態:
int newStyle = oldStyle | 0x00020000 | 0x00C00000;
但是,在最小化並重新打開后,Windows 邊框顯得很奇怪。
然后,我嘗試在圖標化時使用 ChangeListener 來交換 Windows 樣式。
stage.iconifiedProperty().addListener(new ChangeListener<Boolean>() {
@Override
public void changed(ObservableValue<? extends Boolean> ov, Boolean t, Boolean t1) {
if (t1.booleanValue() == true) {
int newStyle = oldStyle | 0x00020000 | 0x00C00000;
user32.SetWindowLong(hwnd, GWL_STYLE, newStyle);
} else if (t1.booleanValue() == false) {
int newStyle = oldStyle | 0x00020000;
user32.SetWindowLong(hwnd, GWL_STYLE, newStyle);
}
}
});
這成功地使窗口取消最小化動畫始終如一地正常工作,同時使(可見)舞台無邊界。
一旦我找到重新申請的最佳方式,我似乎可以讓最小化動畫工作:
int newStyle = oldStyle | 0x00020000 | 0x00C00000;
user32.SetWindowLong(hwnd, GWL_STYLE, newStyle);
就在舞台圖標化之前,用戶看不到邊框。 一旦實現,這可能類似於下面第一個鏈接中的 C# 解決方案。 基本上,上面的 ChangeListener 做了相反的事情。
與解決無邊框/未裝飾動畫有關的鏈接:
https://exceptionshub.com/borderless-window-using-areo-snap-shadow-minimize-animation-and-shake.html
http://pinvoke.net/default.aspx/Constants/Window%20styles.html
沒有正確回答您的問題..但這是解決方案
@FXML private void minimize()
{
Stage stage = (Stage) minimize.getScene().getWindow();
stage.setIconified(true);
}
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.