開發過程中必須解決技術難題並做出架構決策。原型不僅是可丟棄的實驗代碼,更是**產品基石**——在原型階段驗證想法、測試冷門 API、確保效能指標,為正式開發鋪路。本文分享 JImaging 圖片註解原型中的兩大關鍵決策。
類 Windows 畫圖應用需支援:滑鼠繪圖 (線、矩形、橢圖)、物件變形 (移動、縮放、重排序、刪除、複製/剪貼/貼上)、文字註解 (編輯、縮放、換行)。自行實作文字編輯器成本極高,Swing 已提供豐富文字元件 (JTextComponent 族群)。
JTable 的儲存格編輯器。但調整文字框大小、移動位置需額外技巧,實作複雜。JDesktopPane 本質為**多層面板**。MainPane 繼承之,建立兩層:
PaintView 自繪元件,處理幾何圖形繪製。JInternalFrame 內嵌 Swing 文字元件,自動獲得移動、縮放、編輯功能。JInternalFrame 內容非直接繪製於 PaintView,需程式化合成。MainPanel.getAnnotatedImage() 實現:
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
Graphics2D g = image.createGraphics();
printAll(g); // 將整個元件階層 (含內部框架) 繪製到影像
g.dispose();
printAll() 遞迴繪製所有子元件,完美融合自繪圖形與 Swing 文字註解。
Swing 繪製通常在 paint() 內進行。滑鼠繪圖時,MOUSE_PRESSED 到 MOUSE_RELEASED 間產生數百個 MOUSE_DRAGGED 事件。若每次事件都呼叫 repaint() 重繪整個 PaintView (含所有文字註解),效能將不可接受。
於滑鼠事件處理中呼叫 getGraphics() 取得即時繪圖上下文,委派給工具物件繪製:
protected void toolAction(MouseEvent e) {
e.consume();
Graphics2D g2 = (Graphics2D) getGraphics(); // 關鍵:繞過 paint()
float zoomFactor = model.getZoomFactor();
g2.scale(zoomFactor, zoomFactor);
float x = e.getX() / zoomFactor;
float y = e.getY() / zoomFactor;
currentTool.action(e.getID(), x, y, g2);
g2.dispose();
}
滑鼠釋放時才呼叫 repaint() 觸發完整重繪,**使用者完成繪圖後 paint() 僅執行一次**。
protected void registerListeners() {
addMouseListener(new MouseAdapter() {
public void mousePressed(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
requestFocus();
currentTool = model.createTool(AbstractTool.DRAW_STYLE);
toolAction(e);
}
}
public void mouseReleased(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) {
toolAction(e);
model.setLastTool(currentTool);
currentTool = null;
repaint(); // 單次完整重繪
}
}
});
addMouseMotionListener(new MouseMotionAdapter() {
public void mouseDragged(MouseEvent e) {
if (SwingUtilities.isLeftMouseButton(e)) toolAction(e);
}
});
// ... 其他監聽器
}
原型是產品的基礎而非垃圾代碼。透過原型驗證「多層面板整合 Swing 元件」與「繞過 paint() 即時繪製」兩大決策,JImaging 解決了複合編輯器架構與高頻繪製效能的核心挑戰。正式開發時,這些經驗將轉化為穩健的架構與優化代碼。