سؤال

This is what i mean that happens, first tried a circle, then on the right a squarei want to draw different arbitrary figures. Drawing starts when the mouse is clicked in the graphicsview and ends when stop clicking the mouse. However, when starting at a different point in the graphics view to make a new drawing, or to continue on the previous drawing, a line is drawn from the last mouse coordinate of the first drawing, to the first coordinate of the second drawing. The drawings do not necessarily need to be different drawings, but can also just be adjustments to the drawing. This is my code.

#include "mousedraw.h"
#include <QDebug>

MouseDraw::MouseDraw()
{
    setFlag(ItemIsMovable);
}

QRectF MouseDraw::boundingRect() const
{
    return QRectF(0,0,300,300);
}

void MouseDraw::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,      QWidget *widget)
{
    QPainterPath path;
    path.addPolygon(polyPoints2);
    painter->setPen(QPen(QColor(qrand() % 256, qrand() % 256, qrand() % 256),3));
    painter->drawPath(path);
}

void MouseDraw::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
    QPointF point = event->pos();
    if (boundingRect().contains(point)) {
         array1 = point.x();
        array2 = point.y();
        polyPoints2 << QPoint(array1,array2);
        update();
    }
}
هل كانت مفيدة؟

المحلول

Having a custom MouseDraw class derived from QGraphicsItem is unnecessary. There is a QGraphicsPathItem that handles it all for you, correctly.

It is conceptually incorrect to process the mouse interaction within the item being created - your large bounding rectangle is a hack that's needed for it to work, but it's the wrong approach. You don't want the item to interact with the mouse. You want the scene to interact with the mouse and create an item on the fly. Only when editing an existing item you'd want to have mouse interaction, but even then it's better to create a separate editor item overlaid on the item being edited to handle the editing interaction.

Click on the window with right mouse button to get a context menu with the action to clear the picture. You can also toggle whether the subsequent path is joined to the previous one. This code is tested with both Qt 4.8.5 and 5.2.

When the scene is smaller than the view, the view insists on centering it (using the alignment property). An EmptyItem is used as a workaround to this "feature", to constrain the location of the scene within the view when the scene is smaller than the view (including when the scene is "empty").

The code creates a separate item for each disjoint path - QGraphicsScene generally will perform the best with multiple non-overlapping items. You could of course retain just one item and keep adding new segments to it on each mouse press, but that will, eventually, have performance implications - especially if you zoom into the scene, where you'd expect the performance to get better as less is shown.

screenshot

// https://github.com/KubaO/stackoverflown/tree/master/questions/gscene-paint-20632209
#include <QtGui>
#if QT_VERSION >= QT_VERSION_CHECK(5,0,0)
#include <QtWidgets>
#endif

class EmptyItem : public QGraphicsItem
{
public:
    EmptyItem(QGraphicsItem * parent = nullptr) : QGraphicsItem{parent} {}
    QRectF boundingRect() const override { return {0, 0, 1, 1}; }
    void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override {}
};

class Scene : public QGraphicsScene
{
    Q_OBJECT
    Q_PROPERTY(bool joinFigures READ joinFigures WRITE setJoinFigures)
    bool m_joinFigures = false;
    QGraphicsPathItem * m_item = nullptr;
    QPainterPath m_path;

    void newItem() {
        addItem(m_item = new QGraphicsPathItem);
        m_item->setPen(QPen{{qrand() % 256, qrand() % 256, qrand() % 256}});
        m_path = QPainterPath{}; // using std::swap; swap(m_path, QPainterPath());
    }
    void newPoint(const QPointF& pt) {
        if (! m_item) {
            newItem();
            m_path.moveTo(pt);
        } else {
            m_path.lineTo(pt);
            m_item->setPath(m_path);
        }
    }
    void mousePressEvent(QGraphicsSceneMouseEvent * ev) override {
        if (ev->buttons() != Qt::LeftButton) return;
        if (! m_joinFigures) m_item = nullptr;
        newPoint(ev->scenePos());
    }
    void mouseMoveEvent(QGraphicsSceneMouseEvent *ev) override {
        if (ev->buttons() != Qt::LeftButton) return;
        newPoint(ev->scenePos());
    }
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *) override {
        if (! m_path.isEmpty()) return;
        delete m_item; // Empty items are useless
        m_item = nullptr;
    }
public:
    Scene(QObject *parent = nullptr) : QGraphicsScene{parent}
    {
        addItem(new EmptyItem{});
    }
    Q_SLOT void setJoinFigures(bool j) { m_joinFigures = j; }
    bool joinFigures() const { return m_joinFigures; }
};

class Window : public QWidget
{
    Q_OBJECT
    QGridLayout m_layout{this};
    QGraphicsView m_view;
    QCheckBox m_join{"Join Figures (toggle with Spacebar)"};
    QAction m_toggleJoin{this};
public:
    Window(QWidget * parent = 0) : QWidget{parent}
    {
        m_layout.addWidget(&m_view);
        m_layout.addWidget(&m_join);
        m_view.setAlignment(Qt::AlignLeft | Qt::AlignTop);

        m_toggleJoin.setShortcut(QKeySequence(Qt::Key_Space));
        connect(&m_toggleJoin, SIGNAL(triggered()), &m_join, SLOT(toggle()));
        addAction(&m_toggleJoin);

        m_view.addAction(new QAction{"Clear", this});
        m_view.setContextMenuPolicy(Qt::ActionsContextMenu);
        connect(m_view.actions().at(0), SIGNAL(triggered()), SLOT(newScene()));

        // Create a new scene instead of clear()-ing it, since scenes can only grow their
        // sceneRect().
        newScene();
    }
    Q_SLOT void newScene() {
        if (m_view.scene()) m_view.scene()->deleteLater();
        m_view.setScene(new Scene);
        m_view.scene()->connect(&m_join, SIGNAL(toggled(bool)), SLOT(setJoinFigures(bool)));
    }
};

int main(int argc, char *argv[])
{
    QApplication a{argc, argv};
    Window w;
    w.show();
    return a.exec();
}

#include "main.moc"

نصائح أخرى

Create a class that is inherited from QGraphicsItem, or QGraphicsObject if you want signals and slots.

Store a QPainterPath as a member of the class. When you detect the mouse button pressed, call the painter path moveTo() function, providing the coordinate. On receiving mouse move events, call the painter path lineTo() function with the coordinates.

Overload the boundingRect function to return the painter path's rect and also overload the shape() function to return the correct shape for collision.

Finally in the class's paint function, draw the painter path. Here's a skeleton class you can use.

class MouseDraw : public QGraphicsItem
{
    public:
        QRectF boundingRect() const
        {
            return m_painterpath.boundingRect();
        }

        QPainterPath shape() const
        {
            return m_painterpath;
        }

        void QGraphicsItem::paint(QPainter * painter, const QStyleOptionGraphicsItem * option, QWidget * widget)
        {
            // setup pen and brush
            // ....
            // draw the path

            painter->drawPath(m_painterpath);
        }

    private:
        QPainterPath m_painterpath;
};

Handle the mousePress, mouseMove and mouseRelease events to add the required points to the painter path and then instantiate an object of the class and add it to the scene: -

MouseDraw* mouseDraw = new MouseDraw;
scene->addItem(mouseDraw);

Note that the object is added only once to the scene and is created dynamically.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top