如何在JPanel中启动.exe桌面程序?实现左右分栏运行的问题
这个问题很常见——用Runtime.exec()启动外部程序只会让它作为独立进程运行,没法直接嵌入到Swing的JPanel里。要实现把外部exe窗口嵌入到你的Swing界面中,得借助Windows的原生API来修改外部窗口的父容器,这里推荐用JNA(Java Native Access)来简化调用,不用写复杂的JNI代码。
解决思路
- 启动外部exe进程,获取它的主窗口句柄
- 通过Windows API将该窗口的父窗口设置为目标
JPanel的句柄 - 同步嵌入窗口与
JPanel的大小、位置
具体实现步骤
添加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>也可以手动下载JNA的jar包直接导入项目。
编写嵌入逻辑代码
下面是完整的示例代码,包含启动exe、查找窗口、嵌入到JPanel的完整逻辑:import com.sun.jna.Native; import com.sun.jna.platform.win32.User32; import com.sun.jna.platform.win32.WinDef.HWND; import com.sun.jna.platform.win32.WinUser; import javax.swing.*; import java.awt.*; import java.io.IOException; import java.util.concurrent.TimeUnit; public class ExeEmbedDemo extends JFrame { private JPanel leftPanel; public ExeEmbedDemo() { setTitle("外部应用嵌入示例"); setSize(1000, 600); setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); setLayout(new GridLayout(1, 2)); // 左面板:用于嵌入外部exe leftPanel = new JPanel(); leftPanel.setBorder(BorderFactory.createTitledBorder("外部应用")); add(leftPanel); // 右面板:你的自有应用区域 JPanel rightPanel = new JPanel(); rightPanel.setBorder(BorderFactory.createTitledBorder("自有应用")); rightPanel.add(new JLabel("这里是你的自有应用内容")); add(rightPanel); // 在Swing事件线程中启动并嵌入外部exe SwingUtilities.invokeLater(this::launchAndEmbedExe); } private void launchAndEmbedExe() { String exePath = "C:\\Program Files\\...... Starter.exe"; Process process = null; try { // 启动外部exe进程 process = Runtime.getRuntime().exec(exePath); // 等待进程完全启动(时间根据应用启动速度调整) TimeUnit.SECONDS.sleep(2); // 获取外部应用的窗口句柄:两种方式选其一 // 方式1:已知窗口标题时直接查找 HWND hwnd = User32.INSTANCE.FindWindow(null, "你的外部应用窗口标题"); // 方式2:通过进程ID查找(更可靠,适合不知道标题的情况) // HWND hwnd = findWindowByProcessId(process.pid()); if (hwnd != null) { // 获取JPanel的原生窗口句柄 HWND panelHwnd = new HWND(Native.getComponentPointer(leftPanel)); // 将外部窗口的父容器设置为当前JPanel User32.INSTANCE.SetParent(hwnd, panelHwnd); // 可选:移除外部窗口的标题栏和边框,让它完全适配JPanel long windowStyle = User32.INSTANCE.GetWindowLong(hwnd, WinUser.GWL_STYLE); windowStyle &= ~(WinUser.WS_CAPTION | WinUser.WS_THICKFRAME | WinUser.WS_MINIMIZEBOX | WinUser.WS_MAXIMIZEBOX); User32.INSTANCE.SetWindowLong(hwnd, WinUser.GWL_STYLE, windowStyle); // 调整外部窗口大小以匹配JPanel resizeEmbeddedWindow(hwnd); // 添加监听器:JPanel大小变化时同步调整嵌入窗口 leftPanel.addComponentListener(new java.awt.event.ComponentAdapter() { @Override public void componentResized(java.awt.event.ComponentEvent evt) { resizeEmbeddedWindow(hwnd); } }); } else { JOptionPane.showMessageDialog(this, "未找到外部应用的窗口"); } } catch (IOException | InterruptedException e) { e.printStackTrace(); } finally { // 程序关闭时自动销毁外部进程 if (process != null) { Runtime.getRuntime().addShutdownHook(new Thread(process::destroy)); } } } // 调整嵌入窗口大小适配JPanel private void resizeEmbeddedWindow(HWND hwnd) { Rectangle panelBounds = leftPanel.getBounds(); User32.INSTANCE.MoveWindow(hwnd, 0, 0, panelBounds.width, panelBounds.height, true); } // 辅助方法:通过进程ID查找对应窗口句柄 private HWND findWindowByProcessId(int pid) { final int[] targetPid = {pid}; final HWND[] resultHwnd = {null}; // 枚举所有窗口,匹配进程ID User32.INSTANCE.EnumWindows((hwnd, data) -> { int[] windowPid = new int[1]; User32.INSTANCE.GetWindowThreadProcessId(hwnd, windowPid); if (windowPid[0] == targetPid[0]) { resultHwnd[0] = hwnd; return false; // 找到后停止枚举 } return true; // 继续枚举 }, null); return resultHwnd[0]; } public static void main(String[] args) { SwingUtilities.invokeLater(() -> new ExeEmbedDemo().setVisible(true)); } }
关键细节说明
- 窗口句柄查找:如果不知道外部应用的窗口标题,用
findWindowByProcessId方法通过进程ID查找更可靠,但枚举窗口会有轻微性能开销。 - 启动等待时间:必须给外部应用足够的启动时间,否则可能找不到窗口句柄,可根据实际应用的启动速度调整等待时长。
- 窗口样式调整:移除标题栏和边框是可选操作,如果需要保留外部应用的窗口装饰,直接注释掉样式修改的代码即可。
- 跨平台注意:这个方案仅适用于Windows系统,因为调用了Windows原生API;如果需要跨平台,得针对不同操作系统做适配。
内容的提问来源于stack exchange,提问作者Divya




