【问题标题】:JavaFX Minimizing Undecorated StageJavaFX 最小化未装饰阶段
【发布时间】: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 参数(无论是真还是假)。让我们看看..

标签: java javafx jna


【解决方案1】:

您可以设置适当的窗口样式。它适用于 XP,但在 windows 7 32 位中应该没问题。 我认为(但无法测试)如果您使用 64 位然后更改为 Ptr windows 功能,即。 GetWindowLongPtr。

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 中没有这些功能,所以我无法测试它。里面有很多无关代码,主要用于测试或教学。确定要做什么后,删除未使用的行。

ps。不要添加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);
    }

它会打印出前后的样式标志,以便您查看设置了哪些样式。

【讨论】:

  • 这是一个很好的答案,但不是完全我所追求的,至少以目前的形式。窗口周围仍然有原生边框(虽然现在标题栏不见了),理想情况下,我希望完全摆脱原生装饰。顺便说一句,这种方法在我的 64 位 Win7 上似乎工作得很好,但是你建议的 3 行修复不起作用。 (实际上,在弄乱这些东西时,我的 Sublime Text 窗口中的标题栏丢失了 :)。
  • 如果 sublime 文本被聚焦(可能总是在顶部)那么它将失去标题栏。这就是为什么我提供了 2 种方法来获取 hwnd。一个来自私有 sun api,一个来自 user32。您也可以摆脱边界(我会更新)。你可以检查所有基本的window styles here.
  • 我只记得我添加了一个检查以确保 HWND 指向具有相同标题的窗口。它不应该弄乱任何其他窗口。
  • 如果操作系统不发送 WM_SIZE 消息(对于最大、最小等 - 不调整大小 - 那是 WM_SIZING),您将无法使用事件。如果没有特定的样式集,您将无法收到该消息。
  • @Manius 是的,检查我的答案。
【解决方案2】:

这里有两点需要注意。

首先,这些库看起来不像是在最新版本的 JNA 中,截至目前为 5.50,它是从 Maven 存储库添加的。我不得不添加 4.2.1 库。

其次,你可能会遇到这个异常,就像我在 Windows 10 和 Java 11 上所做的那样: Error with package com.sun.glass.ui while learning Java Native Access

解决方案是转到 IDE 中的 VM 选项(在 IntelliJ 中运行 -> 编辑配置...)并添加以下内容:

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

它应该在那之后工作。

我希望看到有人实现本机 Windows 动画来最小化和取消最小化未装饰的窗口,但我还没有进行过彻底的搜索以查看是否已经讨论过。如果我遇到解决方案,我会更新这个。

编辑:

在对 Windows 动画进行进一步研究后,看起来可以一起破解解决方案,但我放弃了尝试在下面实现这个 C# 破解。这似乎更像是一个操作系统问题,而不仅仅是 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 做了相反的事情。

与解决无边界/未装饰动画有关的链接:

Use windows animations on borderless form

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

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

JavaFX Minimizing & Maximing undecorated stage with animations

【讨论】:

    【解决方案3】:

    没有正确回答您的问题..但这里是解决方案

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

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2014-07-11
      • 2016-11-09
      • 2011-12-29
      • 2016-10-02
      • 1970-01-01
      • 2014-01-23
      相关资源
      最近更新 更多