rbaleksandar
rbaleksandar

Reputation: 9681

How to refresh QGraphicsView to show changes in the QGraphicsScene's background

I have a custom QGraphicsView and QGraphicsScene. Inside QGraphicsScene I have overriden void drawBackground(QPainter *painter, const QRectF &rect) and based on a boolean flag I want to toggle a grid on and off. I tried calling clear() or calling the painter's eraseRect(sceneRect()) inside my function but it didn't work. So after doing some reading I guess it wasn't supposed to work since after changing the scene you need to refresh the view. That's why I'm emitting a signal called signalUpdateViewport()

void Scene::drawBackground(QPainter *painter, const QRectF &rect) {
    if(this->gridEnabled) {
        // Draw grid
    }
    else {
        // Erase using the painter
        painter->eraseRect(sceneRect());
        // or by calling
        //clear();
    }

    // Trigger refresh of view
    emit signalUpdateViewport();
    QGraphicsScene::drawBackground(painter, rect);
}

which is then captured by my view:

void View::slotUpdateViewport() {
    this->viewport()->update();
}

Needless to say this didn't work. With doesn't work I mean that the results (be it a refresh from inside the scene or inside the view) are made visible only when changing the widget for example triggering a resize event.

How do I properly refresh the view to my scene to display the changed that I have made in the scene's background?

The code:

scene.h

#ifndef SCENE_HPP
#define SCENE_HPP

#include <QGraphicsScene>

class View;

class Scene : public QGraphicsScene
{
    Q_OBJECT
    Q_ENUMS(Mode)
    Q_ENUMS(ItemType)
  public:
    enum Mode { Default, Insert, Select };
    enum ItemType { None, ConnectionCurve, ConnectionLine, Node };

    Scene(QObject* parent = Q_NULLPTR);
    ~Scene();

    void setMode(Mode mode, ItemType itemType);
  signals:
    void signalCursorCoords(int x, int y);

    void signalSceneModeChanged(Scene::Mode sceneMode);
    void signalSceneItemTypeChanged(Scene::ItemType sceneItemType);
    void signalGridDisplayChanged(bool gridEnabled);

    void signalUpdateViewport();
  public slots:
    void slotSetSceneMode(Scene::Mode sceneMode);
    void slotSetSceneItemType(Scene::ItemType sceneItemType);

    void slotSetGridStep(int gridStep);
    void slotToggleGrid(bool flag);
  private:
    Mode sceneMode;
    ItemType sceneItemType;

    bool gridEnabled;
    int gridStep;

    void makeItemsControllable(bool areControllable);
    double round(double val);
  protected:
    void mousePressEvent(QGraphicsSceneMouseEvent *event);
    void mouseMoveEvent(QGraphicsSceneMouseEvent *event);
    void mouseReleaseEvent(QGraphicsSceneMouseEvent *event);
    void keyPressEvent(QKeyEvent *event);

    void drawBackground(QPainter *painter, const QRectF &rect);
};

Q_DECLARE_METATYPE(Scene::Mode)
Q_DECLARE_METATYPE(Scene::ItemType)

#endif // SCENE_HPP

scene.cpp

#include <QGraphicsItem>
#include <QGraphicsView>
#include <QGraphicsSceneMouseEvent>
#include <QPainter>
#include <QRectF>
#include <QKeyEvent>

#include <QtDebug>

#include "scene.h"

Scene::Scene(QObject* parent)
    : QGraphicsScene (parent),
      sceneMode(Default),
      sceneItemType(None),
      gridEnabled(true),
      gridStep(30)
{

}

Scene::~Scene()
{
}

void Scene::setMode(Mode mode, ItemType itemType)
{
  this->sceneMode = mode;
  this->sceneItemType = itemType;

  QGraphicsView::DragMode vMode = QGraphicsView::NoDrag;

  switch(mode) {
    case Insert:
    {
      makeItemsControllable(false);
      vMode = QGraphicsView::NoDrag;
      break;
    }
    case Select:
    {
      makeItemsControllable(true);
      vMode = QGraphicsView::RubberBandDrag;
      break;
    }
    case Default:
    {
      makeItemsControllable(false);
      vMode = QGraphicsView::NoDrag;
      break;
    }
  }

  QGraphicsView* mView = views().at(0);
  if(mView) {
    mView->setDragMode(vMode);
  }
}

void Scene::slotSetSceneMode(Scene::Mode sceneMode)
{
  this->sceneMode = sceneMode;
  qDebug() << "SM" << (int)this->sceneMode;
  emit signalSceneModeChanged(this->sceneMode);
}

void Scene::slotSetSceneItemType(Scene::ItemType sceneItemType)
{
  this->sceneItemType = sceneItemType;
  qDebug() << "SIT:" << (int)this->sceneItemType;
  emit signalSceneItemTypeChanged(this->sceneItemType);
}

void Scene::slotSetGridStep(int gridStep)
{
  this->gridStep = gridStep;
}

void Scene::slotToggleGrid(bool flag)
{
  this->gridEnabled = flag;
  invalidate(sceneRect(), BackgroundLayer);
  qDebug() << "Grid " << (this->gridEnabled ? "enabled" : "disabled");
  update(sceneRect());
  emit signalGridDisplayChanged(this->gridEnabled);
}

void Scene::makeItemsControllable(bool areControllable)
{
  foreach(QGraphicsItem* item, items()) {
    if(/*item->type() == QCVN_Node_Top::Type
       ||*/ item->type() == QGraphicsLineItem::Type
       || item->type() == QGraphicsPathItem::Type) {
      item->setFlag(QGraphicsItem::ItemIsMovable, areControllable);
      item->setFlag(QGraphicsItem::ItemIsSelectable, areControllable);
    }
  }
}

double Scene::round(double val)
{
  int tmp = int(val) + this->gridStep/2;
  tmp -= tmp % this->gridStep;
  return double(tmp);
}

void Scene::mousePressEvent(QGraphicsSceneMouseEvent *event)
{
  QGraphicsScene::mousePressEvent(event);
}

void Scene::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
{
  emit signalCursorCoords(int(event->scenePos().x()), int(event->scenePos().y()));
  QGraphicsScene::mouseMoveEvent(event);
}

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

void Scene::keyPressEvent(QKeyEvent* event)
{
  if(event->key() == Qt::Key_M) {
    slotSetSceneMode(static_cast<Mode>((int(this->sceneMode) + 1) % 3));
  }
  else if(event->key() == Qt::Key_G) {
    slotToggleGrid(!this->gridEnabled);
  }

  QGraphicsScene::keyPressEvent(event);
}

void Scene::drawBackground(QPainter *painter, const QRectF &rect)
{
  // FIXME Clearing and drawing the grid happens only when scene size or something else changes
  if(this->gridEnabled) {
    painter->setPen(QPen(QColor(200, 200, 255, 125)));
    // draw horizontal grid
    double start = round(rect.top());
    if (start > rect.top()) {
      start -= this->gridStep;
    }
    for (double y = start - this->gridStep; y < rect.bottom(); ) {
      y += this->gridStep;
      painter->drawLine(int(rect.left()), int(y), int(rect.right()), int(y));
    }
    // now draw vertical grid
    start = round(rect.left());
    if (start > rect.left()) {
      start -= this->gridStep;
    }
    for (double x = start - this->gridStep; x < rect.right(); x += this->gridStep) {
      painter->drawLine(int(x), int(rect.top()), int(x), int(rect.bottom()));
    }
  }
  else {
    // Erase whole scene's background
    painter->eraseRect(sceneRect());
  }

  slotToggleGrid(this->gridEnabled);

  QGraphicsScene::drawBackground(painter, rect);
}

The View currently doesn't contain anything - all is default. The setting of my Scene and View instance inside my QMainWindow is as follows:

void App::initViewer()
{
  this->scene = new Scene(this);
  this->view = new View(this->scene, this);
  this->viewport = new QGLWidget(QGLFormat(QGL::SampleBuffers), this->view);
  this->view->setRenderHints(QPainter::Antialiasing);
  this->view->setViewport(this->viewport);
  this->view->setViewportUpdateMode(QGraphicsView::FullViewportUpdate);
  this->view->setCacheMode(QGraphicsView::CacheBackground);

  setCentralWidget(this->view);
}

EDIT: I tried calling invalidate(sceneRect(), BackgroundLayer) before the update(), clear() or whatever I tried to trigger a refresh.

I also tried QGraphicsScene::update() from within the scene but it didn't work Passing both no argument to the function call and then passing sceneRect() didn't result in anything different then what I've described above.

Upvotes: 1

Views: 9432

Answers (3)

Kaaf
Kaaf

Reputation: 380

Sometimes when the view/scene refuse to run update() directly, processing the app events before the update() fixes it, like so:

qApp->processEvents();
update();

Upvotes: 0

rbaleksandar
rbaleksandar

Reputation: 9681

Found the issue - I forgot to set the size of the scene's rectangle:

this->scene->setSceneRect(QRectF(QPointF(-1000, 1000), QSizeF(2000, 2000)));

I actually found the problem by deciding to print the size of the QRectF returned by the sceneRect() call and when I looked at the output I saw 0, 0 so basically I was indeed triggering the update but on a scene with the area of 0 which (obviously) would result in nothing.

Another thing that I tested and worked was to remove the background caching of my view.

Upvotes: 2

goug
goug

Reputation: 2444

When you change your grid settings, whether it's on or off (or color, etc.), you need to call QGraphicsScene::update. That's also a slot, so you can connect a signal to it if you want. You can also specify the exposed area; if you don't, then it uses a default of everything. For a grid, that's probably what you want.

You don't need to clear the grid. The update call ensures that the updated area gets cleared, and then you either paint on it if you want the grid, or don't paint on it if the grid shouldn't be there.

Upvotes: 1

Related Questions