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

Qt 實戰專案:基於 QWidget 的簡易畫板完整實作 (直線/矩形/多形狀管理)

     編輯:關於C語言

理論學習到此告一段落,讓我們串聯前幾章知識,打造一個功能完整的**簡易畫板程式**。支援工具列切換直線/矩形、滑鼠拖曳即時預覽、歷史圖形保留重繪、狀態欄提示,展示 Qt 事件處理、繪圖系統、Model-View 分離的最佳實踐。

專案架構總覽

PaintApp/
├── main.cpp           # 入口
├── mainwindow.h/.cpp  # 主視窗:工具列、狀態列、信號分發
├── paintwidget.h/.cpp # 畫布:滑鼠事件、圖形列表、重繪邏輯
├── shape.h/.cpp       # 抽象基類:起點/終點、純虛 paint()
├── line.h/.cpp        # 直線項目
└── rect.h/.cpp        # 矩形項目

核心類別設計

1. Shape 抽象基類 (Model)

// shape.h
class Shape {
public:
    enum Code { Line, Rect };
    
    Shape() = default;
    virtual ~Shape() = default;
    
    void setStart(const QPoint &p) { start = p; }
    void setEnd(const QPoint &p)   { end = p; }
    QPoint startPoint() const { return start; }
    QPoint endPoint() const   { return end; }
    
    // 純虛繪圖介面
    virtual void paint(QPainter &painter) = 0;
    
protected:
    QPoint start, end;  // 兩點定義所有幾何資訊
};

2. 具體形狀類別

// line.cpp
void Line::paint(QPainter &painter) {
    painter.drawLine(start, end);
}

// rect.cpp
void Rect::paint(QPainter &painter) {
    // QRect 建構式自動處理負寬高 (右上→左下拖曳)
    painter.drawRect(QRect(start, end));
}

3. PaintWidget 畫布核心邏輯

// paintwidget.h
class PaintWidget : public QWidget {
    Q_OBJECT
public:
    PaintWidget(QWidget *parent = nullptr);
    
public slots:
    void setCurrentShape(Shape::Code code);  // 接收工具列切換
    
protected:
    void paintEvent(QPaintEvent *event) override;
    void mousePressEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void mouseReleaseEvent(QMouseEvent *event) override;
    
private:
    Shape::Code currentShape = Shape::Line;
    Shape *currentShapePtr = nullptr;
    bool drawing = false;
    QList shapeHistory;  // 歷史圖形 (Model)
};
// paintwidget.cpp
PaintWidget::PaintWidget(QWidget *parent) : QWidget(parent) {
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
    setMouseTracking(false);  // 僅按下時追蹤
}

void PaintWidget::setCurrentShape(Shape::Code code) {
    if (code != currentShape) currentShape = code;
}

void PaintWidget::mousePressEvent(QMouseEvent *event) {
    // 1. 依當前工具建立形狀物件
    switch (currentShape) {
        case Shape::Line: currentShapePtr = new Line; break;
        case Shape::Rect: currentShapePtr = new Rect; break;
    }
    
    if (currentShapePtr) {
        drawing = false;
        shapeHistory.append(currentShapePtr);  // 加入歷史
        currentShapePtr->setStart(event->pos());
        currentShapePtr->setEnd(event->pos());
    }
}

void PaintWidget::mouseMoveEvent(QMouseEvent *event) {
    if (currentShapePtr && !drawing) {
        currentShapePtr->setEnd(event->pos());
        update();  // 觸發 paintEvent 即時預覽
    }
}

void PaintWidget::mouseReleaseEvent(QMouseEvent *event) {
    drawing = true;  // 固定當前形狀
    currentShapePtr = nullptr;
}

void PaintWidget::paintEvent(QPaintEvent *event) {
    QPainter painter(this);
    
    // 1. 清除背景 (白底)
    painter.fillRect(rect(), Qt::white);
    
    // 2. 繪製所有歷史圖形
    for (Shape *s : shapeHistory) {
        s->paint(painter);
    }
    
    // 3. 繪製正在拖曳的圖形 (即時預覽)
    if (currentShapePtr) {
        currentShapePtr->paint(painter);
    }
}

4. MainWindow 整合 UI 與信號

// mainwindow.cpp
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) {
    // 工具列
    QToolBar *toolBar = addToolBar("Tools");
    QActionGroup *group = new QActionGroup(this);
    
    auto makeAction = [&](const QString &text, const char *icon, Shape::Code code) {
        QAction *act = new QAction(text, this);
        act->setIcon(QIcon(icon));
        act->setCheckable(true);
        act->setData(int(code));
        group->addAction(act);
        toolBar->addAction(act);
        connect(act, &QAction::triggered, this, [this, code]{
            emit changeCurrentShape(code);
        });
        return act;
    };
    
    QAction *lineAct = makeAction("Line", ":/line.png", Shape::Line);
    lineAct->setChecked(true);
    QAction *rectAct = makeAction("Rectangle", ":/rect.png", Shape::Rect);
    
    // 狀態列
    statusBar()->addWidget(new QLabel("Ready"));
    
    // 中心畫布
    PaintWidget *paintWidget = new PaintWidget(this);
    setCentralWidget(paintWidget);
    
    // 信號連接
    connect(this, &MainWindow::changeCurrentShape,
            paintWidget, &PaintWidget::setCurrentShape);
}

關鍵設計模式解析

模式應用位置效益
策略模式Shape 及子類新增形狀只需加子類,不修改 PaintWidget
觀察者模式MainWindow → PaintWidget 信號UI 與畫布解耦,易測試、易擴展
命令模式雛形shapeHistory 列表可輕易實作 Undo/Redo (pop/push)
即時預覽mouseMove → update() → paintEvent流暢使用者體驗,無閃爍

擴展方向 (留作練習)

  1. 撤銷/重做:shapeHistory 改為 QUndoStack 或雙棧
  2. 形狀屬性:Shape 加入 pen、brush、zValue,工具列加顏色選擇器
  3. 選取/移動/縮放:滑鼠釋放後進入選取模式,paintEvent 畫選取框
  4. 圖層管理:QList> 實作多圖層
  5. 匯出圖片:QImage img(size(), Format_ARGB32); paint(&img); img.save("out.png")
  6. 遷移至 Graphics View:下一章將展示同功能的 Graphics View 實作,對比優劣

完整專案結構提示

  • 使用 Qt Creator 建立 **Qt Widgets Application**
  • 關閉 .ui 表單,純程式碼構建 UI (如本文所示)
  • 資源檔 .qrc 放入 line.png、rect.png 圖示
  • CMakeLists.txt / .pro 加入所有 .cpp

這個 200 行左右的專案,完整串聯了 Qt 事件系統、繪圖架構、物件導向設計、信號槽通訊,是進階 Qt 開發的紮實起點。

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