很多现代应用程序结合了标题栏和菜单栏(比如
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%原生标题栏效果。它是怎么做到的?
使用
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