Qt’s QGraphicsView Practical Articles

Foreword

The previous chapters introduced the Graphics View drawing architecture, and finally it’s time for actual combat, and it really came out after a long wait! In this chapter, the Graphics View drawing architecture is used as a drawing tool to realize the drawing of some basic graphics. Let’s take a look at the result demonstration without further ado:

The benefits of this article,Fees to receive Qt development learning materials package, technical video, including (C++ language foundation, introduction to Qt programming, QT signal and slot mechanism, QT interface development – Image drawing, QT network, QT database programming, QT project actual combat, QSS, OpenCV, Quick module, interview questions, etc.) ↓↓↓↓↓↓See below↓↓Click at the bottom of the articleFee collection ↓↓

Introduction to drawing graphics

We know simple geometric figures, as long as two points are determined, the drawing can be completed. For example, to draw a circle, one point can be set as the center of the circle, and the distance to the center of the circle can be calculated from another point, that is, the radius of the circle is determined. Then the circle is completed after the center and radius of the circle are determined.

In the same way, an ellipse, a square, a rectangle, and a rectangle with rounded ends can also be determined by a center point and a point that can be dragged to change the shape and size of the graph (here we call this point an edge point). Coordinates can be directly used to determine the width and height of graphics.

In pie and chord, the edge point is not only used to determine the width and height of the figure, but also needs to be used to determine the angle (the angle with the positive direction of the X axis).

The most troublesome thing is the polygon, because each click needs to draw a point and complete the connection, so the coordinates of each point need to be transferred from the scene to the graphics, and then the drawing is completed in the graphics.

Custom Functional Entity – Point

The point is attached to the graph, so a graph needs to be passed in to the constructor, representing that the point is on the graph, and the coordinates and type of the point need to be initialized;

class BPointItem : public QObject, public QAbstractGraphicsShapeItem
{
    Q_OBJECT

public:
    enum PointType {
        Center = 0, // center point
        Edge, // edge point (can be dragged to change the shape and size of the graph)
        Special // special function points
    };

    BPointItem(QAbstractGraphicsShapeItem* parent, QPointF p, PointType type);

    QPointF getPoint() { return m_point; }
    void setPoint(QPointF p) { m_point = p; }

protected:
    virtual QRectF boundingRect() const override;

    virtual void paint(QPainter *painter,
                       const QStyleOptionGraphicsItem *option,
                       QWidget *widget) override;

    virtual void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;

private:
    QPointF m_point;
    PointType m_type;
};

The shape drawn varies depending on the type. The center point is a circle and the cursor is OpenHandCursor, while other types of points are a square and the cursor is PointingHandCursor;

When we move the point, if it is the center point, the whole graph will move together, and when we move the edge point, the shape of the graph will be dynamically changed according to the coordinates of the edge point. There are too many codes here so I won’t paste them.

BPointItem::BPointItem(QAbstractGraphicsShapeItem* parent, QPointF p, PointType type)
    : QAbstractGraphicsShapeItem(parent)
    , m_point(p)
    , m_type(type)
{
    this->setPos(m_point);
    this->setFlags(QGraphicsItem::ItemIsSelectable |
                   QGraphicsItem::ItemIsMovable|
                   QGraphicsItem::ItemIsFocusable);

    switch (type) {
    case center:
        this->setCursor(Qt::OpenHandCursor);
        break;
    case Edge:
        this->setCursor(Qt::PointingHandCursor);
        break;
    case Special:
        this->setCursor(Qt::PointingHandCursor);
        break;
    default: break;
    }
}

QRectF BPointItem::boundingRect() const
{
    return QRectF(-4, -4, 8, 8);
}

void BPointItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option);
    Q_UNUSED(widget);
    painter->setPen(this->pen());
    painter->setBrush(this->brush());
    this->setPos(m_point);

    switch (m_type) {
    case center:
        painter->drawEllipse(-4, -4, 8, 8);
        break;
    case Edge:
        painter->drawRect(QRectF(-4, -4, 8, 8));
        break;
    case Special:
        painter->drawRect(QRectF(-4, -4, 8, 8));
        break;
    default: break;
    }
}

void BPointItem::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    if ( event->buttons() == Qt::LeftButton ) {
        qreal dx = event->scenePos().x() - event->lastScenePos().x();
        qreal dy = event->scenePos().y() - event->lastScenePos().y();

        BGraphicsItem* item = static_cast<BGraphicsItem *>(this->parentItem());
        BGraphicsItem::ItemType itemType = item->getType();

        switch (m_type) {
        case Center: {
            item->moveBy(dx, dy);
            this->scene()->update();
        } break;
        case Edge: {
            slightly...
        } break;
        case Special: {
            slightly...
        } break;
        default: break;
        }
    }
}

Custom primitive base class

A primitive base class is defined, and each primitive needs at least one center point, one edge point and primitive type. The focus event has been rewritten, and the color will change when the focus is obtained;

class BGraphicsItem : public QObject, public QAbstractGraphicsShapeItem
{
    Q_OBJECT

public:
    enum ItemType {
        Circle = 0, // circle
        Ellipse, // Ellipse
        Concentric_Circle, // concentric circle
        Pie, // pie
        Chord, // chord
        Rectangle, // rectangle
        Square, // square
        Polygon, // polygon
        Round_End_Rectangle, // round end rectangle
        Rounded_Rectangle // rounded rectangle
    };

    QPointF getCenter() { return m_center; }
    void setCenter(QPointF p) { m_center = p; }

    QPointF getEdge() { return m_edge; }
    void setEdge(QPointF p) { m_edge = p; }

    ItemType getType() { return m_type; }

protected:
    BGraphicsItem(QPointF center, QPointF edge, ItemType type);

    virtual void focusInEvent(QFocusEvent *event) override;
    virtual void focusOutEvent(QFocusEvent *event) override;

protected:
    QPointF m_center;
    QPointF m_edge;
    ItemType m_type;
    BPointItemList m_pointList;

    QPen m_pen_isSelected;
    QPen m_pen_noSelected;
};
BGraphicsItem::BGraphicsItem(QPointF center, QPointF edge, ItemType type)
    : m_center(center), m_edge(edge), m_type(type)
{
    m_pen_noSelected.setColor(QColor(0, 160, 230));
    m_pen_noSelected.setWidth(2);
    m_pen_isSelected.setColor(QColor(255, 0, 255));
    m_pen_isSelected.setWidth(2);

    this->setPen(m_pen_noSelected);
    this->setFlags(QGraphicsItem::ItemIsSelectable |
                   QGraphicsItem::ItemIsMovable |
                   QGraphicsItem::ItemIsFocusable);
}

void BGraphicsItem::focusInEvent(QFocusEvent *event)
{
    Q_UNUSED(event);
    this->setPen(m_pen_isSelected);
}

void BGraphicsItem::focusOutEvent(QFocusEvent *event)
{
    Q_UNUSED(event);
    this->setPen(m_pen_noSelected);
}

Rectangle

Take the rectangle as an example to introduce primitives. The parameters of the constructor are the coordinates of the center point and the width, height, and type of the rectangle. After initializing the center point and edge point, the rectangle can be drawn. Rewrite the paint function to bind the width and height of the rectangle to the coordinates of m_edge. The size of the rectangle is determined by the coordinates of the edge point. When we drag to the edge point, we only need to Update the coordinates of m_edge, and the shape of the rectangle can be changed dynamically;

class BRectangle : public BGraphicsItem
{
public:
    BRectangle(qreal x, qreal y, qreal width, qreal height, ItemType type);

protected:
    virtual QRectF boundingRect() const override;

    virtual void paint(QPainter *painter,
                       const QStyleOptionGraphicsItem *option,
                       QWidget *widget) override;
};
BRectangle::BRectangle(qreal x, qreal y, qreal width, qreal height, ItemType type)
    : BGraphicsItem(QPointF(x,y), QPointF(width/2,height/2), type)
{
    BPointItem *point = new BPointItem(this, m_edge, BPointItem::Edge);
    point->setParentItem(this);
    m_pointList.append(point);
    m_pointList.append(new BPointItem(this, m_center, BPointItem::Center));
    m_pointList.setRandColor();
}

QRectF BRectangle::boundingRect() const
{
    return QRectF(m_center.x() - m_edge.x(), m_center.y() - m_edge.y(), m_edge.x() * 2, m_edge.y() * 2);
}

void BRectangle::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
{
    Q_UNUSED(option);
    Q_UNUSED(widget);
    painter->setPen(this->pen());
    painter->setBrush(this->brush());

    QRectF ret(m_center.x() - m_edge.x(), m_center.y() - m_edge.y(), m_edge.x() * 2, m_edge.y() * 2);
    painter->drawRect(ret);
}

Custom scene

When we draw polygons, we need to draw points and lines every click. At this time, our mouse click event is in the scene, and we need to pass the coordinates in the scene to the primitive, so we need to customize the scene to rewrite the mouse event in the scene;

class BQGraphicsScene : public QGraphicsScene
{
    Q_OBJECT

public:
    BQGraphicsScene(QObject *parent = nullptr);

    void startCreate();

protected:
    virtual void mousePressEvent(QGraphicsSceneMouseEvent *event);

signals:
    void updatePoint(QPointF p, QList<QPointF> list, bool isCenter);
    void createFinished();

protected:
    QList<QPointF> m_list;
    bool is_creating_BPolygon;
};
BQGraphicsScene::BQGraphicsScene(QObject *parent) : QGraphicsScene(parent)
{
    is_creating_BPolygon = false;
}

void BQGraphicsScene::startCreate()
{
    is_creating_BPolygon = true;
    m_list. clear();
}

void BQGraphicsScene::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
    if (is_creating_BPolygon) {
        QPointF p(event->scenePos().x(), event->scenePos().y());

        switch ( event->buttons() )
        {
        case Qt::LeftButton: {
            m_list.push_back(p);
            emit updatePoint(p, m_list, false);
        } break;
        case Qt::RightButton: {
            if (m_list. size() >= 3) {
                emit updatePoint(p, m_list, true);
                emit createFinished();
                is_creating_BPolygon = false;
                m_list. clear();
            }
        } break;
        default: break;
        }
    } else {
        QGraphicsScene::mousePressEvent(event);
    }
}
void MainWindow::on_polygonBtn_clicked()
{
    m_scene. startCreate();
    setBtnEnabled(false);
    BPolygon *m_polygon = new BPolygon(BGraphicsItem::ItemType::Polygon);
    m_scene.addItem(m_polygon);

    connect( & amp; m_scene, SIGNAL(updatePoint(QPointF, QList<QPointF>, bool)), m_polygon, SLOT(pushPoint(QPointF, QList<QPointF>, bool)));
    connect( & amp;m_scene, & amp;BQGraphicsScene::createFinished, [=](){
        setBtnEnabled(true);
    });
}

Right-click the pop-up window to modify properties

You can rewrite the contextMenuEvent function to complete the right-click pop-up window, and which controls are required in the pop-up window can be customized;

void BEllipse::contextMenuEvent(QGraphicsSceneContextMenuEvent *event)
{
    if ( !this->isSelected() )
        return;

    QMenu* menu = new QMenu();
    menu->setStyleSheet("QMenu { background-color: rgb(89,87,87); border: 5px solid rgb(235,110,36); }");

    QSpinBox* width_spinBox = new QSpinBox(menu);
    width_spinBox->setStyleSheet("QSpinBox{ width:120px; height:30px;}");
    width_spinBox->setRange(0, 1000);
    width_spinBox->setPrefix("w: ");
    width_spinBox->setSuffix("mm");
    width_spinBox->setSingleStep(1);
    width_spinBox->setValue(2 * abs(m_edge.x()));
    connect(width_spinBox, static_cast<void (QSpinBox::*)(int)>( & amp;QSpinBox::valueChanged), [=](int v){
        m_edge.setX(v/2);
        m_pointList.at(0)->setPoint(m_edge);
        this->hide();
        this->update();
        this->show();
    });

    QSpinBox* height__spinBox = new QSpinBox(menu);
    height__spinBox->setStyleSheet("QSpinBox{ width:120px; height:30px;}");
    height__spinBox->setRange(0, 1000);
    height__spinBox->setPrefix("h: ");
    height__spinBox->setSuffix("mm");
    height__spinBox->setSingleStep(1);
    height__spinBox->setValue(2 * abs(m_edge.y()));
    connect(height__spinBox, static_cast<void (QSpinBox::*)(int)>( & amp;QSpinBox::valueChanged), [=](int v){
        m_edge.setY(v/2);
        m_pointList.at(0)->setPoint(m_edge);
        this->hide();
        this->update();
        this->show();
    });

    QWidgetAction* width_widgetAction = new QWidgetAction(menu);
    width_widgetAction->setDefaultWidget(width_spinBox);
    menu->addAction(width_widgetAction);

    QWidgetAction* height_widgetAction = new QWidgetAction(menu);
    height_widgetAction->setDefaultWidget(height__spinBox);
    menu->addAction(height_widgetAction);

    menu->exec(QCursor::pos());
    delete menu;

    QGraphicsItem::contextMenuEvent(event);
}

The benefits of this article,Fees to receive Qt development learning materials package, technical video, including (C++ language foundation, introduction to Qt programming, QT signal and slot mechanism, QT interface development – Image drawing, QT network, QT database programming, QT project actual combat, QSS, OpenCV, Quick module, interview questions, etc.) ↓↓↓↓↓↓See below↓↓Click at the bottom of the articleFee collection ↓↓