Swing 雖屬舊世代 GUI 框架,但輕量、跨平台、無額外依賴,適合教學與中小型桌面工具。本文以「按鈕二維陣列 + GridLayout」快速建構計算機介面,示範事件委派、元件動態生成、版面管理等核心技巧。
package com.example.swing;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
public class CalculatorUI extends JFrame {
private static final String[][] KEYS = {
{"7", "8", "9", "/"},
{"4", "5", "6", "*"},
{"1", "2", "3", "-"},
{"0", ".", "=", "+"}
};
private JTextField display;
public static void main(String[] args) {
SwingUtilities.invokeLater(() -> {
try {
UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
} catch (Exception ignored) {}
new CalculatorUI().setVisible(true);
});
}
public CalculatorUI() {
super("計算機");
setDefaultCloseOperation(EXIT_ON_CLOSE);
setLayout(new BorderLayout(10, 10));
((JComponent) getContentPane()).setBorder(BorderFactory.createEmptyBorder(10, 10, 10, 10));
// 顯示區
display = new JTextField();
display.setHorizontalAlignment(SwingConstants.RIGHT);
display.setFont(new Font(Font.MONOSPACED, Font.BOLD, 28));
display.setPreferredSize(new Dimension(300, 60));
display.setEditable(false);
add(display, BorderLayout.NORTH);
// 按鈕面板
JPanel panel = new JPanel(new GridLayout(KEYS.length, KEYS[0].length, 5, 5));
for (int r = 0; r < KEYS.length; r++) {
for (int c = 0; c < KEYS[r].length; c++) {
String label = KEYS[r][c];
JButton btn = new JButton(label);
btn.setFont(new Font(Font.SANS_SERIF, Font.BOLD, 22));
btn.addActionListener(e -> onKeyPressed(((JButton) e.getSource()).getText()));
panel.add(btn);
}
}
add(panel, BorderLayout.CENTER);
pack();
setLocationRelativeTo(null); // 置中
setResizable(false);
}
private void onKeyPressed(String key) {
String txt = display.getText();
switch (key) {
case "=" -> evaluate();
case "C" -> display.setText(""); // 若有清除鍵
default -> display.setText(txt + key);
}
}
private void evaluate() {
try {
// 簡易四則運算:使用 ScriptEngine (JS) 或自行解析
// 這裡示範 ScriptEngine (JDK 內建 Nashorn/Rhino)
javax.script.ScriptEngine engine = new javax.script.ScriptEngineManager().getEngineByName("js");
Object result = engine.eval(display.getText());
display.setText(result.toString());
} catch (Exception ex) {
display.setText("Error");
}
}
}
| 技術 | 說明 |
|---|---|
String[][] KEYS | 宣告式定義鍵盤配置,修改版面只需改陣列,不動程式邏輯。 |
GridLayout(rows, cols, hgap, vgap) | 自動等寬等高排列按鈕,完美符合計算機網格需求。 |
SwingUtilities.invokeLater | 確保 GUI 建構在 Event Dispatch Thread (EDT) 執行,避免執行緒安全問題。 |
UIManager.setLookAndFeel | 套用系統原生外觀 (Windows/Mac/Linux),提升使用者體驗。 |
| Lambda 事件處理 | btn.addActionListener(e -> onKeyPressed(...)) 簡潔替代匿名類別。 |
ScriptEngine | 幾行程式碼支援完整運算優先序、括號、函數,免手寫解析器。 |
上方 ScriptEngine 雖方便,但無法處理「連續運算」(如 1+2=3 → +3=6)。實務上需實作狀態機:
enum State { FIRST_OPERAND, OPERATOR, SECOND_OPERAND, RESULT }
private State state = State.FIRST_OPERAND;
private double operand1 = 0;
private String pendingOp = "";
private void onKeyPressed(String key) {
if ("0123456789.".contains(key)) {
if (state == State.RESULT || state == State.OPERATOR) {
display.setText(key);
state = State.SECOND_OPERAND;
} else {
display.setText(display.getText() + key);
}
} else if ("+-*/".contains(key)) {
if (state == State.SECOND_OPERAND) evaluatePending();
operand1 = Double.parseDouble(display.getText());
pendingOp = key;
state = State.OPERATOR;
} else if ("=".equals(key)) {
evaluatePending();
state = State.RESULT;
}
}
private void evaluatePending() {
double operand2 = Double.parseDouble(display.getText());
double result = switch (pendingOp) {
case "+" -> operand1 + operand2;
case "-" -> operand1 - operand2;
case "*" -> operand1 * operand2;
case "/" -> operand2 != 0 ? operand1 / operand2 : Double.NaN;
default -> operand2;
};
display.setText(String.valueOf(result));
operand1 = result;
}
GridLayout 強制等大;若需不同大小,改用 GridBagLayout 或巢狀 JPanel。JFrame 加入 KeyListener 或使用 KeyBinding (推薦) 映射數字鍵、Enter、Backspace。-Dsun.java2d.uiScale.enabled=true;或使用 UIManager.put("swing.boldMetal", Boolean.FALSE)。maven-shade-plugin 打 Fat JAR,`Main-Class` 指向此類別。新專案建議直接採用 JavaFX 或 Compose for Desktop,Swing 僅維護現有專案。