在前幾篇文章中我們詳細介紹了 Graphics View 架構的優勢,但光說不練假把式。這次我們將把先前的畫板範例改用 Graphics View 重寫,讓大家能直觀對比兩種實作方式的差異。相同的基礎架構程式碼將不再贅述,直接從關鍵修改處切入。
由於 Graphics View 架構採用「場景-視圖」模式,繪圖項目必須加入 QGraphicsScene,再透過 QGraphicsView 觀看。因此 MainWindow 的建構子需做以下調整:
PaintWidget *paintWidget = new PaintWidget(this);
QGraphicsView *view = new QGraphicsView(paintWidget, this);
setCentralWidget(view);
這裡將 PaintWidget 視為場景,並用 QGraphicsView 包裝後設為中央元件。工具列與動作群組的建立邏輯保持不變,仍使用 QActionGroup 管理「直線」與「矩形」兩種繪圖模式的互斥選項。
核心變動在於 PaintWidget 現在繼承 QGraphicsScene 而非 QWidget。滑鼠事件參數型別相應改為 QGraphicsSceneMouseEvent,且每個事件處理函式結尾都需呼叫父類別實作,確保事件正確傳遞:
void PaintWidget::mousePressEvent(QGraphicsSceneMouseEvent *event) {
switch (currShapeCode) {
case Shape::Line: {
Line *line = new Line;
currItem = line;
addItem(line);
break;
}
case Shape::Rect: {
Rect *rect = new Rect;
currItem = rect;
addItem(rect);
break;
}
}
if (currItem) {
currItem->startDraw(event);
perm = false;
}
QGraphicsScene::mousePressEvent(event);
}
mouseMoveEvent 與 mouseReleaseEvent 同理,分別轉呼 drawing() 與設定 perm = true,最後呼叫父類別函式。
Qt 提供了 QGraphicsLineItem、QGraphicsRectItem 等現成圖形項目,我們透過多重繼承讓自訂類別同時具備 Shape 介面與 Qt 內建項目特性:
class Line : public Shape, public QGraphicsLineItem {
public:
void startDraw(QGraphicsSceneMouseEvent *event) override {
setLine(QLineF(event->scenePos(), event->scenePos()));
}
void drawing(QGraphicsSceneMouseEvent *event) override {
setLine(QLineF(line().p1(), event->scenePos()));
}
};
Rect 類別邏輯雷同,使用 QRectF 記錄幾何資訊,比先前手動管理兩個 QPoint 更直觀高效。
編譯執行後,畫板基本功能與先前版本無異。但若在 Line 與 Rect 建構子加入:
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
繪製完成的圖形立刻獲得「可移動、可選取」特性,完全不需額外撰寫拖曳邏輯!這正是 Graphics View 架構的強大之處——內建項目管理、碰撞偵測、視圖縮放旋轉等進階功能皆可輕鬆擴充。筆者留給讀者一個小挑戰:目前自訂場景滑鼠事件會干擾項目拖曳,請嘗試重新設計類別架構以完美支援雙重操作模式。