Search code examples
qtqt5qwidgetqtableview

Updating view based on model update on Qt


I am having trouble updating my qtableview when the data wrapped by the model the view uses changes. I am not talking about appending/adding data here, I am specifically talking about modifying existing data. View is only updated when I scroll or select cell(s). In other words, it is only updated when repainting is triggered (this is my guess actually). So, this made me think that calling update() on widget when the data is modified would suffice, but this was not the case.

I am adding data to the model programmatically, user cannot modify or add data in the model.

Here is the minimal, reproducible example:

mymodel.h:

#pragma once

#include <QAbstractTableModel>
#include <QVector>

#include "myobject.h"

class MyModel : public QAbstractTableModel
{
    Q_OBJECT
private:
    QVector<MyObject> my_objects_; /// underlying data structure for the model.
public:
    MyModel(QObject * parent = {});
    int rowCount(const QModelIndex &) const override;
    int columnCount(const QModelIndex &) const override;
    QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
    QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::EditRole) const override;

    void add(const MyObject& my_object);
    void edit(const MyObject& my_object);
};

mymodel.cpp:

#include "mymodel.h"

#include <QDebug>
#include <iostream>

MyModel::MyModel(QObject * parent)
    : QAbstractTableModel{parent}
{
}

int MyModel::rowCount(const QModelIndex &) const
{
    return my_objects_.count();
}

int MyModel::columnCount(const QModelIndex &) const {
    return 4;
}

QVariant MyModel::data(const QModelIndex &index, int role) const {
   const auto row = index.row();
   if(row == -1) {
       return {};
   }

   const auto my_object = my_objects_[row];

   if(role == Qt::DisplayRole) {
       switch (index.column()) {
           case 0: return my_object.id;
           case 1: return my_object.a;
           case 2: return my_object.b;
           case 3: return my_object.c;
           default: return {};
       };
   }

   return {};
}

QVariant MyModel::headerData(int section, Qt::Orientation orientation, int role) const {
   if (orientation != Qt::Horizontal || role != Qt::DisplayRole) {
       return {};
   }

   switch (section) {
       case 0: return "id";
       case 1: return "a";
       case 2: return "b";
       case 3: return "c";
       default: return {};
   }
}

void MyModel::add(const MyObject &my_object)
{
    beginInsertRows({}, my_objects_.count(), my_objects_.count());
    my_objects_.push_back(my_object);
    endInsertRows();
}

void MyModel::edit(const MyObject& my_object) {
    const auto id = my_object.id;

    for(auto& my_object_ : my_objects_) {
        if(my_object_.id == id) {

            my_object_.a = my_object.a;
            my_object_.b = my_object.b;
            my_object_.c = my_object.c;
            /// should I use dataChanged signal here? If so, how?

            break;
        }
    }
}

myobject.h:

#pragma once

#include <QString>

struct MyObject {
    int id;
    int a;
    double b;
    QString c;
};

mainwindow.h:

#pragma once

#include <QMainWindow>
#include "mymodel.h"

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void onEditClicked();

private:
    Ui::MainWindow *ui;
    MyModel my_model_;
};

mainwindow.cpp:

#include "mainwindow.h"
#include "./ui_mainwindow.h"

const static QVector<MyObject> g_my_objects{
    {0, 1, 1.1, "object1"},
    {1, 11, 1.2, "object2"},
    {2, 12, 1.3, "object3"},
    {3, 13, 1.4, "object4"},
    {4, 14, 1.5, "object5"},
    {5, 15, 1.6, "object6"},
    {6, 16, 1.7, "object7"},
    {7, 17, 1.8, "object8"}
};

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);

    ui->tableView->setModel(&my_model_);

    for(const auto& g_my_object : g_my_objects) {
        my_model_.add(g_my_object);
    }
}

MainWindow::~MainWindow()
{
    delete ui;
}

void MainWindow::onEditClicked()
{
    qDebug() << __PRETTY_FUNCTION__;

    my_model_.edit({2, 22222, 2222.222, "myobject22222"});

    update(); /// seems to be doing nothing.
}

main.cpp:

#include "mainwindow.h"

#include <QApplication>
#include <QVector>

#include "myobject.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();
    return a.exec();
}

mainwindow.ui:

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
 <class>MainWindow</class>
 <widget class="QMainWindow" name="MainWindow">
  <property name="geometry">
   <rect>
    <x>0</x>
    <y>0</y>
    <width>800</width>
    <height>600</height>
   </rect>
  </property>
  <property name="windowTitle">
   <string>MainWindow</string>
  </property>
  <widget class="QWidget" name="centralwidget">
   <layout class="QVBoxLayout" name="verticalLayout">
    <item>
     <widget class="QPushButton" name="pushButton">
      <property name="text">
       <string>Edit</string>
      </property>
     </widget>
    </item>
    <item>
     <widget class="QTableView" name="tableView"/>
    </item>
   </layout>
  </widget>
  <widget class="QMenuBar" name="menubar">
   <property name="geometry">
    <rect>
     <x>0</x>
     <y>0</y>
     <width>800</width>
     <height>23</height>
    </rect>
   </property>
  </widget>
  <widget class="QStatusBar" name="statusbar"/>
 </widget>
 <resources/>
 <connections>
  <connection>
   <sender>pushButton</sender>
   <signal>clicked()</signal>
   <receiver>MainWindow</receiver>
   <slot>onEditClicked()</slot>
   <hints>
    <hint type="sourcelabel">
     <x>604</x>
     <y>41</y>
    </hint>
    <hint type="destinationlabel">
     <x>951</x>
     <y>20</y>
    </hint>
   </hints>
  </connection>
 </connections>
 <slots>
  <slot>onEditClicked()</slot>
 </slots>
</ui>

CMakeLists.txt:

cmake_minimum_required(VERSION 3.5)

project(model_view_example VERSION 0.1 LANGUAGES CXX)

set(CMAKE_INCLUDE_CURRENT_DIR ON)

set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)

set(PROJECT_SOURCES
        main.cpp
        mainwindow.cpp
        mymodel.cpp
        mainwindow.h
        mainwindow.ui
)

if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
    qt_add_executable(model_view_example
        MANUAL_FINALIZATION
        ${PROJECT_SOURCES}
    )
# Define target properties for Android with Qt 6 as:
#    set_property(TARGET model_view_example APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
#                 ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
    if(ANDROID)
        add_library(model_view_example SHARED
            ${PROJECT_SOURCES}
        )
# Define properties for Android with Qt 5 after find_package() calls as:
#    set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
    else()
        add_executable(model_view_example
            ${PROJECT_SOURCES}
        )
    endif()
endif()

target_link_libraries(model_view_example PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)

set_target_properties(model_view_example PROPERTIES
    MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
    MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
    MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
    MACOSX_BUNDLE TRUE
    WIN32_EXECUTABLE TRUE
)

if(QT_VERSION_MAJOR EQUAL 6)
    qt_finalize_executable(model_view_example)
endif()

So, why the view is not updated when I call update()? Is there something wrong about my implementation?

Here, I clicked on edit button, but the view only got updated with its new value when I clicked on a cell:

enter image description here

I feel like Qt MVC examples do not really map well to how I do it here. I do not know how I can implement this logic in a more Qt-ish way if possible.

Here is the github repo for the minimal project:

https://github.com/ozcanay/qt_model_view_example


Solution

  • I got help from Qt forum and solved it.