Walletfox.com

How to draw QGraphicsLineItem during run time with mouse coordinates


This article shows how to draw a QGraphicsLineItem with mouse coordinates during run time. This is demonstrated on an example that has two operation modes, namely drawing and selection mode.

Drawing mode
Selection mode

To obtain the desired functionality we need to subclass QGraphicsScene and QMainWindow. In the derived QGraphicsScene class we:

  • override mousePressEvent(), mouseMoveEvent() and mouseReleaseEvent()
  • introduce QGraphicsLineItem* itemToDraw which stores the address of the currently drawn item
  • introduce QPointF origPos that stores the starting position of the currently drawn item
  • introduce enum Mode to distinguish between the drawing and selection mode

In the derived QMainWindow class we:

  • introduce 2 QActions - lineAction and selectAction, as well as QActionGroup that ensures exclusivity of these two actions
  • introduce a slot actionGroupClicked(QAction*) that switches between scene modes

How to subclass QGraphicsScene

Below you can see how to subclass QGraphicsScene. Apart from overriding event handlers, we also introduce a helper function makeItemsControllable(bool) that adapts the selectability and mobility of items depending on the scene mode. We also override keyPressEvent() in order to delete selected items with the Delete key.

class Scene : public QGraphicsScene
{
public:
    enum Mode {NoMode, SelectObject, DrawLine};
    Scene(QObject* parent = 0);
    void setMode(Mode mode);
protected:
    void mousePressEvent(QGraphicsSceneMouseEvent *event);
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
    void keyPressEvent(QKeyEvent *event);
private:
    Mode sceneMode;
    QPointF origPoint;
    QGraphicsLineItem* itemToDraw;
    void makeItemsControllable(bool areControllable);
};

How to override QGraphicsScene::mousePressEvent(), mouseMoveEvent() and mouseReleaseEvent()

In mousePressEvent() the user specifies the starting point of the line. We will store this point in QPointF origPos. The point remains the same during the entire process of drawing a line. You can see this below:

void Scene::mousePressEvent(QGraphicsSceneMouseEvent *event){
    if(sceneMode == DrawLine)
        origPoint = event->scenePos();
    QGraphicsScene::mousePressEvent(event);
}

In mouseMoveEvent() we firstly check whether an instance of a QGraphicsLineItem already exists. If not, we create a new instance of QGraphicsLineItem and set its position to the origPos. We do this only once. In every subsequent call to mouseMoveEvent() we update the bounding line that defines the QGraphicsLineItem. The bounding line always starts at (0,0), its end point is determined by the distance between the event->scenePos() and origPoint. The actual position of the QGraphicsLineItem was indicated by setPos(QPointF) in the 'if block'.


void Scene::mouseMoveEvent(QGraphicsSceneMouseEvent *event){
    if(sceneMode == DrawLine){
        if(!itemToDraw){
            itemToDraw = new QGraphicsLineItem(0, this);
            itemToDraw->setPen(QPen(Qt::black, 3, Qt::SolidLine));
            itemToDraw->setPos(origPoint);
        }
        itemToDraw->setLine(0,0,
                            event->scenePos().x() - origPoint.x(),
                            event->scenePos().y() - origPoint.y());
    }
    else
        QGraphicsScene::mouseMoveEvent(event);
}

In mouseReleaseEvent() we terminate the drawing by setting the itemToDraw pointer to 0.

void Scene::mouseReleaseEvent(QGraphicsSceneMouseEvent *event){
    itemToDraw = 0;
    QGraphicsScene::mouseReleaseEvent(event);
}

How to subclass QMainWindow

The code snippet below shows how to subclass QMainWindow. The line drawing mode is represented by lineAction, while the selection mode is represented by selectAction. Notice that we introduced QActionGroup so that only one of lineAction or selectAction is active at any given time.

class MainWindow : public QMainWindow
{
    Q_OBJECT
public:
    MainWindow();
public slots:
    void actionGroupClicked(QAction*);
private:
    QGraphicsView* view;
    Scene* scene;

    void createActions();
    void createConnections();
    void createToolBar();

    QAction* lineAction;
    QAction* selectAction;
    QActionGroup *actionGroup;
    QToolBar* drawingToolBar;
};

In MainWindow::createActions() we instantiate lineAction and selectAction and add them to the actionGroup. Notice that we also call setData(QVariant) on QActions in order to associate them with a particular scene mode.

void MainWindow::createActions(){
    lineAction = new QAction("Draw line", this);
    lineAction->setData(int(Scene::DrawLine));
    lineAction->setIcon(QIcon(":/icons/line.png"));
    lineAction->setCheckable(true);

    selectAction = new QAction("Select object", this);
    selectAction->setData(int(Scene::SelectObject));
    selectAction->setIcon(QIcon(":/icons/select.png"));
    selectAction->setCheckable(true);

    actionGroup = new QActionGroup(this);
    actionGroup->setExclusive(true);
    actionGroup->addAction(lineAction);
    actionGroup->addAction(selectAction);
}

In MainWindow::createConnections() we connect the actionGroup's signal triggered(QAction*) to the slot actionGroupClicked(QAction*). As can be seen below, the task of the actionGroupClicked(QAction*) is to set the scene's mode.

void MainWindow::createConnections(){
    connect(actionGroup, SIGNAL(triggered(QAction*)),
            this, SLOT(actionGroupClicked(QAction*)));
}

void MainWindow::actionGroupClicked(QAction *action){
    scene->setMode(Scene::Mode(action->data().toInt()));
}

How to implement Scene::setMode(Mode)

To distinguish between the modes, we introduce an enum Mode into the class Scene. We switch between the modes with Scene::setMode(Mode) which you can see below. In DrawLine mode, we disable movement and selectability of all items, so that we do not accidentally select other items while drawing the current item. We also adjust the QGraphicsView's drag mode based on the current scene mode. While drawing an item, we set the mode to QGraphicsView::NoDrag as we want to use the mouse drag for drawing an item, rather than for selection. In all other cases, we allow the selection with RubberBandDrag.

void Scene::setMode(Mode mode){
    sceneMode = mode;
    QGraphicsView::DragMode vMode =
            QGraphicsView::NoDrag;
    if(mode == DrawLine){
        makeItemsControllable(false);
        vMode = QGraphicsView::NoDrag;
    }
    else if(mode == SelectObject){
        makeItemsControllable(true);
        vMode = QGraphicsView::RubberBandDrag;
    }
    QGraphicsView* mView = views().at(0);
    if(mView)
        mView->setDragMode(vMode);
}

That's it! Try clicking on the 'Draw line' icon and draw a line by dragging the mouse. You can select items by clicking on 'Select' icon and dragging a selection rectangle around items. For a more convenient selection and manipulation of QGraphicsLineItem check this article.

Tagged: Qt