課程設計作品:模擬 Windows 記事本功能的 Swing 桌面應用。包含檔案 (新建/開啟/儲存/另存為/結束)、編輯 (撤銷/剪貼簿/搜尋取代/全選/時間戳)、格式 (自動換行/字體/顏色)、說明 (關於) 完整選單系統、右鍵彈出選單、狀態列、捲軸文本區。本篇著重 GUI 建構與事件適配器註冊;核心邏輯見下篇。
Jsb 結構
package jeason;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import javax.swing.event.*;
import java.io.*;
import java.util.*;
import java.text.*;
public class Jsb {
// 狀態欄位
private boolean dirty = false; // 內容是否已修改
private String filename = ""; // 當前檔案路徑
private String strtext = ""; // 暫存文本 (搜尋用)
// UI 元件
private JFrame frame = new JFrame("我的記事本");
private JPopupMenu pop = new JPopupMenu("彈出選單");
private JTextArea text = new JTextArea();
private JLabel statusBar = new JLabel("就緒");
private JMenuBar menuBar = new JMenuBar();
private JFileChooser fileChooser = new JFileChooser();
private JColorChooser colorChooser = new JColorChooser();
// 選單物件
private JMenu mFile, mEdit, mFormat, mHelp;
private JMenuItem miNew, miOpen, miSave, miSaveAs, miExit;
private JMenuItem miUndo, miCut, miCopy, miPaste, miDelete, miFind, miReplace, miSelectAll, miTimeDate;
private JMenuItem miWordWrap, miFont, miColor;
private JMenuItem miAbout;
public Jsb() { initUI(); }
private void initUI() {
// 視窗基本設定
frame.setSize(600, 500);
frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE); // 自行處理關閉
frame.addWindowListener(new WindowAdapter() {
@Override public void windowClosing(WindowEvent e) { close(); }
});
// 主面板:BorderLayout 中央文本區+捲軸,南部狀態列
JPanel mainPanel = new JPanel(new BorderLayout());
mainPanel.add(new JScrollPane(text), BorderLayout.CENTER);
mainPanel.add(statusBar, BorderLayout.SOUTH);
frame.setContentPane(mainPanel);
// 選單列
frame.setJMenuBar(menuBar);
buildMenus();
// 右鍵選單 (共用編輯動作)
buildPopupMenu();
// 文本區事件
text.addMouseListener(new MouseAdapter() {
@Override public void mouseClicked(MouseEvent e) {
if (SwingUtilities.isRightMouseButton(e)) pop.show(text, e.getX(), e.getY());
}
});
text.addCaretListener(e -> dirty = true); // 內容變更標記
frame.setVisible(true);
}
private void buildMenus() {
// ---- 檔案選單 ----
mFile = new JMenu("檔案(F)"); mFile.setMnemonic('F');
miNew = newMenuItem("新建(N)", 'N', KeyEvent.VK_N, e -> newText());
miOpen = newMenuItem("開啟(O)", 'O', KeyEvent.VK_O, e -> openText());
miSave = newMenuItem("儲存(S)", 'S', KeyEvent.VK_S, e -> save());
miSaveAs = newMenuItem("另存為...", 0, 0, e -> saveAs());
miExit = newMenuItem("結束(Q)", 'Q', KeyEvent.VK_Q, e -> close());
mFile.add(miNew); mFile.add(miOpen); mFile.add(miSave); mFile.add(miSaveAs);
mFile.addSeparator(); mFile.add(miExit);
// ---- 編輯選單 ----
mEdit = new JMenu("編輯(E)"); mEdit.setMnemonic('E');
miUndo = newMenuItem("撤銷(U)", 'U', KeyEvent.VK_U, e -> undo());
miCut = newMenuItem("剪下(X)", 'X', KeyEvent.VK_X, e -> cut());
miCopy = newMenuItem("複製(C)", 'C', KeyEvent.VK_C, e -> copy());
miPaste = newMenuItem("貼上(V)", 'V', KeyEvent.VK_V, e -> paste());
miDelete = newMenuItem("刪除(D)", 'D', KeyEvent.VK_D, e -> delete());
miFind = newMenuItem("尋找(F)", 'F', KeyEvent.VK_F, e -> find());
miReplace = newMenuItem("取代(R)", 'R', KeyEvent.VK_R, e -> replace());
miSelectAll = newMenuItem("全選(A)", 'A', KeyEvent.VK_A, e -> selectAll());
miTimeDate = newMenuItem("時間/日期(T)", 'T', KeyEvent.VK_T, e -> insertTimeDate());
mEdit.add(miUndo); mEdit.addSeparator();
mEdit.add(miCut); mEdit.add(miCopy); mEdit.add(miPaste); mEdit.add(miDelete);
mEdit.addSeparator(); mEdit.add(miFind); mEdit.add(miReplace);
mEdit.addSeparator(); mEdit.add(miSelectAll); mEdit.add(miTimeDate);
// ---- 格式選單 ----
mFormat = new JMenu("格式(O)"); mFormat.setMnemonic('O');
miWordWrap = new JCheckBoxMenuItem("自動換行(W)");
miWordWrap.addActionListener(e -> text.setLineWrap(miWordWrap.isSelected()));
miFont = newMenuItem("字體...", 0, 0, e -> chooseFont());
miColor = newMenuItem("顏色...", 0, 0, e -> chooseColor());
mFormat.add(miWordWrap); mFormat.addSeparator(); mFormat.add(miFont); mFormat.add(miColor);
// ---- 說明選單 ----
mHelp = new JMenu("說明(H)"); mHelp.setMnemonic('H');
miAbout = newMenuItem("關於作者(A)", 'A', KeyEvent.VK_A, e -> showAbout());
mHelp.add(miAbout);
// 加入選單列
menuBar.add(mFile); menuBar.add(mEdit); menuBar.add(mFormat); menuBar.add(mHelp);
}
private void buildPopupMenu() {
pop.add(new JMenuItem(new DefaultEditorKit.CutAction()));
pop.add(new JMenuItem(new DefaultEditorKit.CopyAction()));
pop.add(new JMenuItem(new DefaultEditorKit.PasteAction()));
pop.addSeparator();
pop.add(new JMenuItem("全選", e -> selectAll()));
text.setComponentPopupMenu(pop);
}
// 快速建立帶快捷鍵的 JMenuItem
private JMenuItem newMenuItem(String text, char mnemonic, int acceleratorKey, ActionListener listener) {
JMenuItem mi = new JMenuItem(text);
if (mnemonic != 0) mi.setMnemonic(mnemonic);
if (acceleratorKey != 0) mi.setAccelerator(KeyStroke.getKeyStroke(acceleratorKey, Event.CTRL_MASK));
mi.addActionListener(listener);
return mi;
}
// ---- 佔位方法 (實作見下篇) ----
private void newText() { /* 清空 textArea, filename="", dirty=false */ }
private void openText() { /* JFileChooser 讀檔 */ }
private void save() { /* 若 filename 空則 saveAs 否則寫檔 */ }
private void saveAs() { /* JFileChooser 存檔 */ }
private void close() { /* 檢查 dirty 確認儲存後 System.exit(0) */ }
private void undo() { /* UndoManager.undo() */ }
private void cut() { text.cut(); }
private void copy() { text.copy(); }
private void paste() { text.paste(); }
private void delete() { text.replaceSelection(""); }
private void find() { /* FindDialog */ }
private void replace() { /* ReplaceDialog */ }
private void selectAll() { text.selectAll(); }
private void insertTimeDate() { text.append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); }
private void chooseFont() { /* JFontChooser */ }
private void chooseColor() { /* JColorChooser */ }
private void showAbout() { JOptionPane.showMessageDialog(frame, "記事本 v1.0\nAuthor: Jeason"); }
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> new Jsb());
}
}
newMenuItem() 封裝助憶鍵、加速鍵、監聽器,減少重複代碼。DefaultEditorKit:剪貼簿動作無需自寫,直接採用 Swing 內建 Action。dirty、filename 單一來源,避免多處不同步。DO_NOTHING_ON_CLOSE + WindowAdapter 統一委派 close() 處理儲存確認。
javac jeason/Jsb.java
java jeason.Jsb
需 JDK 8 或以上 (Lambda、DefaultEditorKit Action 皆為標準 API)。
UndoManager 完成無限級撤銷/重做 (下篇詳解)。JDialog + JTextField + 選項核取方塊)。JFontChooser 非標準,可自行封裝或用第三方)。JTabbedPane),每個分頁獨立 dirty/filename。RSyntaxTextArea 等成熟元件)。