如何在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); } }
注意事项
- 管理员权限:全局键盘钩子需要应用以管理员权限运行,否则可能无法正常拦截系统热键。
- 消息循环线程:钩子的消息循环必须在独立线程中运行,不能阻塞JavaFX的UI线程。
- 卸载钩子:一定要在应用退出时卸载钩子,避免残留系统资源。
- 业务逻辑适配:你可以根据需求调整钩子的拦截逻辑,比如只在应用全屏时拦截Win键,或者在用户违规时允许Win键触发应用退出。
内容的提问来源于stack exchange,提问作者Haseeb Ahmad Butt
相关产品推荐
相关产品推荐

