首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> Java 相關知識

Java Swing 進階:特殊容器全解析(JSplitPane、JTabbedPane、JLayeredPane、JDesktopPane)

     編輯:Java 相關知識

前言

Swing 提供了多種特殊容器,能夠滿足複雜界面佈局需求。本文將深入講解 JSplitPane(分割面板)、JTabbedPane(標籤面板)、JLayeredPane(分層面板),以及用於實現 MDI(多文檔界面)的 JDesktopPane 與 JInternalFrame。這些容器功能強大,透過完整代碼示例幫助你快速掌握核心用法。

一、JSplitPane:靈活分割界面空間

JSplitPane 允許將兩個組件按水平或垂直方向分割,並提供可拖動的分隔條調整比例。常見於「左側導航+右側內容」或「上方工具欄+下方工作區」佈局。

核心特性

  • 方向控制:HORIZONTAL_SPLIT(水平)與 VERTICAL_SPLIT(垂直)
  • 連續佈局:拖動分隔條時即時重繪組件(setContinuousLayout(true))
  • 一鍵展開/摺疊:setOneTouchExpandable(true) 顯示箭頭按鈕
  • 優先尺寸重置:resetToPreferredSizes() 依組件 preferredSize 自動調整分割比例

完整範例:書籍資訊瀏覽器

以下程式展示巢狀分割面板:左側垂直分割顯示封面與描述,右側放置書籍列表;選擇列表項目即時更新左側內容。

public class BookBrowser {
    private static class Book {
        private final String name;
        private final Icon cover;
        private final String description;

        public Book(String name, Icon cover, String description) {
            this.name = name;
            this.cover = cover;
            this.description = description;
        }
        public String getName() { return name; }
        public Icon getCover() { return cover; }
        public String getDescription() { return description; }
        @Override public String toString() { return name; }
    }

    public void init() {
        // 準備測試資料
        Book[] books = {
            new Book("Struts 2 權威指南", new ImageIcon("ico/struts2.jpg"), "全面介紹 Struts 2 核心知識"),
            new Book("輕量級 J2EE 企業應用實戰", new ImageIcon("ico/j2ee.jpg"), "Struts、Spring、Hibernate 整合開發"),
            new Book("基於 J2EE 的 Ajax 寶典", new ImageIcon("ico/ajax.jpg"), "J2EE 平台 Ajax 開發全方位指南")
        };

        JFrame frame = new JFrame("JSplitPane 示例:書籍瀏覽器");
        JList<Book> bookList = new JList<>(books);
        JLabel coverLabel = new JLabel();
        JTextArea descArea = new JTextArea();
        descArea.setEditable(false);
        descArea.setLineWrap(true);
        descArea.setWrapStyleWord(true);

        // 設定組件優先尺寸
        bookList.setPreferredSize(new Dimension(180, 400));
        coverLabel.setPreferredSize(new Dimension(300, 200));
        descArea.setPreferredSize(new Dimension(300, 200));

        // 列表選擇監聽器
        bookList.addListSelectionListener(e -> {
            if (!e.getValueIsAdjusting()) {
                Book selected = bookList.getSelectedValue();
                if (selected != null) {
                    coverLabel.setIcon(selected.getCover());
                    descArea.setText(selected.getDescription());
                }
            }
        });

        // 左側垂直分割:封面(上)+ 描述(下)
        JSplitPane leftPane = new JSplitPane(
            JSplitPane.VERTICAL_SPLIT, true, coverLabel, new JScrollPane(descArea));
        leftPane.setOneTouchExpandable(true);
        leftPane.setDividerLocation(200);
        leftPane.resetToPreferredSizes();

        // 整體水平分割:左側面板 + 右側列表
        JSplitPane mainPane = new JSplitPane(
            JSplitPane.HORIZONTAL_SPLIT, leftPane, new JScrollPane(bookList));
        mainPane.setOneTouchExpandable(true);
        mainPane.setDividerLocation(400);

        frame.add(mainPane);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new BookBrowser().init());
    }
}

二、JTabbedPane:標籤式多視圖切換

JTabbedPane 是實現多文檔/多功能頁面切換的標準組件,支援標籤位置(上下左右)、佈局策略(換行/滾動)、圖標、工具提示、動態加載內容等豐富功能。

關鍵配置

  • 標籤位置:TOP、BOTTOM、LEFT、RIGHT
  • 佈局策略:WRAP_TAB_LAYOUT(自動換行)與 SCROLL_TAB_LAYOUT(滾動箭頭)
  • 延遲加載:監聽 ChangeEvent,僅在標籤首次被選中時建立組件,提升啟動效能
  • 動態圖標切換:setIconAt(index, icon) 可視化標示已讀/未讀狀態

完整範例:動態標籤管理器

示範標籤位置/佈局切換、延遲加載圖片、自訂單選按鈕面板控制屬性。

public class DynamicTabDemo {
    private final JFrame frame = new JFrame("JTabbedPane 進階示例");
    private final JTabbedPane tabbedPane = new JTabbedPane(JTabbedPane.LEFT, JTabbedPane.WRAP_TAB_LAYOUT);
    private final Map<String, String> bookCovers = new LinkedHashMap<>();
    private final ImageIcon defaultIcon = new ImageIcon("ico/close.gif");
    private final ImageIcon openedIcon = new ImageIcon("ico/open.gif");

    public void init() {
        // 初始化書籍資料
        bookCovers.put("ROR 敏捷開發最佳實踐", "ror.jpg");
        bookCovers.put("Struts 2 權威指南", "struts2.jpg");
        bookCovers.put("基於 J2EE 的 Ajax 寶典", "ajax.jpg");
        bookCovers.put("輕量級 J2EE 企業應用實戰", "j2ee.jpg");
        bookCovers.put("Spring 2.0 寶典", "spring.jpg");

        // 建立標籤(初始組件為 null,延遲加載)
        for (String title : bookCovers.keySet()) {
            tabbedPane.addTab(title, defaultIcon, null, "點擊查看封面");
        }

        // 標籤切換監聽器:延遲加載內容
        tabbedPane.addChangeListener(e -> {
            int idx = tabbedPane.getSelectedIndex();
            if (tabbedPane.getComponentAt(idx) == null) {
                loadTabContent(idx);
            }
        });

        // 預載第一個標籤
        loadTabContent(0);

        // 控制面板:切換標籤位置與佈局策略
        JPanel controlPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
        controlPanel.setBorder(BorderFactory.createTitledBorder("標籤屬性控制"));

        String[] placements = {"頂部", "底部", "左側", "右側"};
        String[] layouts = {"換行佈局", "滾動佈局"};

        controlPanel.add(createRadioGroup("標籤位置", placements, placement -> {
            switch (placement) {
                case "頂部": tabbedPane.setTabPlacement(JTabbedPane.TOP); break;
                case "底部": tabbedPane.setTabPlacement(JTabbedPane.BOTTOM); break;
                case "左側": tabbedPane.setTabPlacement(JTabbedPane.LEFT); break;
                case "右側": tabbedPane.setTabPlacement(JTabbedPane.RIGHT); break;
            }
        }));

        controlPanel.add(createRadioGroup("佈局策略", layouts, layout -> {
            if ("換行佈局".equals(layout)) {
                tabbedPane.setTabLayoutPolicy(JTabbedPane.WRAP_TAB_LAYOUT);
            } else {
                tabbedPane.setTabLayoutPolicy(JTabbedPane.SCROLL_TAB_LAYOUT);
            }
        }));

        frame.add(tabbedPane, BorderLayout.CENTER);
        frame.add(controlPanel, BorderLayout.SOUTH);
        tabbedPane.setPreferredSize(new Dimension(600, 400));
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private void loadTabContent(int index) {
        String title = tabbedPane.getTitleAt(index);
        String imgFile = bookCovers.get(title);
        ImageIcon cover = new ImageIcon("ico/" + imgFile);
        JLabel label = new JLabel(cover);
        label.setHorizontalAlignment(SwingConstants.CENTER);
        tabbedPane.setComponentAt(index, new JScrollPane(label));
        tabbedPane.setIconAt(index, openedIcon);
    }

    private JPanel createRadioGroup(String title, String[] options, Consumer<String> handler) {
        JPanel panel = new JPanel();
        panel.setBorder(BorderFactory.createTitledBorder(title));
        panel.setLayout(new BoxLayout(panel, BoxLayout.Y_AXIS));
        ButtonGroup group = new ButtonGroup();
        for (int i = 0; i < options.length; i++) {
            JRadioButton rb = new JRadioButton(options[i]);
            rb.setActionCommand(options[i]);
            rb.addActionListener(e -> handler.accept(((JRadioButton) e.getSource()).getActionCommand()));
            rb.setSelected(i == 0);
            group.add(rb);
            panel.add(rb);
        }
        return panel;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new DynamicTabDemo().init());
    }
}

三、JLayeredPane:分層佈局與組件重疊

JLayeredPane 允許組件在 Z 軸方向重疊,透過「層級」控制繪製順序。預設層級常數(由低到高):DEFAULT_LAYER、PALETTE_LAYER、MODAL_LAYER、POPUP_LAYER、DRAG_LAYER。亦可使用整數自定義層級(數值越大越靠上)。

核心概念

  • 絕對定位:子組件必須呼叫 setBounds(x, y, w, h) 指定位置大小
  • 層級管理:add(comp, layer) 或 add(comp, intLayer)
  • 應用場景:浮動工具提示、拖放預覽、自訂彈出選單、遊戲精靈重疊

範例:重疊圖片卡片

public class LayeredPaneDemo {
    public void init() {
        JFrame frame = new JFrame("JLayeredPane 示例:重疊卡片");
        JLayeredPane layeredPane = new JLayeredPane();
        layeredPane.setPreferredSize(new Dimension(500, 350));

        // 新增三個不同層級的面板
        layeredPane.add(createCard(20, 30, "Struts 2 權威指南", "ico/struts2.jpg"), JLayeredPane.MODAL_LAYER);
        layeredPane.add(createCard(120, 80, "ROR 敏捷開發", "ico/ror.jpg"), JLayeredPane.DEFAULT_LAYER);
        layeredPane.add(createCard(220, 130, "輕量級 J2EE 實戰", "ico/j2ee.jpg"), 150); // 自定義層級

        frame.add(layeredPane);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private JPanel createCard(int x, int y, String title, String imgPath) {
        JPanel panel = new JPanel();
        panel.setBorder(BorderFactory.createTitledBorder(
            BorderFactory.createEtchedBorder(), title));
        panel.add(new JLabel(new ImageIcon(imgPath)));
        panel.setBounds(x, y, 180, 220);
        return panel;
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new LayeredPaneDemo().init());
    }
}

四、JDesktopPane 與 JInternalFrame:打造 MDI 應用

MDI(Multiple Document Interface) 允許在單一主視窗內管理多個子視窗。JDesktopPane 扮演「虛擬桌面」角色,JInternalFrame 則是可獨立最小化、最大化、關閉、調整大小的內部視窗。

關鍵功能

  • 內部視窗屬性:可調整大小、可關閉、可最大化、可最小化、可圖標化
  • 桌面管理:JDesktopPane 內建 DesktopManager 處理拖曳、調整大小、圖標化等行為
  • 視窗排列:級聯(cascadeWindows)、平鋪(tileWindows)、切換下一個視窗(selectNextWindow)
  • 拖曳模式:LIVE_DRAG_MODE(即時重繪)與 OUTLINE_DRAG_MODE(僅顯示輪廓)

完整範例:簡易 MDI 文字編輯器

包含選單列、工具列、新建/關閉內部視窗、視窗排列、拖曳模式切換。

public class MdiEditor {
    private static final int DESKTOP_W = 800;
    private static final int DESKTOP_H = 600;
    private static final int OFFSET = 30;

    private final JFrame frame = new JFrame("MDI 文字編輯器");
    private final CustomDesktopPane desktop = new CustomDesktopPane();
    private int nextX = 0, nextY = 0;
    private final int frameW = DESKTOP_W / 2;
    private final int frameH = DESKTOP_H / 2;

    public void init() {
        // 選單列
        JMenuBar menuBar = new JMenuBar();
        JMenu fileMenu = new JMenu("檔案(F)");
        JMenu windowMenu = new JMenu("視窗(W)");

        Action newAction = new AbstractAction("新建文檔", new ImageIcon("ico/new.png")) {
            @Override public void actionPerformed(ActionEvent e) { createInternalFrame(); }
        };
        Action exitAction = new AbstractAction("退出", new ImageIcon("ico/exit.png")) {
            @Override public void actionPerformed(ActionEvent e) { System.exit(0); }
        };

        fileMenu.add(newAction);
        fileMenu.addSeparator();
        fileMenu.add(exitAction);

        windowMenu.add(new JMenuItem(new AbstractAction("下一個視窗") {
            @Override public void actionPerformed(ActionEvent e) { desktop.selectNextWindow(); }
        }));
        windowMenu.add(new JMenuItem(new AbstractAction("級聯排列") {
            @Override public void actionPerformed(ActionEvent e) { desktop.cascadeWindows(OFFSET, 0.75); }
        }));
        windowMenu.add(new JMenuItem(new AbstractAction("平鋪排列") {
            @Override public void actionPerformed(ActionEvent e) { desktop.tileWindows(); }
        }));

        JCheckBoxMenuItem dragModeItem = new JCheckBoxMenuItem("僅顯示拖曳輪廓");
        dragModeItem.addActionListener(e -> desktop.setDragMode(
            dragModeItem.isSelected() ? JDesktopPane.OUTLINE_DRAG_MODE : JDesktopPane.LIVE_DRAG_MODE));
        windowMenu.add(dragModeItem);

        menuBar.add(fileMenu);
        menuBar.add(windowMenu);
        frame.setJMenuBar(menuBar);

        // 工具列
        JToolBar toolBar = new JToolBar();
        toolBar.add(newAction);
        toolBar.add(exitAction);
        frame.add(toolBar, BorderLayout.NORTH);

        // 桌面面板
        desktop.setPreferredSize(new Dimension(DESKTOP_W, DESKTOP_H));
        frame.add(desktop, BorderLayout.CENTER);

        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setLocationRelativeTo(null);
        frame.setVisible(true);
    }

    private void createInternalFrame() {
        JInternalFrame iframe = new JInternalFrame("未命名文檔 " + (desktop.getAllFrames().length + 1),
            true, true, true, true);
        iframe.add(new JScrollPane(new JTextArea()));
        desktop.add(iframe);
        iframe.reshape(nextX, nextY, frameW, frameH);
        try { iframe.setSelected(true); } catch (PropertyVetoException ignored) {}
        iframe.setVisible(true);

        nextX += OFFSET; nextY += OFFSET;
        if (nextX + frameW > desktop.getWidth()) nextX = 0;
        if (nextY + frameH > desktop.getHeight()) nextY = 0;
    }

    // 自訂桌面面板:提供級聯、平鋪、切換視窗邏輯
    private static class CustomDesktopPane extends JDesktopPane {
        public void cascadeWindows(int offset, double scale) {
            int w = (int) (getWidth() * scale);
            int h = (int) (getHeight() * scale);
            int x = 0, y = 0;
            for (JInternalFrame f : getAllFrames()) {
                try {
                    f.setMaximum(false); f.setIcon(false);
                    f.reshape(x, y, w, h);
                    x += offset; y += offset;
                    if (x + w > getWidth()) x = 0;
                    if (y + h > getHeight()) y = 0;
                } catch (PropertyVetoException ignored) {}
            }
        }

        public void tileWindows() {
            JInternalFrame[] frames = getAllFrames();
            int n = frames.length;
            if (n == 0) return;
            int rows = (int) Math.sqrt(n);
            int cols = n / rows;
            int extra = n % rows;
            int w = getWidth() / cols;
            int h = getHeight() / rows;
            int x = 0, y = 0;
            for (int i = 0; i < n; i++) {
                JInternalFrame f = frames[i];
                try {
                    f.setMaximum(false); f.setIcon(false);
                    f.reshape(x * w, y * h, w, h);
                    y++;
                    if (y == rows) {
                        y = 0; x++;
                        if (extra == cols - x) { rows++; h = getHeight() / rows; }
                    }
                } catch (PropertyVetoException ignored) {}
            }
        }

        public void selectNextWindow() {
            JInternalFrame[] frames = getAllFrames();
            for (int i = 0; i < frames.length; i++) {
                if (frames[i].isSelected()) {
                    int next = (i + 1) % frames.length;
                    while (next != i) {
                        if (!frames[next].isIcon()) {
                            try {
                                frames[next].setSelected(true);
                                frames[next].toFront();
                                frames[i].toBack();
                                return;
                            } catch (PropertyVetoException ignored) {}
                        }
                        next = (next + 1) % frames.length;
                    }
                }
            }
        }
    }

    public static void main(String[] args) {
        SwingUtilities.invokeLater(() -> new MdiEditor().init());
    }
}

總結與最佳實踐

容器適用場景關鍵提示
JSplitPane可調整比例的雙區域佈局(如導航+內容)善用 resetToPreferredSizes() 與 setOneTouchExpandable(true)
JTabbedPane多功能頁面切換、設定面板、多文檔介面延遲加載標籤內容;動態切換圖標提升 UX
JLayeredPane組件重疊、浮動元素、自訂彈出層必須使用絕對定位(setBounds);層級數值越大越靠上
JDesktopPane + JInternalFrameMDI 應用、多視窗編輯器、IDE 風格介面繼承 JDesktopPane 封裝級聯/平鋪/切換邏輯;善用 DesktopManager 自訂拖曳行為

這四大特殊容器是構建專業級 Swing 應用的基石。建議先從 JSplitPane 與 JTabbedPane 入手,掌握基本佈局與事件處理後,再挑戰 JLayeredPane 的分層藝術與 MDI 的完整視窗管理。實作過程中請參考官方 API 文檔深入了解各屬性與方法的細節。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved