0

Many modern applications combine title bar and menu bar (such as Chrome, VS Code, IntelliJ IDEA, MS Offer), how is this possible? Does the Windows system provide support for this effect or does the application implement all the title bar features entirely on its own?

I want to use JavaFX to achieve this effect, but it is not as simple as setting StageStyle.UNDECORATED and adding a mouse movement event.

For the Windows platform, double-clicking the title bar can switch the window between Maximize and Restore, press Alt+Space combination to open the title bar menu, there will be shadows or no shadows on the edge of the window according to the Windows system settings, and when dragging the window to move, only the window boundary is displayed instead of the window content according to the Windows system settings. In addition, Windows Aero Snap provides a switch between "Maximize", "Restore" and "Minimize" by pressing Win+↑ / Win+↓, and press Win+← / Win+→ to enter the snapped windows resize mode.

I found many JavaFX projects on the Internet for observation, so I found the FX-BorderlessScene library of the XR3Player project. It achieves a good effect, but it still does not achieve the full native effect. For example, after closing Aero Snap Window in the Windows system, it is still You can press Win+↑ to switch to maximize.

Is there a convenient and mature way to achieve a fully localized title bar? I would forego merging the title bar and menu bar if I had to do a huge amount of work to implement it myself.

IntelliJ IDEA developed in Java has almost 100% native title bar effect. How does it do it?

Xi Minghui
  • 95
  • 1
  • 9
  • 2
    Googling brought me to this: [Title bar customization](https://learn.microsoft.com/en-us/windows/apps/develop/title-bar?tabs=wasdk). However, that only seems helpful if you're using a Windows GUI framework (e.g., UWP). I'm not sure how to tap into those APIs from Java. Though I can say that JavaFX provides no public API for this. – Slaw Feb 22 '23 at 21:53
  • 1
    There is a [`StageStyle.UNIFED`](https://openjfx.io/javadoc/19/javafx.graphics/javafx/stage/StageStyle.html#UNIFIED). It is platform specific and, I think, even when supported functionality may vary between platforms. It may help you, but it also might not support what you want (as suggested by Slaw). – jewelsea Feb 22 '23 at 23:04
  • 1
    *"IntelliJ IDEA developed in Java has almost 100% native title bar effect. How does it do it?"*-> it uses a [customized windowing toolkit developed mainly in kotlin on top of Java Swing](https://github.com/JetBrains/intellij-community/tree/master/platform/platform-impl/src/com/intellij/openapi/wm/impl). Core code is probably in [IdeRootPane.kt](https://github.com/JetBrains/intellij-community/blob/master/platform/platform-impl/src/com/intellij/openapi/wm/impl/IdeRootPane.kt). It is not something that you would want to adapt or use in a JavaFX application IMO. – jewelsea Feb 22 '23 at 23:58
  • Reply to Slaw: Thanks, this is exactly what I was looking for. It's a shame it's the Windows SDK, JavaFX doesn't currently provide one support. IntelliJ IDEA may write a lot of code to implement the complete title bar feature by itself. – Xi Minghui Feb 23 '23 at 02:19
  • Reply to jewelsea: Thanks, this might help me. Also is the JVM language, is it possible for me to mix its code in the project? I will be looking into this. – Xi Minghui Feb 23 '23 at 02:25
  • It would be lucky if ```intellij-community``` separated the code including focus handling, title bar events, window shadow drawing as a Java library. An alternative is to make some enhancements based on ```FX-BorderlessScene```, but while obtaining the window drag event processing, it needs to endure the inconsistent performance of the focus event, title bar behavior, window shadow border drawing and platform. So I decided to give up merging the title bar and menu bar and use the platform's default decoration, which is too expensive just for compact space and aesthetics. – Xi Minghui Feb 23 '23 at 02:43
  • @jewelsea Can `UNIFIED` be used for something like this? I'll admit, I've never seen much difference between `UNIFIED` and `DECORATED` the few times I experiemented with the former. And unfortunately, messing with it right now (JavaFX 19.0.2.1 from Maven Central, Windows 11), initializing the style to `UNIFIED` causes nothing to be rendered in the scene (no matter the content). Not sure why that's happening; must be a bug, as `Platform.isSupported` says this feature is supposedly supported. Though maybe I'm doing something wrong. – Slaw Feb 23 '23 at 07:22
  • @Slaw I seem to recall UNIFIED doing something different than normal windows on older windows systems running aero but I guess it may not even work on newer windows versions from your report. I tried UNIFIED on OS X 13.1 with JavaFX 19 and it looked exactly the same as a normal window (scene still renders OK), even though the conditional feature for unified reports true. So, basically UNIFIED is (at best) useless is my guess, unfortunately. – jewelsea Feb 23 '23 at 08:14
  • 2
    I'm trying to call ```DwmExtendFrameIntoClientArea``` or ```SetWindowLongPtr``` function via JNI/JNA to remove/hide the title bar. Setting ```StageStyle``` to an enum value with platform decoration (```DECORATED```, ```UNIFIED``` or ```UTILITY```), I only need to reimplement the title bar functionality (or use ```FX-BorderlessScene```), other native window features (including border shadow, aero snap) will be preserved. If this solution is feasible, I will paste the solution into an answer and attach a demo codes. – Xi Minghui Feb 23 '23 at 08:33
  • 1
    @Slaw: ```UNIFIED``` also doesn't work on my computer, which seems to be [a bug](https://bugs.openjdk.org/browse/JDK-8154847). – Xi Minghui Feb 23 '23 at 14:17

1 Answers1

2

Use JNA to call the native API of Mac, Windows or Linux to hide the title bar, and the following demo shows how to hide the title-bar on Windows and keep all Windows localized window decorations. There is no custom title-bar implementation provided here, you need to design it yourself.


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

}

Demo: https://videos.ximinghui.org/230303_demo_running_on_win10.mp4

Note: The top 6px white bar question redirects Create window without titlebar, with resizable border and without bogus 6px white stripe

Xi Minghui
  • 95
  • 1
  • 9