理論學習到此告一段落,讓我們串聯前幾章知識,打造一個功能完整的**簡易畫板程式**。支援工具列切換直線/矩形、滑鼠拖曳即時預覽、歷史圖形保留重繪、狀態欄提示,展示 Qt 事件處理、繪圖系統、Model-View 分離的最佳實踐。
PaintApp/ ├── main.cpp # 入口 ├── mainwindow.h/.cpp # 主視窗:工具列、狀態列、信號分發 ├── paintwidget.h/.cpp # 畫布:滑鼠事件、圖形列表、重繪邏輯 ├── shape.h/.cpp # 抽象基類:起點/終點、純虛 paint() ├── line.h/.cpp # 直線項目 └── rect.h/.cpp # 矩形項目
// 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; // 兩點定義所有幾何資訊
};
// line.cpp
void Line::paint(QPainter &painter) {
painter.drawLine(start, end);
}
// rect.cpp
void Rect::paint(QPainter &painter) {
// QRect 建構式自動處理負寬高 (右上→左下拖曳)
painter.drawRect(QRect(start, end));
}
// 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);
}
}
// 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 | 流暢使用者體驗,無閃爍 |
shapeHistory 改為 QUndoStack 或雙棧pen、brush、zValue,工具列加顏色選擇器paintEvent 畫選取框QList> 實作多圖層QImage img(size(), Format_ARGB32); paint(&img); img.save("out.png").ui 表單,純程式碼構建 UI (如本文所示).qrc 放入 line.png、rect.png 圖示CMakeLists.txt / .pro 加入所有 .cpp這個 200 行左右的專案,完整串聯了 Qt 事件系統、繪圖架構、物件導向設計、信號槽通訊,是進階 Qt 開發的紮實起點。