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

SWT/JFace 自定義 BorderLayout 實現:移植 AWT 經典佈局到 SWT

     編輯:Java 相關知識

前言

SWT 為了輕量級、跨平台一致性,未內建 AWT/Swing 的 BorderLayout。但許多遷移專案或習慣「北/南/東/西/中」五區佈局的開發者需要此功能。本文提供完整的 BorderLayout 移植實現,包含 BorderData 佈局數據、五區域常數、尺寸計算與佈局邏輯,並附測試範例。

核心實現:BorderLayout.java

package swt_jface.demo2;

import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.graphics.Rectangle;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Layout;

public class BorderLayout extends Layout {
    // 五大區域常數
    public static final int NORTH = 0;
    public static final int SOUTH = 1;
    public static final int CENTER = 2;
    public static final int EAST = 3;
    public static final int WEST = 4;

    // 佈局數據類
    public static class BorderData {
        public int region = CENTER;
        public BorderData() {}
        public BorderData(int region) { this.region = region; }
    }

    // 內部追蹤五區域控件
    private Control[] controls = new Control[5];
    private Point[] sizes;
    private int width, height;

    // 計算父容器建議尺寸
    @Override
    protected Point computeSize(Composite composite, int wHint, int hHint, boolean flushCache) {
        if (sizes == null || flushCache) refreshSizes(composite.getChildren());
        int w = (wHint == SWT.DEFAULT) ? width : wHint;
        int h = (hHint == SWT.DEFAULT) ? height : hHint;
        return new Point(w, h);
    }

    // 核心佈局邏輯
    @Override
    protected void layout(Composite composite, boolean flushCache) {
        if (flushCache || sizes == null) refreshSizes(composite.getChildren());
        
        Rectangle clientArea = composite.getClientArea();
        int x = clientArea.x, y = clientArea.y;
        int w = clientArea.width, h = clientArea.height;

        // NORTH: 頂部橫條
        if (controls[NORTH] != null) {
            controls[NORTH].setBounds(x, y, w, sizes[NORTH].y);
            y += sizes[NORTH].y;
            h -= sizes[NORTH].y;
        }

        // SOUTH: 底部橫條
        if (controls[SOUTH] != null) {
            int southH = sizes[SOUTH].y;
            controls[SOUTH].setBounds(x, y + h - southH, w, southH);
            h -= southH;
        }

        // WEST: 左側直條
        if (controls[WEST] != null) {
            int westW = sizes[WEST].x;
            controls[WEST].setBounds(x, y, westW, h);
            x += westW;
            w -= westW;
        }

        // EAST: 右側直條
        if (controls[EAST] != null) {
            int eastW = sizes[EAST].x;
            controls[EAST].setBounds(x + w - eastW, y, eastW, h);
            w -= eastW;
        }

        // CENTER: 填滿剩餘
        if (controls[CENTER] != null) {
            controls[CENTER].setBounds(x, y, w, h);
        }
    }

    // 掃描子控件,收集 BorderData 並計算尺寸
    private void refreshSizes(Control[] children) {
        // 重置
        for (int i = 0; i < controls.length; i++) controls[i] = null;
        
        // 分配區域
        for (Control child : children) {
            Object layoutData = child.getLayoutData();
            if (!(layoutData instanceof BorderData)) continue;
            BorderData bd = (BorderData) layoutData;
            if (bd.region < 0 || bd.region > 4) continue;
            controls[bd.region] = child;
        }

        // 計算各區域首選尺寸
        if (sizes == null) sizes = new Point[5];
        width = 0; height = 0;
        
        for (int i = 0; i < controls.length; i++) {
            Control c = controls[i];
            sizes[i] = (c == null) ? new Point(0, 0) : c.computeSize(SWT.DEFAULT, SWT.DEFAULT, true);
        }

        // 整體寬度:max(北寬, 西寬+中寬+東寬, 南寬)
        width = Math.max(width, sizes[NORTH].x);
        width = Math.max(width, sizes[WEST].x + sizes[CENTER].x + sizes[EAST].x);
        width = Math.max(width, sizes[SOUTH].x);

        // 整體高度:max(西高, 東高, 中高) + 北高 + 南高
        height = Math.max(Math.max(sizes[WEST].y, sizes[EAST].y), sizes[CENTER].y)
               + sizes[NORTH].y + sizes[SOUTH].y;
    }
}

測試範例:五區域展示

public class BorderLayoutSample {
    public static void main(String[] args) {
        Display display = new Display();
        Shell shell = new Shell(display);
        shell.setText("BorderLayout Demo");
        shell.setLayout(new BorderLayout());

        // West
        Button west = new Button(shell, SWT.PUSH);
        west.setText("West");
        west.setLayoutData(new BorderLayout.BorderData(BorderLayout.WEST));

        // East
        Button east = new Button(shell, SWT.PUSH);
        east.setText("East");
        east.setLayoutData(new BorderLayout.BorderData(BorderLayout.EAST));

        // North
        Button north = new Button(shell, SWT.PUSH);
        north.setText("North");
        north.setLayoutData(new BorderLayout.BorderData(BorderLayout.NORTH));

        // South
        Button south = new Button(shell, SWT.PUSH);
        south.setText("South");
        south.setLayoutData(new BorderLayout.BorderData(BorderLayout.SOUTH));

        // Center
        Text center = new Text(shell, SWT.MULTI | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL);
        center.setText("Center Area\nFills remaining space");
        center.setLayoutData(new BorderLayout.BorderData(BorderLayout.CENTER));

        shell.pack();
        shell.open();
        while (!shell.isDisposed()) {
            if (!display.readAndDispatch()) display.sleep();
        }
        display.dispose();
    }
}

設計要點解析

1. 繼承 Layout 抽象類

  • 必須實現 computeSize()(返回建議尺寸)與 layout()(實際定位)。
  • flushCache 參數指示是否重新計算緩存尺寸。

2. BorderData 作為 LayoutData

  • 每個子控件通過 setLayoutData(new BorderData(區域)) 聲明所屬區域。
  • 佈局管理器在 layout() 中讀取此數據決定定位邏輯。

3. 佈局順序關鍵:北/南優先,再西/東,最後中

這確保北/南橫條佔據全寬,西/東直條在剩餘高度內定位,中間填滿最後剩餘區域。這正是 BorderLayout 的經典語義。

4. 尺寸計算策略

  • 各區域尺寸取 computeSize(SWT.DEFAULT, SWT.DEFAULT, true)(首選尺寸)。
  • 整體寬高取最大組合,確保所有區域內容可見。

與 AWT BorderLayout 差異對比

特性AWT BorderLayoutSWT 自定義版
區域常數NORTH, SOUTH, EAST, WEST, CENTER相同
LayoutData 類無(用 String 約束)BorderData 類(型別安全)
間距/邊距支援hgap, vgap未實現(可擴展)
多組件同區域後者覆蓋前者同樣覆蓋(建議用 Composite 容納)
動態增減自動重新佈局需呼叫 parent.layout()

擴展建議

  1. 支援間距:在 BorderLayout 增加 hgap, vgap 屬性,在 layout() 中應用。
  2. 支援多組件同區域:將 controls 改為 List[],區域內再套用 RowLayout/GridLayout。
  3. 最小尺寸約束:增加 minWidth/minHeight 防止中心區域過小。
  4. 整合到專案工具庫:放入共用 UI 模組,供所有 SWT 專案復用。

適用場景

  • AWT/Swing 遷移專案,保持佈局邏輯不變。
  • 經典「頂部工具列、底部狀態列、左側導航、右側屬性、中間內容」介面結構。
  • 簡單對話框:北=標題、南=按鈕、中=表單。

總結

透過繼承 Layout 並實現 computeSize/layout,我們成功將 AWT 經典 BorderLayout 移植到 SWT:

  • 型別安全:BorderData 替代字串約束。
  • 語義一致:五區域定位邏輯完全對齊 AWT 行為。
  • 輕量級:無額外依賴,單檔案即可整合。
  • 可擴展:易於增加間距、多組件、最小尺寸等功能。

此實現證明了 SWT 佈局機制的開放性:任何佈局邏輯皆可透過自定義 Layout 實現。

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