雖然現代 Java 桌面開發主流已轉向 JavaFX/Swing,但 java.awt 作為基礎圖形庫,理解其**事件委派模型、組件層次、原生對等體機制**對掌握 Java GUI 本質極有幫助。本文提供一個功能完整的 AWT 記事本,涵蓋菜單欄、文件開啟/儲存、全選/複製/剪貼/貼上、視窗關閉等核心功能。
import java.awt.*;
import java.awt.datatransfer.*;
import java.awt.event.*;
import java.io.*;
public class SimpleNotepad extends Frame implements ActionListener {
private static final long serialVersionUID = 1L;
// UI 組件
private final TextArea textArea = new TextArea();
private final MenuBar menuBar = new MenuBar();
private final Menu fileMenu = new Menu("File");
private final Menu editMenu = new Menu("Edit");
// File 菜單項
private final MenuItem newItem = new MenuItem("New");
private final MenuItem openItem = new MenuItem("Open");
private final MenuItem saveItem = new MenuItem("Save");
private final MenuItem saveAsItem = new MenuItem("Save As");
private final MenuItem exitItem = new MenuItem("Exit");
// Edit 菜單項
private final MenuItem selectAllItem = new MenuItem("Select All");
private final MenuItem copyItem = new MenuItem("Copy");
private final MenuItem cutItem = new MenuItem("Cut");
private final MenuItem pasteItem = new MenuItem("Paste");
// 狀態
private String fileName = null;
private final Toolkit toolkit = Toolkit.getDefaultToolkit();
private final Clipboard clipboard = toolkit.getSystemClipboard();
// 文件對話框 (AWT 原生)
private final FileDialog openDialog = new FileDialog(this, "Open File", FileDialog.LOAD);
private final FileDialog saveDialog = new FileDialog(this, "Save File As", FileDialog.SAVE);
public SimpleNotepad() {
initUI();
initEvents();
}
private void initUI() {
setTitle("簡易記事本 - AWT 版");
setFont(new Font("Monospaced", Font.PLAIN, 14));
setBackground(Color.WHITE);
setSize(600, 450);
setLocationRelativeTo(null); // 居中 (需 JDK 1.4+)
// 構建菜單
fileMenu.add(newItem);
fileMenu.add(openItem);
fileMenu.addSeparator();
fileMenu.add(saveItem);
fileMenu.add(saveAsItem);
fileMenu.addSeparator();
fileMenu.add(exitItem);
editMenu.add(selectAllItem);
editMenu.addSeparator();
editMenu.add(copyItem);
editMenu.add(cutItem);
editMenu.add(pasteItem);
menuBar.add(fileMenu);
menuBar.add(editMenu);
setMenuBar(menuBar);
add(textArea, BorderLayout.CENTER); // Frame 默認 BorderLayout
}
private void initEvents() {
// 視窗關閉
addWindowListener(new WindowAdapter() {
@Override public void windowClosing(WindowEvent e) {
exitApplication();
}
});
// 統一事件監聽
MenuItem[] items = {newItem, openItem, saveItem, saveAsItem, exitItem,
selectAllItem, copyItem, cutItem, pasteItem};
for (MenuItem item : items) {
item.addActionListener(this);
}
}
@Override
public void actionPerformed(ActionEvent e) {
Object src = e.getSource();
if (src == newItem) {
newFile();
} else if (src == openItem) {
openFile();
} else if (src == saveItem) {
saveFile();
} else if (src == saveAsItem) {
saveAsFile();
} else if (src == exitItem) {
exitApplication();
} else if (src == selectAllItem) {
textArea.selectAll();
} else if (src == copyItem) {
copyToClipboard();
} else if (src == cutItem) {
cutToClipboard();
} else if (src == pasteItem) {
pasteFromClipboard();
}
}
// === 文件操作 ===
private void newFile() {
textArea.setText("");
fileName = null;
setTitle("簡易記事本 - AWT 版");
}
private void openFile() {
openDialog.setVisible(true); // 阻塞直到用戶選擇
String dir = openDialog.getDirectory();
String file = openDialog.getFile();
if (dir != null && file != null) {
fileName = dir + file;
readFile(fileName);
setTitle("簡易記事本 - " + file);
}
}
private void saveFile() {
if (fileName != null) {
writeFile(fileName);
} else {
saveAsFile();
}
}
private void saveAsFile() {
saveDialog.setVisible(true);
String dir = saveDialog.getDirectory();
String file = saveDialog.getFile();
if (dir != null && file != null) {
fileName = dir + file;
writeFile(fileName);
setTitle("簡易記事本 - " + file);
}
}
private void readFile(String path) {
try (FileReader reader = new FileReader(path)) {
char[] buffer = new char[8192];
StringBuilder content = new StringBuilder();
int len;
while ((len = reader.read(buffer)) != -1) {
content.append(buffer, 0, len);
}
textArea.setText(content.toString());
} catch (IOException ex) {
showError("讀取文件失敗: " + ex.getMessage());
}
}
private void writeFile(String path) {
try (FileWriter writer = new FileWriter(path)) {
writer.write(textArea.getText());
} catch (IOException ex) {
showError("儲存文件失敗: " + ex.getMessage());
}
}
// === 剪貼簿操作 ===
private void copyToClipboard() {
String selected = textArea.getSelectedText();
if (selected != null && !selected.isEmpty()) {
StringSelection selection = new StringSelection(selected);
clipboard.setContents(selection, null);
}
}
private void cutToClipboard() {
copyToClipboard();
textArea.replaceRange("", textArea.getSelectionStart(), textArea.getSelectionEnd());
}
private void pasteFromClipboard() {
Transferable contents = clipboard.getContents(this);
if (contents != null && contents.isDataFlavorSupported(DataFlavor.stringFlavor)) {
try {
String text = (String) contents.getTransferData(DataFlavor.stringFlavor);
textArea.replaceRange(text, textArea.getSelectionStart(), textArea.getSelectionEnd());
} catch (Exception ex) {
showError("貼上失敗: " + ex.getMessage());
}
}
}
private void exitApplication() {
System.exit(0);
}
private void showError(String msg) {
// AWT 無標準對話框,簡單輸出到控制台
System.err.println(msg);
// 實際專案可用 new Dialog(this, "錯誤", true) 自定義錯誤彈窗
}
public static void main(String[] args) {
// AWT 事件分發線程啟動 (可選但推薦)
EventQueue.invokeLater(() -> {
SimpleNotepad notepad = new SimpleNotepad();
notepad.setVisible(true);
});
}
}
ActionListener + actionPerformed 處理菜單/按鈕點擊。WindowAdapter 適配器模式簡化 WindowListener (僅覆寫 windowClosing)。EventQueue.invokeLater 啟動。openDialog.setVisible(true); // 當前線程阻塞,直到用戶關閉對話框 String file = openDialog.getFile(); // 獲取選擇結果
這是 AWT 簡單同步 API 設計,現代 Swing/JavaFX 採用異步回調模式。
Toolkit.getDefaultToolkit().getSystemClipboard() 獲取系統剪貼簿。StringSelection 實現 Transferable,封裝純文本。DataFlavor.stringFlavor 標識 MIME 類型 text/plain; charset=utf-16。getSelectedText() / getSelectionStart() / getSelectionEnd() 獲取選區。replaceRange(str, start, end) 替換指定範圍文本 (實現剪切/貼上)。selectAll() 全選。| 特性 | AWT (本例) | Swing | JavaFX |
|---|---|---|---|
| 組件豐富度 | 基礎 (無表格/樹) | 豐富 (JTable, JTree, JTabbedPane...) | 極豐富 (TableView, TreeView, WebView, 3D...) |
| 外觀一致性 | 原生 (平台差異大) | 可換膚 (Metal, Nimbus, 系統風格) | CSS 樣式化、跨平台高度一致 |
| 繪圖管線 | Graphics (即時模式) | Graphics2D (保留模式、雙緩衝) | Prism (GPU 加速、場景圖) |
| 事件模型 | 委派模型 (1.1+) | 同 AWT,擴展 EventListenerList | 觀察者模式 + 屬性綁定 |
| 線程模型 | 單一 EDT | 單一 EDT + SwingWorker | JavaFX Application Thread + Task/Service |
| 現代狀態 | 維護模式 (JDK 內建) | 維護模式 (JDK 內建,Java 9+ 模組化) | **主流推薦** (OpenJFX 獨立發布) |
UndoManager (Swing) 或自建命令棧。TextArea.getText() 正則搜尋,高亮需自繪或遷移 Swing JTextArea + Highlighter。TextArea 或 Canvas 繪製行號。JTextPane + StyledDocument 或 JavaFX CodeArea (RichTextFX)。CardLayout (AWT) 或 JTabbedPane (Swing)。這個 200 行左右的 AWT 記事本,雖然功能簡單,卻完整展示了 Java GUI 程式設計的**骨架**:視窗容器、佈局管理、菜單系統、事件迴路、原生對話框、文件 I/O、系統剪貼簿。理解它,就是理解 Java 桌面開發的根基。