JFace ApplicationWindow 為獨立視窗應用提供標準化框架:選單列、工具列、狀態列、內容區域自動佈局。本文解析一個 FileViewer 範例,展示檔案載入、非同步進度回報(IRunnableWithProgress)、行數統計與資源管理完整流程。
public class FileViewer extends ApplicationWindow {
Text text; String content; String lineDelimiter;
public FileViewer(Shell parentShell) {
super(parentShell);
addMenuBar(); addStatusLine(); addToolBar(SWT.FLAT);
}
protected Control createContents(Composite parent) {
getShell().setText("FileViewer v2.0");
setStatus("Ready");
text = new Text(parent, SWT.MULTI | SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL);
text.setSize(300, 200);
return text; // 回傳內容區域控制項
}
}
addMenuBar()、addToolBar()、addStatusLine():啟用標準 UI 區域createContents:建立客戶區內容,此處為多行 TextcreateMenuManager、createToolBarManager、createStatusLineManager:覆寫以自訂動作Action actionOpenFile = new Action("Open", ImageDescriptor.createFromFile(null, "icons/open.gif")) {
public void run() {
FileDialog dialog = new FileDialog(getShell(), SWT.OPEN);
String file = dialog.open();
if (file != null) {
try {
String content = readFileAsAString(new File(file));
text.setText(content);
setStatus("File loaded successfully: " + file);
} catch (IOException e) {
e.printStackTrace(); setStatus("Failed to load file: " + file);
}
}
}
};
IRunnableWithProgress runnableWithProgress = monitor -> {
System.out.println("Running from thread: " + Thread.currentThread().getName());
// 取得文字內容需在 UI 執行緒
getShell().getDisplay().syncExec(() -> {
content = text.getText();
lineDelimiter = text.getLineDelimiter();
});
monitor.beginTask("Counting total number of lines", content.length());
int lines = 1;
for (int i = 0; i < content.length(); i++) {
if (monitor.isCanceled()) { monitor.done(); System.out.println("Action cancelled"); return; }
if (i + lineDelimiter.length() < content.length() &&
lineDelimiter.equals(content.substring(i, i + lineDelimiter.length()))) {
lines++;
}
monitor.worked(1);
Thread.sleep(1); // 模擬耗時運算
}
monitor.done();
System.out.println("Total number of lines: " + lines);
};
Action actionCount = new Action("Count", ImageDescriptor.createFromFile(null, "icons/run.gif")) {
public void run() {
try {
// fork=true: 背景執行緒; cancelable=true: 可取消; runnable: 任務
FileViewer.this.run(true, true, runnableWithProgress);
} catch (InvocationTargetException | InterruptedException e) { e.printStackTrace(); }
}
};
run(),不阻塞 UI;進度對話框顯示於前景。Text.getText() 必須在 UI 執行緒呼叫,故用 display.syncExec 切回主執行緒。monitor.done()。beginTask(taskName, totalWork) 設定總工作量。
protected MenuManager createMenuManager() {
MenuManager menuManager = new MenuManager("");
MenuManager fileMenu = new MenuManager("&File");
fileMenu.add(actionOpenFile);
menuManager.add(fileMenu);
MenuManager toolsMenu = new MenuManager("&Tools");
toolsMenu.add(actionCount);
menuManager.add(toolsMenu);
return menuManager;
}
protected ToolBarManager createToolBarManager(int style) {
ToolBarManager mgr = new ToolBarManager(style);
mgr.add(actionOpenFile); mgr.add(actionCount);
return mgr;
}
public static String readFileAsAString(File file) throws IOException {
return new String(getBytesFromFile(file));
}
public static byte[] getBytesFromFile(File file) throws IOException {
try (InputStream is = new FileInputStream(file)) {
long length = file.length();
if (length > Integer.MAX_VALUE)
throw new IllegalArgumentException("File too large (>= 2GB)");
byte[] bytes = new byte[(int) length];
int offset = 0, numRead;
while (offset < bytes.length && (numRead = is.read(bytes, offset, bytes.length - offset)) >= 0)
offset += numRead;
if (offset < bytes.length)
throw new IOException("Could not completely read file " + file.getName());
return bytes;
}
}
使用 try-with-resources 確保流關閉;分塊讀取避免大檔案單次分配記憶體壓力(雖此處仍一次性分配位元組陣列,超大檔案建議改用串流處理)。
public static void main(String[] args) {
ApplicationWindow viewer = new FileViewer(null);
viewer.setBlockOnOpen(true); // 阻塞直到視窗關閉
viewer.open();
}
| 特性 | 說明 |
|---|---|
| 標準化 UI 區域 | 選單列、工具列、狀態列、內容區自動佈局 |
| 動作貢獻模型 | Action 一次定義,同時出現在選單/工具列/右鍵選單 |
| 進度回報整合 | run(fork, cancelable, runnable) 內建進度對話框與取消支援 |
| 生命週期管理 | open()/close()、setBlockOnOpen 封裝事件迴圈 |
| 狀態列管理 | StatusLineManager 統一管理狀態文字、進度條 |
SWTException: Invalid thread access。Text.setText 會一次載入全部字串,超大檔案會導致 OOM,建議改用 StyledText 配合虛擬載入或分頁顯示。ImageDescriptor.createFromFile(FileViewer.class, "icons/open.gif") 支援 JAR 內資源,部署更彈性。本範例展示了 JFace ApplicationWindow 快速構建標準視窗應用的能力,結合 IRunnableWithProgress 實現非同步任務與進度回報。開發者可在此基礎上擴充:編輯功能、語法高亮、多標籤、最近開啟清單等,打造完整的文字編輯/檢視工具。