Qt 的 Graphics View Framework 提供場景-視圖架構,適合處理大量 2D 圖形項目、縮放平移、碰撞偵測與事件分發。本文以「簡易繪圖板」為例,示範如何將原本基於 QWidget 的滑鼠繪圖邏輯,遷移至 QGraphicsScene + QGraphicsView 架構,並啟用項目原生的可移動、可選取功能。
| 傳統 QWidget 繪圖 | Graphics View 架構 |
|---|---|
在 paintEvent 手繪所有圖形 | 每個圖形為獨立 QGraphicsItem,場景自動管理繪製順序 |
| 滑鼠事件需自行座標轉換、命中測試 | 項目自帶 mousePressEvent 等,場景自動分發 |
| 縮放/平移需自行實作矩陣變換 | QGraphicsView 內建 scale, translate, dragMode |
| 選取/拖曳需自行維護狀態 | 設定 ItemIsMovable, ItemIsSelectable 旗標即可 |
// Shape.h - 抽象基底,定義繪製介面
class Shape {
public:
enum Code { Line, Rect };
virtual void startDraw(QGraphicsSceneMouseEvent* e) = 0;
virtual void drawing(QGraphicsSceneMouseEvent* e) = 0;
virtual ~Shape() {}
};
// Line.h - 多重繼承:Shape 介面 + QGraphicsLineItem 圖形項目
class Line : public Shape, public QGraphicsLineItem {
public:
void startDraw(QGraphicsSceneMouseEvent* e) override {
setLine(QLineF(e->scenePos(), e->scenePos())); // 起點=終點
}
void drawing(QGraphicsSceneMouseEvent* e) override {
setLine(QLineF(line().p1(), e->scenePos())); // 更新終點
}
};
// Rect.h - 同理繼承 QGraphicsRectItem
class Rect : public Shape, public QGraphicsRectItem {
public:
void startDraw(QGraphicsSceneMouseEvent* e) override {
setRect(QRectF(e->scenePos(), QSizeF(0,0)));
}
void drawing(QGraphicsSceneMouseEvent* e) override {
QRectF r(rect().topLeft(), e->scenePos() - rect().topLeft());
setRect(r.normalized());
}
};
class PaintWidget : public QGraphicsScene {
Q_OBJECT
Shape::Code currShape = Shape::Line;
Shape* currItem = nullptr;
bool perm = false; // 滑鼠釋放鎖定旗標
public slots:
void setCurrentShape(Shape::Code c) { currShape = c; }
protected:
void mousePressEvent(QGraphicsSceneMouseEvent* e) override {
switch (currShape) {
case Shape::Line: currItem = new Line; break;
case Shape::Rect: currItem = new Rect; break;
}
if (currItem) {
addItem(currItem); // 加入場景,場景擁有所有權
currItem->startDraw(e);
perm = false;
}
QGraphicsScene::mousePressEvent(e);
}
void mouseMoveEvent(QGraphicsSceneMouseEvent* e) override {
if (currItem && !perm) currItem->drawing(e);
QGraphicsScene::mouseMoveEvent(e);
}
void mouseReleaseEvent(QGraphicsSceneMouseEvent* e) override {
perm = true; // 鎖定形狀,後續移動交由 ItemIsMovable 處理
QGraphicsScene::mouseReleaseEvent(e);
}
};
MainWindow::MainWindow(QWidget* parent) : QMainWindow(parent) {
// 工具列動作群組
QToolBar* bar = addToolBar("Tools");
QActionGroup* grp = new QActionGroup(bar);
auto addTool = [&](const char* name, const char* icon, Shape::Code code) {
QAction* act = new QAction(name, bar);
act->setIcon(QIcon(icon));
act->setCheckable(true);
act->setData(int(code));
grp->addAction(act);
bar->addAction(act);
connect(act, &QAction::triggered, [this, code]{
emit changeCurrentShape(code);
});
};
addTool("Line", ":/line.png", Shape::Line);
addTool("Rect", ":/rect.png", Shape::Rect);
grp->actions().first()->setChecked(true);
// 場景 + 視圖
PaintWidget* scene = new PaintWidget(this);
QGraphicsView* view = new QGraphicsView(scene, this);
view->setRenderHint(QPainter::Antialiasing);
setCentralWidget(view);
// 信號連接場景
connect(this, &MainWindow::changeCurrentShape, scene, &PaintWidget::setCurrentShape);
}
在 Line 與 Rect 建構子加入:
Line::Line() {
setFlag(QGraphicsItem::ItemIsMovable, true);
setFlag(QGraphicsItem::ItemIsSelectable, true);
setPen(QPen(Qt::black, 2));
}
即可獲得:
Ctrl+點選 多選、Delete 刪除QGraphicsView::setDragMode(QGraphicsView::RubberBandDrag) 框選多項目startDraw 中以 sceneRect() 校正或關閉 QGraphicsView::setAlignment(Qt::AlignLeft | Qt::AlignTop)。QGraphicsItem 內建事件處理,或安裝 QGraphicsScene::installEventFilter 區分「繪圖模式」與「編輯模式」。QUndoStack + QUndoCommand 封裝 addItem/removeItem/屬性變更。Graphics View 框架將「圖形資料模型」與「視圖渲染」徹底分離,讓開發者專注於領域邏輯。善用 QGraphicsItem 旗標與 QGraphicsView 內建互動模式,即可在極少代碼下構建專業級圖形編輯器雛形。