简体   繁体   English

JavaFX 最小化未修饰的舞台

[英]JavaFX Minimizing Undecorated Stage

I have an undecorated JavaFX Stage, and my own minimize, maximize & close buttons.我有一个未修饰的 JavaFX Stage,以及我自己的最小化、最大化和关闭按钮。 But unfortunately clicking the taskbar icon in Windows 7 does not automatically minimize the stage - compared to the decorated behaviour.但不幸的是,与装饰行为相比,单击 Windows 7 中的任务栏图标并不会自动最小化舞台。

Is there a way to minimize an undecorated stage with pure Java code, by clicking the taskbar icon?有没有办法通过单击任务栏图标来最小化带有纯 Java 代码的未修饰阶段? If not how can I do this with, say, JNA?如果不是,我怎么能用 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.编辑:好的,我一直在尝试用 JNA 解决这个问题,但是几乎没有完成 C/C++/JNA,我在设置它时遇到了一些麻烦。 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.编辑 2:我最终对这个进行了进一步的研究,但是一旦我重新设置了 WNDPROC,我未装饰的窗口就没有响应任何事件......我为一个独立的例子提供了 100 点声望有一个有效的解决方案。 Windows (7+) only is OK, I do not even know how this behaves on other platforms.仅 Windows (7+) 还可以,我什至不知道这在其他平台上的表现如何。

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..编辑 3:嗯,我有点放弃了这个......我正确设置了一切,并收到了事件,但在找出要收听的正确事件时遇到了问题..

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);
        }
    }
}

You can just set the appropriate window style.您只需设置适当的窗口样式即可。 It works in XP but should be ok in windows 7 32 bit.它适用于 XP,但在 Windows 7 32 位中应该没问题。 I think (but can't test) if you use 64 bit then change to the Ptr windows functions, ie.我认为(但无法测试)如果您使用 64 位然后更改为 Ptr windows 函数,即。 GetWindowLongPtr.获取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);
        }
    }

}

My guess is you replace the last 3 lines with我的猜测是您将最后 3 行替换为

            long oldStyleLong = user32.GetWindowLongPtr(hwnd, GWL_STYLE).longValue();
            long newStyleLong = oldStyleLong & ~ 0x00400000l;
            user32.SetWindowLongPtr(hwnd, GWL_STYLE, new BaseTSD.LONG_PTR(newStyleLong));

for 64 bit.对于 64 位。 I think I don't have those functions in my User32.dll, so I can't test it.我想我的 User32.dll 中没有这些功能,所以我无法测试它。 There's lots of extraneous code in there, mainly for testing or teaching.那里有很多无关的代码,主要用于测试或教学。 Remove the unused lines once you figure out what you want to do.一旦你弄清楚你想要做什么,就删除未使用的行。

ps.附: Don't add newStyle = newStyle & ~0x00020000;//WS_MINIMIZEBOX .不要添加newStyle = newStyle & ~0x00020000;//WS_MINIMIZEBOX That's one of the style flags JavaFX doesn't use for undecorated.这是 JavaFX 不用于未修饰的样式标志之一。 That's why the minimize isn't available.这就是为什么最小化不可用。 Maybe if you try setting stage undecorated and adding (using |, not &~) the minimize box flag, you'll get the same result.也许如果您尝试设置未装饰的舞台并添加(使用 |,而不是 &~)最小化框标志,您将获得相同的结果。 There are tools to look up all the style flags from any window.有一些工具可以从任何窗口查找所有样式标志。

Here's the simplest amount of code that just changes an undecorated stage using the stage's HWND.这是使用舞台的 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);
    }

It prints out the style flags before and after so you can look up what styles are set.它会打印前后的样式标志,以便您可以查看设置的样式。

Two things to note here.这里要注意两点。

First, it doesn't look like these libraries are in the latest version of JNA, 5.50 as of now, adding from the Maven repository.首先,这些库看起来不像是最新版本的 JNA,截至目前为 5.50,是从 Maven 存储库添加的。 I had to add the 4.2.1 library instead.我不得不添加 4.2.1 库。

Second, you may encounter this exception, like I did on Windows 10 and Java 11: Error with package com.sun.glass.ui while learning Java Native Access其次,您可能会遇到此异常,就像我在 Windows 10 和 Java 11 上所做的那样: 在学习 Java Native Access 时包 com.sun.glass.ui 出错

The solution is go to your VM options in your IDE (Run -> Edit Configurations..., in IntelliJ) and add this:解决方案是转到 IDE 中的 VM 选项(运行 -> 编辑配置...,在 IntelliJ 中)并添加以下内容:

--add-exports
javafx.graphics/com.sun.glass.ui=ALL-UNNAMED

It should work after that.在那之后它应该工作。

I would like to see someone implement the native Windows animations for minimizing and un-minimizing an undecorated window, but I haven't searched too thoroughly yet to see if this has already been discussed.我希望看到有人实现本地 Windows 动画来最小化和取消最小化未装饰的窗口,但我还没有仔细搜索过是否已经讨论过。 I'll update this if I come across a solution.如果我遇到解决方案,我会更新它。

Edit:编辑:

Upon further research on the Windows animations, it looks like a solution could be hacked together, but I gave up at trying to implement this C# hack below.根据对 Windows 动画的进一步研究,看起来可以一起破解解决方案,但我放弃了尝试在下面实现这个 C# hack。 It seems to be more of an OS issue and not just JavaFX.这似乎更像是一个操作系统问题,而不仅仅是 JavaFX。

I was able to get the initial window to stay undecorated while minimizing and with the animation by modifying this in start():通过在 start() 中修改它,我能够在最小化和动画的同时使初始窗口保持未装饰状态:

int newStyle = oldStyle | 0x00020000 | 0x00C00000;

But, after minimizing and reopening, the Windows border appears oddly enough.但是,在最小化并重新打开后,Windows 边框显得很奇怪。

Then, I tried to use a ChangeListener to swap Windows styles when iconifying.然后,我尝试在图标化时使用 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);
            }
        }
    });

This successfully gets the windows un-minimize animation to work fine consistently, while leaving the (visible) stage borderless.这成功地使窗口取消最小化动画始终如一地正常工作,同时使(可见)舞台无边界。

It looks like I can get minimization animations working once I find out the best way to re-apply:一旦我找到重新申请的最佳方式,我似乎可以让最小化动画工作:

int newStyle = oldStyle | 0x00020000 | 0x00C00000;
user32.SetWindowLong(hwnd, GWL_STYLE, newStyle);

just before the stage is iconified, and the border isn't visible to the user.就在舞台图标化之前,用户看不到边框。 Once implemented, this might work similarly to the C# solution in the first link below.一旦实现,这可能类似于下面第一个链接中的 C# 解决方案。 Basically, what the above ChangeListener does in reverse.基本上,上面的 ChangeListener 做了相反的事情。

Links to do with solving borderless/undecorated animations:与解决无边框/未装饰动画有关的链接:

Use windows animations on borderless form 在无边框形式上使用 Windows 动画

https://exceptionshub.com/borderless-window-using-areo-snap-shadow-minimize-animation-and-shake.html https://exceptionshub.com/borderless-window-using-areo-snap-shadow-minimize-animation-and-shake.html

http://pinvoke.net/default.aspx/Constants/Window%20styles.html http://pinvoke.net/default.aspx/Constants/Window%20styles.html

JavaFX Minimizing & Maximing undecorated stage with animations JavaFX 使用动画最小化和最大化未装饰的舞台

Didn't get your question properly..but here's the solution没有正确回答您的问题..但这是解决方案

@FXML private void minimize()
{
Stage stage = (Stage) minimize.getScene().getWindow();
stage.setIconified(true);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM