表格內嵌編輯是桌面應用常見需求。SWT 提供 TableEditor 實現單元格級編輯器(文字框、核取方塊等);JFace TableViewer 則提供更高層抽象:CellEditor、ICellModifier、ViewerFilter、ViewerSorter。本文透過 BugTracker(純 SWT)與 BugTrackerJFace 兩個版本,深度對比兩種架構的實作差異與優劣。
Table table = new Table(shell, SWT.BORDER | SWT.FULL_SELECTION);
TableColumn tcID = new TableColumn(table, SWT.LEFT); tcID.setText("ID"); tcID.setWidth(60);
TableColumn tcSummary = new TableColumn(table, SWT.NULL); tcSummary.setText("Summary"); tcSummary.setWidth(200);
TableColumn tcAssigned = new TableColumn(table, SWT.NULL); tcAssigned.setText("Assigned to"); tcAssigned.setWidth(80);
TableColumn tcSolved = new TableColumn(table, SWT.NULL); tcSolved.setText("Solved"); tcSolved.setWidth(50);
final TableEditor editor = new TableEditor(table);
table.addListener(SWT.MouseDown, e -> {
Point pt = new Point(e.x, e.y);
TableItem item = table.getItem(pt);
if (item == null) return;
int column = -1;
for (int i = 0; i < table.getColumnCount(); i++)
if (item.getBounds(i).contains(pt)) column = i;
if (column == -1) return;
if (column == 3) { // Solved 欄:核取方塊
Button btn = new Button(table, SWT.CHECK);
btn.setSelection(item.getText(3).equalsIgnoreCase("YES"));
editor.horizontalAlignment = SWT.LEFT;
editor.grabHorizontal = true;
editor.setEditor(btn, item, column);
btn.addListener(SWT.FocusOut, ev -> { item.setText(3, btn.getSelection()?"YES":"NO"); btn.dispose(); });
btn.addListener(SWT.Traverse, ev -> { if (ev.detail==SWT.TRAVERSE_RETURN||ev.detail==SWT.TRAVERSE_ESCAPE) { item.setText(3, btn.getSelection()?"YES":"NO"); btn.dispose(); ev.doit=false; }});
btn.setFocus();
} else if (column < 3) { // 文字欄:文字框
Text txt = new Text(table, SWT.NONE);
txt.setText(item.getText(column));
editor.horizontalAlignment = SWT.LEFT; editor.grabHorizontal = true;
editor.setEditor(txt, item, column);
txt.addListener(SWT.FocusOut, ev -> { item.setText(column, txt.getText()); txt.dispose(); });
txt.addListener(SWT.Traverse, ev -> { if (ev.detail==SWT.TRAVERSE_RETURN||ev.detail==SWT.TRAVERSE_ESCAPE) { txt.dispose(); ev.doit=false; }});
txt.setFocus();
}
});
關鍵點:
TableEditor 一次只能編輯一個單元格,setEditor(control, item, column) 綁定位置。grabHorizontal=true 讓編輯器寬度跟隨欄位寬度。FocusOut)或按 Enter/Esc(Traverse)時寫回資料並銷毀編輯器。
Listener sortListener = e -> {
if (!(e.widget instanceof TableColumn)) return;
TableColumn tc = (TableColumn) e.widget;
sortTable(table, table.indexOf(tc));
};
for (int i=0; i i.getText(colIndex)));
// 重建項目順序(TableItem 不支援直接移動)
for (TableItem old : items) {
TableItem nu = new TableItem(table, SWT.NULL);
for (int j=0; j
private void saveBugs() {
try (DataOutputStream out = new DataOutputStream(new FileOutputStream("bugs.dat"))) {
for (int i=0; i
public static class Bug {
public String id, summary, assignedTo; public boolean isSolved;
public Bug(String id, String summary, String assignedTo, boolean solved) { ... }
public static Vector loadBugs(File file) { ... } // 讀取 bugs.dat 回傳 Vector
}
tableViewer.setContentProvider(new IStructuredContentProvider() {
public Object[] getElements(Object input) { return ((Vector)input).toArray(); }
public void dispose() {} public void inputChanged(Viewer v, Object old, Object neu) {}
});
tableViewer.setLabelProvider(new ITableLabelProvider() {
public Image getColumnImage(Object element, int col) { return col==0 ? bugIcon : null; }
public String getColumnText(Object element, int col) {
Bug b = (Bug) element;
return switch(col) { case 0 -> b.id; case 1 -> b.summary; case 2 -> b.assignedTo; case 3 -> b.isSolved?"YES":"NO"; default -> null; };
}
// 其他介面方法空實作
});
CellEditor[] editors = new CellEditor[4];
editors[0] = editors[1] = editors[2] = new TextCellEditor(table);
editors[3] = new CheckboxCellEditor(table);
tableViewer.setCellEditors(editors);
tableViewer.setColumnProperties(new String[]{"ID","Summary","Assigned to","Solved"});
tableViewer.setCellModifier(new ICellModifier() {
public boolean canModify(Object element, String property) { return true; }
public Object getValue(Object element, String property) {
Bug b = (Bug) element;
return switch(property) { case "ID" -> b.id; case "Summary" -> b.summary; case "Assigned to" -> b.assignedTo; case "Solved" -> b.isSolved; default -> null; };
}
public void modify(Object element, String property, Object value) {
Bug b = (element instanceof TableItem) ? (Bug)((TableItem)element).getData() : (Bug)element;
switch(property) { case "ID" -> b.id=(String)value; case "Summary" -> b.summary=(String)value; case "Assigned to" -> b.assignedTo=(String)value; case "Solved" -> b.isSolved=(Boolean)value; }
tableViewer.update(b, null); // 通知 Viewer 重繪該元素
}
});
優勢: 編輯邏輯作用於領域物件(Bug),而非 UI 元件(TableItem),資料與視圖分離。
ViewerFilter filter = new ViewerFilter() {
public boolean select(Viewer v, Object parent, Object element) { return !((Bug)element).isSolved; }
};
Action actionShowUnsolved = new Action("Show unsolved only", IAction.AS_CHECK_BOX) {
public void run() { if (isChecked()) tableViewer.addFilter(filter); else tableViewer.removeFilter(filter); }
};
class BugSorter extends ViewerSorter {
private int propIndex;
public BugSorter(String prop) { for(int i=0;i b1.id.compareTo(b2.id); case 1 -> b1.summary.compareTo(b2.summary); case 2 -> b1.assignedTo.compareTo(b2.assignedTo); case 3 -> Boolean.compare(b1.isSolved, b2.isSolved); default -> 0; };
}
}
tcID.addListener(SWT.Selection, e -> tableViewer.setSorter(new BugSorter(colNames[0])));
// ... 其他欄位同理
bugs = Bug.loadBugs(new File("bugs.dat"));
tableViewer.setInput(bugs); // 觸發 ContentProvider.getElements
| 維度 | SWT TableEditor | JFace TableViewer |
|---|---|---|
| 資料模型 | 隱含在 TableItem 文字/圖示 | 獨立領域物件 |
| 編輯器管理 | 手動 TableEditor + Listener | CellEditor 陣列 + ICellModifier |
| 排序 | 手動 Comparator + 重建 TableItem | ViewerSorter + setSorter |
| 過濾 | 手動隱藏/移除項目 | ViewerFilter + addFilter/removeFilter |
| 資料同步 | 開發者自行維護 | update(element) 自動重繪 |
| 程式碼量 | 較多(UI 邏輯混雜) | 較少(關注點分離) |
| 學習曲線 | 低(直觀) | 中(需理解 Viewer 架構) |
| 擴充性 | 差(大量複製貼上) | 優(可複用 Provider/Editor/Filter/Sorter) |
TableEditor 快速交付。TableViewer 架構,長期收益顯著。TableViewer 僅作為顯示層,保留 TableEditor 編輯,逐步替換。本文透過 BugTracker 雙版本對比,展示了從「UI 綁定資料」到「資料驅動 UI」的架構演進。JFace Viewer 框架雖有學習門檻,但其 ContentProvider/LabelProvider/CellEditor/Filter/Sorter 的關注點分離設計,能大幅降低複雜表格應用的維護成本,是專業 SWT/JFace 開發者的必備技能。