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

Qt學習之路(31):使用 QWidget 實作簡易畫板

     編輯:關於C++

原本我不打算放一個過於複雜的範例,畢竟講解起來可能需要好幾篇幅。但既然繪圖相關的基礎已經介紹完畢,還是決定實作一個簡單的畫板程式來串連前面的知識點。這個畫板支援繪製直線與矩形,我會分兩種方式實現:第一種使用普通的 QWidget 作為畫布,第二種則採用 Graphics View Framework。之所以安排這兩種實作,是因為讀者反映對 Graphics View 的相關概念不太理解,藉此機會加以說明。

我們的專案架構仍使用 Qt Creator 建立的 Gui Application 專案。main() 函式就不再贅述,直接來看 MainWindow 的實作。順帶一提,我習慣不使用 .ui 檔案,所有介面程式碼皆為手寫。先看最終執行結果:

雖然功能簡單,但足以將前面所學的繪圖、事件處理、信號槽等知識串聯起來。

MainWindow 類別實作

MainWindow 負責建立工具列、動作群組,並透過信號通知畫布切換繪圖模式:

// mainwindow.h
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QtGui>
#include "shape.h"
#include "paintwidget.h"

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow(QWidget *parent = 0);
signals:
    void changeCurrentShape(Shape::Code newShape);
private slots:
    void drawLineActionTriggered();
    void drawRectActionTriggered();
};
#endif // MAINWINDOW_H

// mainwindow.cpp
#include "mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    QToolBar *bar = this->addToolBar("Tools");
    QActionGroup *group = new QActionGroup(bar);

    // 直線動作
    QAction *drawLineAction = new QAction("Line", bar);
    drawLineAction->setIcon(QIcon(":/line.png"));
    drawLineAction->setToolTip(tr("Draw a line."));
    drawLineAction->setStatusTip(tr("Draw a line."));
    drawLineAction->setCheckable(true);
    drawLineAction->setChecked(true);
    group->addAction(drawLineAction);
    bar->addAction(drawLineAction);

    // 矩形動作
    QAction *drawRectAction = new QAction("Rectangle", bar);
    drawRectAction->setIcon(QIcon(":/rect.png"));
    drawRectAction->setToolTip(tr("Draw a rectangle."));
    drawRectAction->setStatusTip(tr("Draw a rectangle."));
    drawRectAction->setCheckable(true);
    group->addAction(drawRectAction);
    bar->addAction(drawRectAction);

    // 狀態列訊息標籤
    QLabel *statusMsg = new QLabel;
    statusBar()->addWidget(statusMsg);

    // 設定中心元件為繪圖元件
    PaintWidget *paintWidget = new PaintWidget(this);
    setCentralWidget(paintWidget);

    // 信號槽連接
    connect(drawLineAction, SIGNAL(triggered()),
            this, SLOT(drawLineActionTriggered()));
    connect(drawRectAction, SIGNAL(triggered()),
            this, SLOT(drawRectActionTriggered()));
    connect(this, SIGNAL(changeCurrentShape(Shape::Code)),
            paintWidget, SLOT(setCurrentShape(Shape::Code)));
}

void MainWindow::drawLineActionTriggered()
{
    emit changeCurrentShape(Shape::Line);
}

void MainWindow::drawRectActionTriggered()
{
    emit changeCurrentShape(Shape::Rect);
}

程式碼邏輯清晰:QActionGroup 確保同一時間只能選擇一種繪圖模式;triggered() 信號無參數,因此在槽函式中重新發射帶參數的 changeCurrentShape 信號;中心元件設為自訂的 PaintWidget。

PaintWidget 繪圖元件

PaintWidget 繼承自 QWidget,處理滑鼠事件並管理圖形列表:

// paintwidget.h
#ifndef PAINTWIDGET_H
#define PAINTWIDGET_H
#include <QtGui>
#include <QDebug>
#include "shape.h"
#include "line.h"
#include "rect.h"

class PaintWidget : public QWidget
{
    Q_OBJECT
public:
    PaintWidget(QWidget *parent = 0);
public slots:
    void setCurrentShape(Shape::Code s)
    {
        if(s != currShapeCode) {
            currShapeCode = s;
        }
    }
protected:
    void paintEvent(QPaintEvent *event);
    void mousePressEvent(QMouseEvent *event);
    void mouseMoveEvent(QMouseEvent *event);
    void mouseReleaseEvent(QMouseEvent *event);
private:
    Shape::Code currShapeCode;
    Shape *shape;
    bool perm;
    QList<Shape*> shapeList;
};
#endif // PAINTWIDGET_H

// paintwidget.cpp
#include "paintwidget.h"

PaintWidget::PaintWidget(QWidget *parent)
    : QWidget(parent), currShapeCode(Shape::Line), shape(NULL), perm(false)
{
    setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
}

void PaintWidget::paintEvent(QPaintEvent *event)
{
    QPainter painter(this);
    painter.setBrush(Qt::white);
    painter.drawRect(0, 0, size().width(), size().height());
    foreach(Shape * shape, shapeList) {
        shape->paint(painter);
    }
    if(shape) {
        shape->paint(painter);
    }
}

void PaintWidget::mousePressEvent(QMouseEvent *event)
{
    switch(currShapeCode)
    {
    case Shape::Line:
        shape = new Line;
        break;
    case Shape::Rect:
        shape = new Rect;
        break;
    }
    if(shape != NULL) {
        perm = false;
        shapeList << shape;
        shape->setStart(event->pos());
        shape->setEnd(event->pos());
    }
}

void PaintWidget::mouseMoveEvent(QMouseEvent *event)
{
    if(shape && !perm) {
        shape->setEnd(event->pos());
        update();
    }
}

void PaintWidget::mouseReleaseEvent(QMouseEvent *event)
{
    perm = true;
}

核心邏輯在於三個滑鼠事件的配合:

  • mousePressEvent:根據當前模式建立對應的圖形物件(Line 或 Rect),記錄起始點與終點(初始相同),並加入 shapeList 保存歷史圖形。
  • mouseMoveEvent:滑鼠移動時更新終點並呼叫 update() 觸發重繪,形成「橡皮筋」效果。
  • mouseReleaseEvent:設定 perm = true 標記繪製完成,後續移動不再更新該圖形。

paintEvent 中先繪製白色背景,再遍歷 shapeList 繪製所有歷史圖形,最後繪製正在編輯的圖形(若存在)。

圖形類別體系

採用繼承架構,Shape 為抽象基類,定義通用介面與資料成員:

// shape.h
#ifndef SHAPE_H
#define SHAPE_H
#include <QtGui>

class Shape
{
public:
    enum Code { Line, Rect };
    Shape();
    void setStart(QPoint s) { start = s; }
    void setEnd(QPoint e) { end = e; }
    QPoint startPoint() { return start; }
    QPoint endPoint() { return end; }
    virtual void paint(QPainter & painter) = 0;
protected:
    QPoint start;
    QPoint end;
};
#endif // SHAPE_H

兩個具體子類別分別實作 paint():

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

// rect.h / rect.cpp
class Rect : public Shape {
public:
    Rect();
    void paint(QPainter &painter) {
        painter.drawRect(start.x(), start.y(),
                         end.x() - start.x(), end.y() - start.y());
    }
};

設計亮點:直線由兩點決定,矩形以兩點為對角頂點亦可確定,故基類僅需儲存 start 與 end 兩點。paint() 為純虛函式,強制子類實作繪製邏輯。若未來需擴充顏色、線寬等屬性,只需在 Shape 中新增即可,體現物件導向設計的優勢。

完整專案原始碼可參考原文附件。這個範例將繪圖、事件、信號槽、類別繼承等核心概念完整串聯,是 Qt 入門的絕佳練習。

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