Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. Custom QAbstractTableModel class updating QTableView

Custom QAbstractTableModel class updating QTableView

Scheduled Pinned Locked Moved Solved General and Desktop
qabstracttablemqtableviewc++datachangedqt5
13 Posts 2 Posters 5.8k Views
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • S Offline
    S Offline
    Sebbo
    wrote on 5 Aug 2016, 08:11 last edited by A Former User 8 May 2016, 19:28
    #1

    Hi all,

    I already read a few threads on this forum dealing with similar issues but sadly they didn't provide "the" solution.

    I built my own QAbstractTableModel class which fills a QTableView with data from a .csv file. The file is used for logging purposes and stores its (new) data every 10 minutes. The data is shown inside a QTableView but needs to be updated manually (button or closing and reopening the QDialog). What I want to achieve is that the view is automatically updated whenever new data is written to the .csv file.

    The code snippets of my TableModel looks as follows:

    int QCsvTableModel::rowCount(const QModelIndex &parent) const
    {
        Q_UNUSED(parent);
        return csvMatrix.rowCount();
    }
    
    int QCsvTableModel::columnCount(const QModelIndex &parent) const
    {
        Q_UNUSED(parent);
        return csvMatrix.columnCount();
    }
    
    QVariant QCsvTableModel::data(const QModelIndex &index, int role) const
    {
        if (index.isValid())
            if (role == Qt::DisplayRole || role == Qt::EditRole)
                return csvMatrix.at(index.row(), index.column());
        return QVariant();
    }
    
    bool QCsvTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
    {
        if (index.isValid() && role == Qt::EditRole) {
            csvMatrix.setValue(index.row(), index.column(), value.toString());
    
            emit dataChanged(index,index);     // No difference if dataChanged is emitted or not
    
            return true;
        }
    
        return false;
    }
    

    MainWindow source:

    MainWindow::MainWindow(QWidget *parent) :
        QMainWindow(parent),
        ui(new Ui::MainWindow)
    {
        ui->setupUi(this);
    
        QString fileName = ":/value.csv";
    
        if (!fileName.isEmpty()) {
            QCsvTableModel *model = new QCsvTableModel(this);
    
            QString extension = QFileInfo(QFile(fileName)).completeSuffix();
    
            if (extension.toLower() == "csv")     // known file extension
                model->loadFromFile(fileName);
    
            ui->tableView->setModel(model);
        } // if fileName ..
    
        connect(ui->tableView->model(), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
                       this, SLOT(onModelsDataChanged(const QModelIndex&, const QModelIndex&)));
    }
    
    void MainWindow::onModelsDataChanged(const  QModelIndex  &topLeft,  const  QModelIndex  &bottomRight)
    {
        Q_UNUSED(topLeft);
        Q_UNUSED(bottomRight);
    
        qDebug() << "The data has changed." << endl;
    }
    

    But the dataChanged() signal isn't fired at all and "The data has changed." never shows up.

    What am I missing? Any help is appreciated!

    Seb

    R 1 Reply Last reply 5 Aug 2016, 08:30
    0
    • S Sebbo
      5 Aug 2016, 08:11

      Hi all,

      I already read a few threads on this forum dealing with similar issues but sadly they didn't provide "the" solution.

      I built my own QAbstractTableModel class which fills a QTableView with data from a .csv file. The file is used for logging purposes and stores its (new) data every 10 minutes. The data is shown inside a QTableView but needs to be updated manually (button or closing and reopening the QDialog). What I want to achieve is that the view is automatically updated whenever new data is written to the .csv file.

      The code snippets of my TableModel looks as follows:

      int QCsvTableModel::rowCount(const QModelIndex &parent) const
      {
          Q_UNUSED(parent);
          return csvMatrix.rowCount();
      }
      
      int QCsvTableModel::columnCount(const QModelIndex &parent) const
      {
          Q_UNUSED(parent);
          return csvMatrix.columnCount();
      }
      
      QVariant QCsvTableModel::data(const QModelIndex &index, int role) const
      {
          if (index.isValid())
              if (role == Qt::DisplayRole || role == Qt::EditRole)
                  return csvMatrix.at(index.row(), index.column());
          return QVariant();
      }
      
      bool QCsvTableModel::setData(const QModelIndex &index, const QVariant &value, int role)
      {
          if (index.isValid() && role == Qt::EditRole) {
              csvMatrix.setValue(index.row(), index.column(), value.toString());
      
              emit dataChanged(index,index);     // No difference if dataChanged is emitted or not
      
              return true;
          }
      
          return false;
      }
      

      MainWindow source:

      MainWindow::MainWindow(QWidget *parent) :
          QMainWindow(parent),
          ui(new Ui::MainWindow)
      {
          ui->setupUi(this);
      
          QString fileName = ":/value.csv";
      
          if (!fileName.isEmpty()) {
              QCsvTableModel *model = new QCsvTableModel(this);
      
              QString extension = QFileInfo(QFile(fileName)).completeSuffix();
      
              if (extension.toLower() == "csv")     // known file extension
                  model->loadFromFile(fileName);
      
              ui->tableView->setModel(model);
          } // if fileName ..
      
          connect(ui->tableView->model(), SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)),
                         this, SLOT(onModelsDataChanged(const QModelIndex&, const QModelIndex&)));
      }
      
      void MainWindow::onModelsDataChanged(const  QModelIndex  &topLeft,  const  QModelIndex  &bottomRight)
      {
          Q_UNUSED(topLeft);
          Q_UNUSED(bottomRight);
      
          qDebug() << "The data has changed." << endl;
      }
      

      But the dataChanged() signal isn't fired at all and "The data has changed." never shows up.

      What am I missing? Any help is appreciated!

      Seb

      R Offline
      R Offline
      raven-worx
      Moderators
      wrote on 5 Aug 2016, 08:30 last edited by
      #2

      @Sebbo
      how does your loadFromFile() method look like?

      The dataChanged() signal is only emitted if the data for an existing index changed and needs update. You won't receive it for the loadFromFile() call, instead a model reset should happen there.

      --- SUPPORT REQUESTS VIA CHAT WILL BE IGNORED ---
      If you have a question please use the forum so others can benefit from the solution in the future

      1 Reply Last reply
      0
      • S Offline
        S Offline
        Sebbo
        wrote on 5 Aug 2016, 08:34 last edited by
        #3

        Hi raven-worx,

        thanks for your reply!!
        The loadFromFile() method looks as follows:

        bool QCsvTableModel::loadFromFile(const QString &fileName, const QChar &delim)
        {
            csvMatrix.clear();
            QChar delimiter;
            QFile file(fileName);
        
            if (delim == 0) {
                QString extension = QFileInfo(file).completeSuffix();
                if (extension.toLower() == "csv")
                    delimiter = QChar(';');
            }
            else if (delim == QChar('"'))
                    return false; // The only invalid delimiter is double quote (")
            else
                delimiter = delim;
            if (!file.isOpen())
                if (!file.open(QFile::ReadOnly|QFile::Text))
                    return false;
        
            QString temp;
            QChar lastCharacter;
            QTextStream in(&file);
            QList<QString> row;
        
            while (true) {
                QChar character;
                in >> character;
                if (in.atEnd()) {
                    if (lastCharacter == delimiter) // Cases where last character is equal to the delimiter
                        temp = "";
                    checkString(temp, row, csvMatrix, delimiter, QChar('\n'));
                    break;
                } else if (character == delimiter || character == QChar('\n'))
                    checkString(temp, row, csvMatrix, delimiter, character);
                else {
                    temp.append(character);
                    lastCharacter = character;
                }
            }
        
            /*
            int row1 = csvMatrix.rowCount();
            QModelIndex index;
            beginInsertRows(QModelIndex(), row1, row1 + index.row() - 1);
            QModelIndex transposedIndex = createIndex(index.column(), index.row());
            emit dataChanged(transposedIndex, transposedIndex);
            emit layoutChanged();
            endInsertRows();
            */
        
            file.close();
            in.flush();
            in.reset();
        
            return true;
        }
        

        As you can see I already tried to emit the dataChanged signal from there but nothing happened.

        R 1 Reply Last reply 5 Aug 2016, 08:44
        0
        • S Sebbo
          5 Aug 2016, 08:34

          Hi raven-worx,

          thanks for your reply!!
          The loadFromFile() method looks as follows:

          bool QCsvTableModel::loadFromFile(const QString &fileName, const QChar &delim)
          {
              csvMatrix.clear();
              QChar delimiter;
              QFile file(fileName);
          
              if (delim == 0) {
                  QString extension = QFileInfo(file).completeSuffix();
                  if (extension.toLower() == "csv")
                      delimiter = QChar(';');
              }
              else if (delim == QChar('"'))
                      return false; // The only invalid delimiter is double quote (")
              else
                  delimiter = delim;
              if (!file.isOpen())
                  if (!file.open(QFile::ReadOnly|QFile::Text))
                      return false;
          
              QString temp;
              QChar lastCharacter;
              QTextStream in(&file);
              QList<QString> row;
          
              while (true) {
                  QChar character;
                  in >> character;
                  if (in.atEnd()) {
                      if (lastCharacter == delimiter) // Cases where last character is equal to the delimiter
                          temp = "";
                      checkString(temp, row, csvMatrix, delimiter, QChar('\n'));
                      break;
                  } else if (character == delimiter || character == QChar('\n'))
                      checkString(temp, row, csvMatrix, delimiter, character);
                  else {
                      temp.append(character);
                      lastCharacter = character;
                  }
              }
          
              /*
              int row1 = csvMatrix.rowCount();
              QModelIndex index;
              beginInsertRows(QModelIndex(), row1, row1 + index.row() - 1);
              QModelIndex transposedIndex = createIndex(index.column(), index.row());
              emit dataChanged(transposedIndex, transposedIndex);
              emit layoutChanged();
              endInsertRows();
              */
          
              file.close();
              in.flush();
              in.reset();
          
              return true;
          }
          

          As you can see I already tried to emit the dataChanged signal from there but nothing happened.

          R Offline
          R Offline
          raven-worx
          Moderators
          wrote on 5 Aug 2016, 08:44 last edited by raven-worx 8 May 2016, 08:46
          #4

          @Sebbo
          First please make sure that the connect() call returns true. If not check the console output for the cause.

          Also no line of this code makes any (functional) sense:

          int row1 = csvMatrix.rowCount();
          QModelIndex index;
          beginInsertRows(QModelIndex(), row1, row1 + index.row() - 1);
          QModelIndex transposedIndex = createIndex(index.column(), index.row());
          emit dataChanged(transposedIndex, transposedIndex);
          emit layoutChanged();
          endInsertRows();
          

          As i said the correct approach for the loadFromFile() method would be to call beginRestModel() at the beginning of the method and endRestModel() at the end of the method. No dataChanged(), no beginInsertRows(), no layoutChanged(), etc. signals ...

          The dataChanged() signal should only called for already existing indexes.

          --- SUPPORT REQUESTS VIA CHAT WILL BE IGNORED ---
          If you have a question please use the forum so others can benefit from the solution in the future

          1 Reply Last reply
          0
          • S Offline
            S Offline
            Sebbo
            wrote on 5 Aug 2016, 09:06 last edited by
            #5

            @raven-worx

            I forgot to tell that I tested the method with beginResetModel() and endResetModel() as well (without trying to emit all the signals) which didn't do the trick. My apologies.

            The connect() call returns true.

            R 1 Reply Last reply 5 Aug 2016, 09:12
            0
            • S Sebbo
              5 Aug 2016, 09:06

              @raven-worx

              I forgot to tell that I tested the method with beginResetModel() and endResetModel() as well (without trying to emit all the signals) which didn't do the trick. My apologies.

              The connect() call returns true.

              R Offline
              R Offline
              raven-worx
              Moderators
              wrote on 5 Aug 2016, 09:12 last edited by raven-worx 8 May 2016, 09:12
              #6

              @Sebbo
              but to make sure i didn't mean that the beginResetModel()/endResetModel() signals do internally emit the dataChanged() singal! Instead they emit modelAboutToBeReset() and modelReset() respectively.

              Whats do you actually want to achieve in the onModelsDataChanged() slot?

              --- SUPPORT REQUESTS VIA CHAT WILL BE IGNORED ---
              If you have a question please use the forum so others can benefit from the solution in the future

              1 Reply Last reply
              0
              • S Offline
                S Offline
                Sebbo
                wrote on 5 Aug 2016, 09:25 last edited by
                #7

                @raven-worx
                Thank you for the effort!!

                For testing purposes I've added a pushbutton which adds a row with some foo-text to the csv file. But since I wasn't able to show the newly stored data inside the qtableview I wanted to see whether the dataChanged() signal is emitted and calls the method with just a console output.
                What I've done in first place was to repaint() / update() the tableview inside the onModelsDataChanged() method.

                R 1 Reply Last reply 5 Aug 2016, 09:49
                0
                • S Sebbo
                  5 Aug 2016, 09:25

                  @raven-worx
                  Thank you for the effort!!

                  For testing purposes I've added a pushbutton which adds a row with some foo-text to the csv file. But since I wasn't able to show the newly stored data inside the qtableview I wanted to see whether the dataChanged() signal is emitted and calls the method with just a console output.
                  What I've done in first place was to repaint() / update() the tableview inside the onModelsDataChanged() method.

                  R Offline
                  R Offline
                  raven-worx
                  Moderators
                  wrote on 5 Aug 2016, 09:49 last edited by raven-worx 8 May 2016, 09:50
                  #8

                  @Sebbo
                  ok here are my thoughts:

                  The simplest approach is the "static" one. It just displays the contents of the csv file at the time it is opened:

                  • in the loadFromFile() method do like i said and use the reset mechanism; the reset tells the view that it should forget all about what it knows from the model and that it should get all the data again

                  The disadvantage of the rest-method is that the view looses it's selection, scroll position, current index, etc.

                  For a more "dynamic" approach you need to do this:

                  1. implement the same loadFromFile() method like for the "static" approach. But additionally you also install an QFileSystemWatcher on that file and connect it's fileChanged() signal to a new slot called something like "csvFileContentsChanged"
                  2. in the loadFromFile() method you need to uninstall the old QFileSystemWatcher everytime a new/different file is set
                  3. also save the count of lineNumbers read from the csv file
                  4. in the new csvFileContentsChanged slot read the csv file line by line and compare the new line count to the previously stored one. If the new line count is higher you need to call beginInsertRows()/endInsertRows() signals, if the new count is smaller you need to call beginRemoveRows()/endRemoveRows(). And dataChanged() for the rest of the indexes which are already there.

                  Pseudo code:

                  void csvFileContentsChanged()
                  {
                       // read cvs file again into a local "tmpCsvMatrix" temporary variable
                  
                       int newLineCount = ...;
                       int diff = qAbs( newLineCount - oldLineCount );
                  
                      if( newLineCount < oldLineCount )
                      {
                              beginRemoveRows( QModelindex(), newLineCount, newLineCount + diff - 1 );
                                  csvMatrix = tmpCsvmatrix;
                              endRemoveRows();
                      }
                      else if( newLineCount > oldLineCount )
                      {
                             beginInsertRows( QModelindex(), oldLineCount, oldLineCount + diff - 1 );
                                  csvMatrix = tmpCsvMatrix;
                              endInsertRows();
                      }
                  
                      // emit dataChanged for the rest of the indexes, since we do not know if their content actually has changed inside the csv file
                      if( rowCount() > 0 && columnsCount() > 0 )
                            emit dataChanged( index(0, columnCount()-1), index(newLineCount-1 ,columnCount()-1) );
                  }
                  

                  I haven't tested this though.

                  --- SUPPORT REQUESTS VIA CHAT WILL BE IGNORED ---
                  If you have a question please use the forum so others can benefit from the solution in the future

                  1 Reply Last reply
                  1
                  • S Offline
                    S Offline
                    Sebbo
                    wrote on 5 Aug 2016, 10:10 last edited by
                    #9

                    @raven-worx

                    Thank you so much for your efforts and thoughts!!!
                    I like the idea of your 2nd dynamic approach but I have to admit that the 1st one still doesn't work for me.

                    beginResetModel();
                    csvMatrix.clear();
                    endResetModel();
                    

                    doesn't update the QTableView neither if new data is added outside Qt nor if the QPushButton is clicked which opens the file, writes into it (QTextStream) and closes it afterwards. :(

                    R 1 Reply Last reply 5 Aug 2016, 10:22
                    0
                    • S Sebbo
                      5 Aug 2016, 10:10

                      @raven-worx

                      Thank you so much for your efforts and thoughts!!!
                      I like the idea of your 2nd dynamic approach but I have to admit that the 1st one still doesn't work for me.

                      beginResetModel();
                      csvMatrix.clear();
                      endResetModel();
                      

                      doesn't update the QTableView neither if new data is added outside Qt nor if the QPushButton is clicked which opens the file, writes into it (QTextStream) and closes it afterwards. :(

                      R Offline
                      R Offline
                      raven-worx
                      Moderators
                      wrote on 5 Aug 2016, 10:22 last edited by raven-worx 8 May 2016, 10:24
                      #10

                      @Sebbo
                      because is said you should trigger these signals at the beginning and the end of the loadFromFile() method. Mens the very first and very last call inside the method.
                      Between those 2 signal calls you need to update your data structure.

                      --- SUPPORT REQUESTS VIA CHAT WILL BE IGNORED ---
                      If you have a question please use the forum so others can benefit from the solution in the future

                      1 Reply Last reply
                      0
                      • S Offline
                        S Offline
                        Sebbo
                        wrote on 5 Aug 2016, 11:16 last edited by
                        #11

                        @raven-worx

                        That is exactly what I'm not getting right now. Don't get me wrong I know where to put the begin/endReset members of the model but I'm embarrassingly lost on how to update the data in between....

                        R 1 Reply Last reply 5 Aug 2016, 11:23
                        0
                        • S Sebbo
                          5 Aug 2016, 11:16

                          @raven-worx

                          That is exactly what I'm not getting right now. Don't get me wrong I know where to put the begin/endReset members of the model but I'm embarrassingly lost on how to update the data in between....

                          R Offline
                          R Offline
                          raven-worx
                          Moderators
                          wrote on 5 Aug 2016, 11:23 last edited by raven-worx 8 May 2016, 11:24
                          #12

                          @Sebbo
                          in between these 2 signals you just need to replace your csvMatrixvariable, thats all
                          Since you take all the model data from this variable

                          --- SUPPORT REQUESTS VIA CHAT WILL BE IGNORED ---
                          If you have a question please use the forum so others can benefit from the solution in the future

                          1 Reply Last reply
                          0
                          • S Offline
                            S Offline
                            Sebbo
                            wrote on 5 Aug 2016, 19:12 last edited by
                            #13

                            @raven-worx
                            Jackpot! Thank you so much for your help!! Finally I did it using QFileSystemWatcher.
                            Thumbs up for you (if possible?!). :)

                            Thread marked as solved.
                            Cheers

                            1 Reply Last reply
                            0

                            10/13

                            5 Aug 2016, 10:22

                            • Login

                            • Login or register to search.
                            10 out of 13
                            • First post
                              10/13
                              Last post
                            0
                            • Categories
                            • Recent
                            • Tags
                            • Popular
                            • Users
                            • Groups
                            • Search
                            • Get Qt Extensions
                            • Unsolved