You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在JavaFX应用中禁用Windows键,防止全屏时焦点丢失

解决JavaFX全屏应用中Windows键弹出开始菜单的问题

你的问题核心在于Windows键是系统级热键,JavaFX的Scene事件无法优先拦截它——系统会先响应Win键打开开始菜单,你的应用才能收到按键事件,这时候已经晚了。要解决这个问题,必须通过系统级的键盘钩子来拦截Win键,下面是具体实现方案:

方案:使用JNA实现全局键盘钩子

JNA可以让Java调用Windows原生API,实现全局键盘事件拦截,直接在系统层面消费Win键,阻止系统响应。

步骤1:添加JNA依赖

如果用Maven,在pom.xml中添加:

<dependency>
    <groupId>net.java.dev.jna</groupId>
    <artifactId>jna</artifactId>
    <version>5.13.0</version>
</dependency>
<dependency>
    <groupId>net.java.dev.jna</groupId>
    <artifactId>jna-platform</artifactId>
    <version>5.13.0</version>
</dependency>

如果是Gradle:

implementation 'net.java.dev.jna:jna:5.13.0'
implementation 'net.java.dev.jna:jna-platform:5.13.0'

步骤2:实现全局键盘钩子类

创建一个WindowsKeyHook类,负责拦截Win键:

import com.sun.jna.Native;
import com.sun.jna.platform.win32.*;
import com.sun.jna.win32.W32APIOptions;
import java.util.List;
import java.util.Arrays;

public class WindowsKeyHook {
    private static HHOOK hHook;
    private static final User32 USER32 = User32.INSTANCE;
    private static final Kernel32 KERNEL32 = Kernel32.INSTANCE;

    // 键盘钩子回调函数
    private static LowLevelKeyboardProc keyboardProc = new LowLevelKeyboardProc() {
        @Override
        public LRESULT callback(int nCode, WPARAM wParam, KBDLLHOOKSTRUCT info) {
            if (nCode >= 0) {
                // 拦截左Win键(0x5B)和右Win键(0x5C)
                if (info.vkCode.intValue() == 0x5B || info.vkCode.intValue() == 0x5C) {
                    // 返回1表示消费该事件,阻止系统处理
                    return new LRESULT(1);
                }
            }
            // 其他事件交给系统处理
            return USER32.CallNextHookEx(hHook, nCode, wParam, info.getPointer());
        }
    };

    // 安装钩子
    public static void installHook() {
        hHook = USER32.SetWindowsHookExW(
                User32.WH_KEYBOARD_LL,
                keyboardProc,
                KERNEL32.GetModuleHandleW(null),
                new DWORD(0)
        );
        if (hHook == null) {
            throw new RuntimeException("Failed to set keyboard hook");
        }
    }

    // 卸载钩子
    public static void uninstallHook() {
        if (hHook != null) {
            USER32.UnhookWindowsHookEx(hHook);
            hHook = null;
        }
    }

    // 必须保持消息循环,否则钩子会失效
    public static void startMessageLoop() {
        MSG msg = new MSG();
        while (USER32.GetMessageW(msg, null, new DWORD(0), new DWORD(0)) != 0) {
            USER32.TranslateMessage(msg);
            USER32.DispatchMessageW(msg);
        }
    }

    // JNA接口定义
    public interface User32 extends StdCallLibrary {
        User32 INSTANCE = Native.load("user32", User32.class, W32APIOptions.DEFAULT_OPTIONS);
        int WH_KEYBOARD_LL = 13;

        HHOOK SetWindowsHookExW(int idHook, LowLevelKeyboardProc lpfn, HINSTANCE hMod, DWORD dwThreadId);
        boolean UnhookWindowsHookEx(HHOOK hhk);
        LRESULT CallNextHookEx(HHOOK hhk, int nCode, WPARAM wParam, LPARAM lParam);
        int GetMessageW(MSG lpMsg, HWND hWnd, DWORD wMsgFilterMin, DWORD wMsgFilterMax);
        boolean TranslateMessage(MSG lpMsg);
        boolean DispatchMessageW(MSG lpMsg);
    }

    public interface Kernel32 extends StdCallLibrary {
        Kernel32 INSTANCE = Native.load("kernel32", Kernel32.class, W32APIOptions.DEFAULT_OPTIONS);
        HINSTANCE GetModuleHandleW(String lpModuleName);
    }

    public interface LowLevelKeyboardProc extends StdCallCallback {
        LRESULT callback(int nCode, WPARAM wParam, KBDLLHOOKSTRUCT info);
    }

    public static class KBDLLHOOKSTRUCT extends Structure {
        public DWORD vkCode;
        public DWORD scanCode;
        public DWORD flags;
        public DWORD time;
        public ULONG_PTR dwExtraInfo;

        @Override
        protected List<String> getFieldOrder() {
            return Arrays.asList("vkCode", "scanCode", "flags", "time", "dwExtraInfo");
        }
    }
}

步骤3:在你的JavaFX应用中集成钩子

修改HomeApplication类,在Windows系统下安装钩子,并在应用退出时卸载:

import javafx.application.Application;
import javafx.application.Platform;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.image.Image;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyCombination;
import javafx.scene.input.KeyEvent;
import javafx.scene.paint.Color;
import javafx.stage.Stage;
import javafx.stage.StageStyle;
import org.dc_proctoring.dc_v_1.UtilityClasses.OSOperations;
import org.dc_proctoring.dc_v_1.UtilityClasses.ResourceLoaders;

import java.io.IOException;

public class HomeApplication extends Application {
    @Override
    public void start(Stage stage) throws IOException, InterruptedException {
        String osName = System.getProperty("os.name");
        System.out.println("osName = " + osName);
        if(osName.startsWith("Windows")){
            // 启动钩子线程,避免阻塞UI线程
            new Thread(() -> {
                WindowsKeyHook.installHook();
                WindowsKeyHook.startMessageLoop();
            }).start();

            // 终止当前所有用户运行的进程
            Thread killUserProcessesThread = new Thread(OSOperations::killAllProcesses);
            killUserProcessesThread.start();
            loadApplicationForWindows(stage);

            // 应用退出时卸载钩子
            Runtime.getRuntime().addShutdownHook(new Thread(WindowsKeyHook::uninstallHook));

        }else if(osName.startsWith("Linux")){
            System.out.println("检测到Linux系统");
        } else if(osName.startsWith("Mac")){
            System.out.println("检测到Mac系统");
        }
    }

    private static void loadApplicationForWindows(Stage stage){
        Scene scene = getScene();

        stage.initStyle(StageStyle.UNDECORATED);
        stage.setTitle("DC Proctoring Application");
        Image icon = new Image(ResourceLoaders.loadResources("logo.jpg"));
        stage.getIcons().add(icon);
        stage.setOnCloseRequest(event -> {
            // 仅在允许关闭时放行,根据你的业务逻辑调整
            // 示例:默认阻止,直到用户完成测试或触发违规操作
            event.consume();
            // 满足条件时调用Platform.exit()关闭应用
        });
        stage.setFullScreen(true);
        stage.setResizable(false);
        stage.setAlwaysOnTop(true);
        stage.setFullScreenExitHint("You can't escape it.");
        stage.setFullScreenExitKeyCombination(KeyCombination.NO_MATCH);
        stage.setScene(scene);
        stage.show();
    }

    private static Scene getScene() {
        Group group = new Group();
        Scene scene = new Scene(group, Color.WHITE);
        scene.setOnDragOver(dragEvent -> {
            System.out.println("dragEvent = " + dragEvent);
            System.out.println("触发dragEvent");
        });
        scene.setOnKeyPressed(keyEvent -> {
            KeyCode keyCode = keyEvent.getCode();
            if(keyCode == KeyCode.UNDEFINED){
                keyEvent.consume();
            }
            // 保留违规操作处理逻辑,比如按下Win键时触发应用退出
            else if (keyCode == KeyCode.WINDOWS){
                Platform.exit();
            }
        });
        return scene;
    }

    public static void main(String[] args) {
        launch(args);
    }
}

注意事项

  1. 管理员权限:全局键盘钩子需要应用以管理员权限运行,否则可能无法正常拦截系统热键。
  2. 消息循环线程:钩子的消息循环必须在独立线程中运行,不能阻塞JavaFX的UI线程。
  3. 卸载钩子:一定要在应用退出时卸载钩子,避免残留系统资源。
  4. 业务逻辑适配:你可以根据需求调整钩子的拦截逻辑,比如只在应用全屏时拦截Win键,或者在用户违规时允许Win键触发应用退出。

内容的提问来源于stack exchange,提问作者Haseeb Ahmad Butt

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.03 10:08:13