You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在JPanel中启动.exe桌面程序?实现左右分栏运行的问题

这个问题很常见——用Runtime.exec()启动外部程序只会让它作为独立进程运行,没法直接嵌入到Swing的JPanel里。要实现把外部exe窗口嵌入到你的Swing界面中,得借助Windows的原生API来修改外部窗口的父容器,这里推荐用JNA(Java Native Access)来简化调用,不用写复杂的JNI代码。

解决思路

  • 启动外部exe进程,获取它的主窗口句柄
  • 通过Windows API将该窗口的父窗口设置为目标JPanel的句柄
  • 同步嵌入窗口与JPanel的大小、位置

具体实现步骤

  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>
    

    也可以手动下载JNA的jar包直接导入项目。

  2. 编写嵌入逻辑代码
    下面是完整的示例代码,包含启动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

火山引擎 最新活动