Search code examples
c++qtpaintevent

QListView update - does not trigger update


I have following problem:

when I call update() on QListView, its paintEvent() is not triggered unless some other event occurs over the widget (mouse move, got focus....)

I am using Qt 4.8.3, and unless this is definitely bug in the version, I would prefer not upgrading (as from my experience upgrades bears more trouble than benefits).

The question: How to make QListView (and Q...View) to update next time main loop gets control?

Some background as what I am solving if it helps:

Meant as single threaded application.

At the bottom is some independent (no Qt) model, which is hierarchical, and consumers request subitems. Items at the bottom of hierarchy may be modified.

On modification, consumer requests W(ritable)Item. At that point, Model parts influenced by the change reports "modified" via observer approach. So observers are notified at the start of the change (model returns writable object, has no control or idea when change ends).

Consumer is expected to finish modification before returning from function/method which started modification.

Modifying methods/functions are expected to be called from main thread, so next time main thread tinkers with GUI, model is in consistent state, and consumers can refresh.

QModels are done to provide data from model below in Qt accessible format.

Next are QWidgets (lists/textboxes/labels) visualising data to user, these are modified to support Desync() method, which mark visualised data to be out of sync, and overridden paintEvent, which checks for inSync state. For simple QWidgets like labels, on synchronization, callback is called, which just fills in the data. For Q...View, I assumed to force models to emit modelReset, so list reloads number of rows and content of visible ones.

On the top is class collecting it all together under its region, which is hooked to observers, and on reported changes Desync relevant widgets.

All methods changing anything are connected via signal/slot Qt thingie to buttons/comboboxes/other GUI elements, so I assume it all runs under main thread.

Idea of change:

  • GUI induced event, main thread starts processing consumer's change method
  • consumer obtains necessary items for change
  • consumer obtains writable items
  • real model reports modified to observers
  • observers mark (Desync) relevant QWidgets as out of sync
  • QWidgets are marked as out of sync, and scheduled to update, yet do not attempt to access anything, as we run under main thread
  • consumer performs change, during which real model might be even inconsistent
  • consumer returns control to whatever called it
  • main loop performs updates, which are overridden to sync widgets

* What I observed: *

  • update() results in paintEvent for most widgets that do not have model at all (label/ textbox...)
  • update() does not result in paintEvent for QListView
  • repaint() does not help (was just wild attempt)
  • moving mouse over widget results in paintEvent, and QWidget synchronizes
  • trying to visible(false); update(); visible(true); repaints immediately
    • that is wrong, as QWidget synchronizes before consumer performs the change
  • switching windows (i.e. visual studio) or back results in paintEvent being called

Simplified sources where to get the behavior:

myList.h

  #ifndef __myList_h__
  #define __myList_h__

  #include <qlistview.h>

  class myList : public QListView
  {
     bool inSync;
     void sync();

     protected:
        virtual void paintEvent(QPaintEvent * event) override;

     public:
        myList(QWidget * parent);
        void Desync();
        virtual ~myList();
  };

  #endif

myList.cpp

  #include "myList.h"
  #include "myModel.h"

  void myList::sync()
  {
     if (inSync)
        return;

     inSync = true; //< set early, to prevent loops
     ((myModel*)model())->ResetModel();
  }

  void myList::paintEvent(QPaintEvent * event)
  {
     sync();
     QListView::paintEvent(event);
  }

  myList::myList(QWidget * parent) : QListView(parent), inSync(false)
  {}

  void myList::Desync()
  {
     inSync = false;
     update();
  }

  myList::~myList()
  {}

myModel.h

  #ifndef __myModel_h__
  #define __myModel_h__

  #include <QAbstractListModel>

  class myModel : public QAbstractListModel
  {
     Q_OBJECT;

     int & externalComplexData;

     public:
        myModel(int & externalComplexData);

        virtual int rowCount(QModelIndex const & parent = QModelIndex()) const override;
        virtual QVariant data(QModelIndex const & index, int role) const override;

        void ResetModel();

        virtual ~myModel();

  };

  #endif

myModel.cpp

  #include "myModel.h"

  myModel::myModel(int & externalComplexData) : externalComplexData(externalComplexData)
  {}

  int myModel::rowCount(QModelIndex const & parent) const
  {
     return 1;
  }

  QVariant myModel::data(QModelIndex const & index, int role) const
  {
     if (role != Qt::DisplayRole)
        return QVariant();

     return QString::number(externalComplexData);
  }

  void myModel::ResetModel()
  {
     reset();
  }

  myModel::~myModel()
  {}

tmp.h

  #ifndef __Tmp_H__
  #define __Tmp_H__

  #include <QtGui/QMainWindow>
  #include "ui_tmp.h"

  class tmp : public QMainWindow
  {
      Q_OBJECT

     public:
         tmp(QWidget *parent = 0, Qt::WFlags flags = 0);
         ~tmp();

     private:
         Ui::tmpClass ui;

     private slots:
         void clicked();
  };

  #endif

tmp.cpp

  #include "tmp.h"
  #include "myModel.h"

  int localComplexData = 0;

  tmp::tmp(QWidget *parent, Qt::WFlags flags)
      : QMainWindow(parent, flags)
  {
     ui.setupUi(this);

     ui.lst->setModel(new myModel(localComplexData));
     connect(ui.btn, SIGNAL(clicked()), this, SLOT(clicked()));
  }

  void tmp::clicked()
  {
     ui.lst->Desync();
     ++localComplexData;
  }

  tmp::~tmp()
  {}

Behavior: Clicking button updates external model, yet the list is not synchronized.

When moving mouse over the list, it synchronizes.

Expected Behavior: Registering programmer's wish to update(), and result in paintEvent next time main loop gets the control (or even few loops later).


Solution

  • You did it wrong. Do not touch QListView you don't have to. Just fix data model (Qt) and rest will work out of box.

    Your model myModel should simply invoke proper methods when data are changing. This model should observe source of real data. When something will happen to the data:

    If you do this properly nothing else is needed.