首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> C語言 >> C++ >> 關於C++

Qt 圖形視圖框架實戰:打造支援拖曳選取的簡易繪圖板

     編輯:關於C++

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));
}

即可獲得:

  • 滑鼠拖曳移動圖形
  • 點選顯示選取框 (sizing handles)
  • 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 內建互動模式,即可在極少代碼下構建專業級圖形編輯器雛形。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved