1

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.

esote
  • 831
  • 12
  • 25
rbaleksandar
  • 8,713
  • 7
  • 76
  • 161

3 Answers3

2

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.

rbaleksandar
  • 8,713
  • 7
  • 76
  • 161
1

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.

goug
  • 2,294
  • 1
  • 11
  • 15
  • I actually also tried update from within the scene but it didn't work (sorry that I forgot to mention it in the question). I tried passing both no argument to the function call and then passing `sceneRect()` but neither showed a different behaviour from what I described as a problem in post. Btw I'm using OpenGL for the rendering of the scene. Can this have any effect on the way the scene is cleared? – rbaleksandar Nov 18 '16 at 07:18
  • Also I tried calling `invalidate(sceneRect(), BackgroundLayer)` before the `update()`, `clear()` or whatever I tried to trigger a refresh. – rbaleksandar Nov 18 '16 at 07:20
0

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

qApp->processEvents();
update();
Kaaf
  • 350
  • 5
  • 10