SWT 為了輕量級、跨平台一致性,未內建 AWT/Swing 的 BorderLayout。但許多遷移專案或習慣「北/南/東/西/中」五區佈局的開發者需要此功能。本文提供完整的 BorderLayout 移植實現,包含 BorderData 佈局數據、五區域常數、尺寸計算與佈局邏輯,並附測試範例。
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();
}
}
computeSize()(返回建議尺寸)與 layout()(實際定位)。flushCache 參數指示是否重新計算緩存尺寸。setLayoutData(new BorderData(區域)) 聲明所屬區域。layout() 中讀取此數據決定定位邏輯。這確保北/南橫條佔據全寬,西/東直條在剩餘高度內定位,中間填滿最後剩餘區域。這正是 BorderLayout 的經典語義。
computeSize(SWT.DEFAULT, SWT.DEFAULT, true)(首選尺寸)。| 特性 | AWT BorderLayout | SWT 自定義版 |
|---|---|---|
| 區域常數 | NORTH, SOUTH, EAST, WEST, CENTER | 相同 |
| LayoutData 類 | 無(用 String 約束) | BorderData 類(型別安全) |
| 間距/邊距支援 | hgap, vgap | 未實現(可擴展) |
| 多組件同區域 | 後者覆蓋前者 | 同樣覆蓋(建議用 Composite 容納) |
| 動態增減 | 自動重新佈局 | 需呼叫 parent.layout() |
BorderLayout 增加 hgap, vgap 屬性,在 layout() 中應用。controls 改為 List[] ,區域內再套用 RowLayout/GridLayout。minWidth/minHeight 防止中心區域過小。透過繼承 Layout 並實現 computeSize/layout,我們成功將 AWT 經典 BorderLayout 移植到 SWT:
BorderData 替代字串約束。此實現證明了 SWT 佈局機制的開放性:任何佈局邏輯皆可透過自定義 Layout 實現。