Java Foundation Classes (JFC) 的 Swing 組件庫將 MVC(Model-View-Controller)設計模式從應用層下沉到單個 UI 組件級別,這一創新使 JTable、JTree、JComboBox 等複雜組件擁有極高的靈活性與可擴展性。本文通過 JButton 的完整源碼級分析,揭示 Swing 如何將 MVC 三要素解耦並動態重組。
傳統 MVC 用於整個應用分層,Swing 創新性地將其應用於單個組件:
ButtonModel 維護三種布爾狀態,定義精確的交互語義:
// 核心狀態字段
private boolean boolPressed = false; // 鼠標按下未釋放
private boolean boolArmed = false; // 按下且鼠標在組件內
private boolean boolSelected = false; // 切換型按鈕的選中狀態
// 狀態變更通知機制
public void setPressed(boolean boolPressed) {
this.boolPressed = boolPressed;
fireChangeEvent(new ChangeEvent(button)); // 通知所有 ChangeListener
}
// 事件監聽器管理
private Vector vectorChangeListeners = new Vector();
public void addChangeListener(ChangeListener l) {
vectorChangeListeners.addElement(l);
}
protected void fireChangeEvent(ChangeEvent e) {
Enumeration enum = vectorChangeListeners.elements();
while (enum.hasMoreElements()) {
((ChangeListener)enum.nextElement()).stateChanged(e);
}
}
UI 對象不維護狀態,可被多組件共享,通過 installUI/uninstallUI 管理事件監聽器:
// 渲染邏輯
public void paint(Button button, Graphics g) {
Dimension d = button.getSize();
Color c = button.getBackground();
g.setColor(c);
g.fillRect(0, 0, d.width, d.height);
}
// 事件監聽器安裝/卸載
public void installUI(Button button) {
button.addMouseListener(buttonUIListener);
button.addMouseMotionListener(buttonUIListener);
button.addChangeListener(buttonUIListener);
}
public void uninstallUI(Button button) {
button.removeMouseListener(buttonUIListener);
button.removeMouseMotionListener(buttonUIListener);
button.removeChangeListener(buttonUIListener);
}
將底層 AWT 事件轉換為模型可理解的高級語義事件:
public void mousePressed(MouseEvent e) {
Button button = (Button)e.getSource();
ButtonModel model = button.getModel();
model.setPressed(true);
model.setArmed(true);
}
public void mouseDragged(MouseEvent e) {
Button button = (Button)e.getSource();
ButtonModel model = button.getModel();
if (model.isPressed()) {
model.setArmed(button.getUI().contains(button, e.getPoint()));
}
}
public void mouseReleased(MouseEvent e) {
Button button = (Button)e.getSource();
ButtonModel model = button.getModel();
model.setPressed(false);
model.setArmed(false);
// 此處觸發 ActionEvent 由模型內部邏輯完成
}
public void stateChanged(ChangeEvent e) {
((Button)e.getSource()).repaint(); // 狀態變更觸發重繪
}
JButton 運行時可動態替換 Model 與 UI,實現熱插拔:
public void setModel(ButtonModel newModel) {
if (this.model != null) {
this.model.removeChangeListener(changeListener);
this.model.removeActionListener(actionListener);
}
this.model = newModel;
if (this.model != null) {
changeListener = new ButtonChangeListener();
actionListener = new ButtonActionListener();
this.model.addChangeListener(changeListener);
this.model.addActionListener(actionListener);
}
updateButton();
}
public void setUI(ButtonUI newUI) {
if (this.ui != null) this.ui.uninstallUI(this);
this.ui = newUI;
if (this.ui != null) this.ui.installUI(this);
updateButton();
}