JavaFX如何整合标题栏和菜单栏?

问题描述 投票:0回答:1

很多现代应用程序结合了标题栏和菜单栏(比如

Chrome
VS Code
IntelliJ IDEA
MS Offer
),这怎么可能? Windows系统是否支持这种效果,或者应用程序是否完全自行实现所有标题栏功能?

我想用JavaFX来实现这个效果,但是不是设置

StageStyle.UNDECORATED
和添加鼠标移动事件那么简单

对于Windows平台,双击标题栏可以在

Maximize
Restore
之间切换窗口,按
Alt+Space
组合键打开标题栏菜单,窗口边缘会有阴影或没有阴影根据Windows系统设置,拖动窗口移动时,只显示窗口边界,不显示Windows系统设置的窗口内容。此外,
Windows Aero Snap
提供了“最大化”、“恢复”和“最小化”的切换,通过按
Win+↑
/
Win+↓
,按
Win+←
/
Win+→
进入捕捉窗口调整大小模式。

在网上找了很多JavaFX项目进行观察,于是找到了XR3Player项目的FX-BorderlessScene库。 达到了不错的效果,但还是没有达到完整的原生效果。比如Windows系统关闭

Aero Snap Window
后,还是可以按
Win+↑
切换到最大化

有没有方便成熟的方式实现全本地化的标题栏?如果我必须自己做大量的工作来实现它,我会放弃合并标题栏和菜单栏。

IntelliJ IDEA
Java开发,几乎100%原生标题栏效果。它是怎么做到的?

user-interface javafx titlebar aero modern-ui
1个回答
0
投票

使用

JNA
调用
Mac
Windows
Linux
的原生API隐藏标题栏,下面
demo
展示了如何在Windows上隐藏标题栏并保持所有Windows本地化窗口装饰。这里没有提供自定义标题栏的实现,需要自己设计。


pom.xml:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>demo</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <maven.compiler.release>17</maven.compiler.release>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.26</version>
            <scope>provided</scope>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.12.0</version>
        </dependency>
        <dependency>
            <groupId>org.openjfx</groupId>
            <artifactId>javafx-controls</artifactId>
            <version>19.0.2.1</version>
        </dependency>
        <dependency>
            <groupId>net.java.dev.jna</groupId>
            <artifactId>jna-platform</artifactId>
            <version>5.13.0</version>
        </dependency>
        <!-- This dependency is required when using FX-BorderlessScene -->
        <dependency>
            <groupId>uk.co.bithatch</groupId>
            <artifactId>FX-BorderlessScene</artifactId>
            <version>5.0.0</version>
        </dependency>
    </dependencies>

</project>
package org.example;

import javafx.application.Application;

public class Main {

    public static void main(String[] args) {
        Application.launch(MyApp.class);
    }

}
package org.example;

import com.goxr3plus.fxborderlessscene.borderless.BorderlessScene;
import javafx.application.Application;
import javafx.scene.*;
import javafx.scene.layout.*;
import javafx.scene.paint.*;
import javafx.stage.*;
import org.apache.commons.lang3.SystemUtils;

public class MyApp extends Application {

    private static final String TITLE = "Test GUI";

    private static final Color PRIMARY_COLOR = Color.rgb(60, 60, 60);

    private static final boolean USE_BORDERLESS_WRAPPER = true;

    private static Scene getScene(Stage stage, Parent root) {
        if (!USE_BORDERLESS_WRAPPER) return new Scene(root);
        BorderlessScene borderlessScene = new BorderlessScene(stage, StageStyle.DECORATED, root);
        // Pass your custom title-bar element
        borderlessScene.setMoveControl(root);
        return borderlessScene;
    }

    @Override
    public void start(Stage primaryStage) {
        // Init window
        primaryStage.setTitle(TITLE);
        primaryStage.setWidth(1024);
        primaryStage.setHeight(768);
        Pane pane = new Pane();
        pane.setBackground(Background.fill(PRIMARY_COLOR));
        primaryStage.setScene(getScene(primaryStage, pane));
        primaryStage.show();

        // Only for Windows system
        if (SystemUtils.IS_OS_WINDOWS) {
            // Remove caption
            WindowsUtils.removeCaption(null, TITLE);
            // Redraw window
            WindowsUtils.refreshWindow(null, TITLE);
        } else if (SystemUtils.IS_OS_MAC) {
            throw new RuntimeException("Please write a window custom implementation suitable for this platform.");
        } else if (SystemUtils.IS_OS_LINUX) {
            throw new RuntimeException("Please write a window custom implementation suitable for this platform.");
        } else {
            throw new RuntimeException("Please write a window custom implementation suitable for this platform.");
        }
    }

}
package org.example;

import com.sun.jna.Pointer;
import com.sun.jna.platform.win32.*;
import org.apache.commons.lang3.SystemUtils;

/** Windows OS utils */
public class WindowsUtils {

    public static void removeCaption(String className, String windowName) {
        requireWindowsOS();
        // Find window by class name and/or window name
        WinDef.HWND window = findWindow(className, windowName);
        // Get current style of window
        int presentStyle = User32.INSTANCE.GetWindowLongPtr(window, WinUser.GWL_STYLE).intValue();
        // Remove title-bar of window: current style minus caption
        int newStyle = presentStyle ^ WinUser.WS_CAPTION;
        // Update window style
        User32.INSTANCE.SetWindowLongPtr(window, WinUser.GWL_STYLE, new Pointer(newStyle));
    }

    public static void refreshWindow(String className, String windowName) {
        requireWindowsOS();
        // Find window by class name and/or window name
        WinDef.HWND window = findWindow(className, windowName);

        int uFlags =

                WinUser.SWP_FRAMECHANGED |
                        // 保留当前位置(忽略X和Y参数)
                        WinUser.SWP_NOMOVE |
                        // 保留当前大小(忽略cx和cy参数)
                        WinUser.SWP_NOSIZE |
                        // 不更改所有者窗口在 Z 顺序中的位置
                        WinUser.SWP_NOREPOSITION |
                        // 保留当前的 Z 顺序(忽略 hWndInsertAfter 参数)
                        WinUser.SWP_NOZORDER;

        // 更新SetWindowLong函数设置的样式
        User32.INSTANCE.SetWindowPos(window, null, 0, 0, 0, 0, uFlags);
    }

    public static WinDef.HWND findWindow(String className, String windowName) {
        requireWindowsOS();
        return User32.INSTANCE.FindWindow(className, windowName);
    }

    protected static void requireWindowsOS() {
        if (SystemUtils.IS_OS_WINDOWS) return;
        throw new IllegalStateException("unsupported operation");
    }

}

演示:https://videos.ximinghui.org/230303_demo_running_on_win10.mp4

注意:顶部 6px 白条问题重定向 Create window without titlebar, with resizable border and without bogus 6px white stripe

© www.soinside.com 2019 - 2024. All rights reserved.