C++ - Qt Framework - Using QAbstractItemModel with QTableView and QListView

The QAbstractItemModel is usually used with views like QTableView, QTreeView or QListView.

We’re going to see how to use this class in a very easy example.

To be honest nothing is easy but we’ll use the minimum elements in order to have something useful for every new project.

Let’s see that in this QAbstractItemModel tutorial.

First of all

The Model/View design is quite different from the classic Widgets.

Indeed, here the view and the model are separated in at least two different classes.

The main advantage with the Model/View is that you don’t have to refresh yourself data in all your view once the model is modified.

Everything is updating itself, or almost.

In this example we’ll use a QTableView and a QListView sharing the same model.

And, what a wondferful world, the two views will be modified when we’ll change a data in one view.

It’ll prove that the Model/View design works.

The QAbstractItemModel isn’t the simplest model to understand but with it you’ll be able to use all the views.

You can find indeed some examples with QAbstractTableModel for using with QTableView or QAbstractTreeModel to use with QTreeView.

Both of these models inherits from the QAbstractItemModel.

In this tutorial we'll use instead directly the QAbstractItemModel.

Our Model will be taken from a file (name and age of people) and we’ll save this model further in the same file but potentially with different data.

That’s way we’ll be sure that our construction works well.

As QAbstractItemModel is an Abstract class, we’ll have to override some methods from this parent class.

All the details are in the code.

For the GUI you'll have to add a TableView, a ListView and a PushButton on the MainWindow.

So let’s code a bit.

Code

BadprogData.cpp

#include "BadprogData.h"

// Badprog.com

// ============================================================================
//
// ============================================================================
BadprogData::BadprogData() {
}

// ============================================================================
//
// ============================================================================
BadprogData::~BadprogData() {
}

// ============================================================================
//
// ============================================================================
void BadprogData::setName(QString name) {
_name = name;
}

// ============================================================================
//
// ============================================================================
void BadprogData::setAge(int age) {
_age = age;
}

// ============================================================================
//
// ============================================================================
QString BadprogData::getName() const {
return _name;
}

// ============================================================================
//
// ============================================================================
int BadprogData::getAge() const {
return _age;
}

 

BadprogData.h

#ifndef BADPROG_DATA_H
#define BADPROG_DATA_H

#include <QString>

// Badprog.com

class BadprogData
{
public:
BadprogData();
~BadprogData();
// ============================================================================
// methods
// ============================================================================
public:
void setName(QString name);
void setAge(int age);
QString getName() const;
int getAge() const;

// ============================================================================
// variables
// ============================================================================
private:
QString _name;
int _age;
};

#endif // !BADPROG_DATA_H

 

BadprogItemModel.cpp

#include "BadprogItemModel.h"

#include <QColor>
#include <QFont>

// Badprog.com

// ============================================================================
//
// ============================================================================
BadprogItemModel::BadprogItemModel() {
}

// ============================================================================
//
// ============================================================================
BadprogItemModel::BadprogItemModel(int numberOfRows, int numberOfColumns) :
_numberOfRows(numberOfRows), _numberOfColumns(numberOfColumns) {
}

// ============================================================================
//
// ============================================================================
BadprogItemModel::~BadprogItemModel() {
}

// ============================================================================
//
// ============================================================================
QVariant BadprogItemModel::data(const QModelIndex &index, int role) const {
//
QVariant result = QVariant();
int row = index.row();
int column = index.column();

//
if (!index.isValid() || row >= rowCount() || column >= columnCount()) {
return result;
}

switch (role) {
//
case Qt::DisplayRole:

// checking row and column to get data
switch (column) {
case 0:
result = _data.at(row).getName();
break;
case 1:
result = _data.at(row).getAge();
break;
default:
break;
} // end switch column
break;
//
case Qt::FontRole:
if (2 == row) {
QFont font;
font.setBold(true);
result = font;
}
break;
//
case Qt::ForegroundRole:
if (1 == column) {
result = QColor(Qt::red);
}
break;
//
case Qt::BackgroundRole:
if (1 == column) {
result = QColor(255, 255, 40);
}
break;
//
case Qt::TextAlignmentRole:
result = Qt::AlignCenter;
break;
//
default:
break;
}

//
return result;
}

// ============================================================================
//
// ============================================================================
int BadprogItemModel::rowCount(const QModelIndex &parent) const {
(void)(parent);
return _numberOfRows;
}

// ============================================================================
//
// ============================================================================
int BadprogItemModel::columnCount(const QModelIndex &parent) const {
(void)(parent);
return _numberOfColumns;
}

// ============================================================================
//
// ============================================================================
QVariant BadprogItemModel::headerData(int section, Qt::Orientation orientation, int role) const {
//
QVariant result = QVariant();

//
if (Qt::DisplayRole == role && Qt::Horizontal == orientation) { // H
//
switch (section) {
//
case 0:
result = "Hello";
break;
//
case 1:
result = "World";
break;
//
case 2:
result = ":D";
break;
default:
break;
}
} else if (Qt::DisplayRole == role && Qt::Vertical == orientation) { // V
//
switch (section) {
//
case 0:
result = "Welcome";
break;
//
case 1:
result = "Aboard";
break;
//
case 2:
result = "Guys";
break;
default:
break;
}
} else {
// other stuff
}

//
return result;
}

// ============================================================================
//
// ============================================================================
bool BadprogItemModel::setData(const QModelIndex &index, const QVariant &value, int role) {
//
bool result = false;

if (index.isValid() && role == Qt::EditRole) {
index.row(), index.column();

int row = index.row();
int column = index.column();

switch (column) {
case 0:
_data.at(row).setName(value.toString());
break;
case 1:
_data.at(row).setAge(value.toInt());
break;
default:
break;
} // end switch column

emit dataChanged(index, index);
result = true;
}

//
return result;
}

// ============================================================================
//
// ============================================================================
Qt::ItemFlags BadprogItemModel::flags(const QModelIndex &index) const {
//
Qt::ItemFlags result = Qt::ItemIsEditable | QAbstractItemModel::flags(index);

//
return result;
}

// ============================================================================
//
// ============================================================================
QModelIndex BadprogItemModel::index(int row, int column, const QModelIndex &parent) const {
// return QModelIndex();
return createIndex(row, column, nullptr);
}

// ============================================================================
//
// ============================================================================
QModelIndex BadprogItemModel::parent(const QModelIndex &child) const {
return QModelIndex();
}

// ============================================================================
//
// ============================================================================
void BadprogItemModel::addNewElementToData(BadprogData data) {
_data.push_back(data);
}

// ============================================================================
//
// ============================================================================
std::vector<BadprogData> BadprogItemModel::getElementsFromData() const {
return _data;
}

 

BadprogItemModel.h

#ifndef BADPROG_ITEM_MODEL_H
#define BADPROG_ITEM_MODEL_H

#include "BadprogData.h"

#include <QAbstractItemModel>

// Badprog.com

class BadprogItemModel : public QAbstractItemModel
{

// ============================================================================
// ctor
// ============================================================================
public:
BadprogItemModel();
BadprogItemModel(int numberOfRows, int numberOfColumns);
~BadprogItemModel();

// ============================================================================
// methods
// ============================================================================
public:
// override from virtual pure
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex &parent = QModelIndex()) const override;
int columnCount(const QModelIndex &parent = QModelIndex()) const override;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
QModelIndex parent(const QModelIndex &child) const;

// override from virtual
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole) override;
Qt::ItemFlags flags(const QModelIndex &index) const override;

// classic
void addNewElementToData(BadprogData data);
std::vector<BadprogData> getElementsFromData() const;

// ============================================================================
// variables
// ============================================================================
private:
std::vector<BadprogData> _data;
int _numberOfRows;
int _numberOfColumns;

};

#endif // !BADPROG_ITEM_MODEL_H

 

BadprogMainWindow.cpp

#include "BadprogMainWindow.h"
#include "BadprogItemModel.h"

#include <QDebug>
#include <QMessageBox>
#include <QFile>

// Badprog.com

// ============================================================================
//
// ============================================================================
BadprogMainWindow::BadprogMainWindow(QWidget *parent) : QMainWindow(parent) {
// inits
ui.setupUi(this);
_model = nullptr;
unsigned int numberOfRows = 0;
QTextStream stream;
QFile file = nullptr;
bool result = false;

// file
_fileName = "people.txt";
file.setFileName(_fileName);

// deals with the file
result = managerFile(file, QIODevice::ReadOnly);
stream.setDevice(&file);

// gets number of lines
while (!stream.atEnd()) {
stream.readLine();
++numberOfRows;
}
stream.seek(0); // sets the cursor to begin
_model = new BadprogItemModel(numberOfRows, 2);
 
// reading the file
while (!stream.atEnd()) {
QString line = stream.readLine();
QStringList fields = line.split(" ");
BadprogData data;

data.setName(fields.at(0));
data.setAge(fields.at(1).toInt());
_model->addNewElementToData(data);
}

// sets the model
ui.tableView->setModel(_model);
ui.listView->setModel(_model);

// if file exists we allow saving mode
if (result) {
// slots
QObject::connect( ui.pushButton, &QPushButton::clicked,
this, &BadprogMainWindow::slotSaveData);
}
}

// ============================================================================
//
// ============================================================================
bool BadprogMainWindow::managerFile(QFile &file, QIODevice::OpenModeFlag flag) {
//
bool result = true;

//
if (!file.open(flag)) {
QMessageBox::information( // parent, title, message
this,
"Problem with the file",
"File: \"" + file.fileName() + "\".\n" + file.errorString());
result = false;
}

//
return result;
}

// ============================================================================
//
// ============================================================================
void BadprogMainWindow::slotSaveData() {
// inits
QFile file;
QTextStream out;

//
file.setFileName(_fileName);
managerFile(file, QIODevice::WriteOnly);

// writing to the file
out.setDevice(&file);
for (auto &element : _model->getElementsFromData()) {
out << element.getName() << " " << element.getAge();
out << "\n";
}
}

 

BadprogMainWindow.h

#ifndef BADPROG_MAIN_WINDOW_H
#define BADPROG_MAIN_WINDOW_H

#include <QtWidgets/QMainWindow>
#include "ui_BadprogMainWindow.h"
#include "BadprogItemModel.h"
#include <QPointer>

// Badprog.com

class QFile;

class BadprogMainWindow : public QMainWindow
{
Q_OBJECT

// ============================================================================
// ctor
// ============================================================================
public:
BadprogMainWindow(QWidget *parent = Q_NULLPTR);

// ============================================================================
// methods
// ============================================================================
public:
bool managerFile(QFile &file, QIODevice::OpenModeFlag flag);

// ============================================================================
// slots
// ============================================================================
public slots:
void slotSaveData();

// ============================================================================
//
// ============================================================================
private:
Ui::BadprogMainWindowClass ui;
QPointer<BadprogItemModel> _model;
QString _fileName;
};

#endif // !BADPROG_MAIN_WINDOW_H

 

BadprogMainWindow.ui

<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>BadprogMainWindowClass</class>
<widget class="QMainWindow" name="BadprogMainWindowClass">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1106</width>
<height>400</height>
</rect>
</property>
<property name="windowTitle">
<string>BadprogMainWindow</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QTableView" name="tableView"/>
</item>
<item row="0" column="1">
<widget class="QListView" name="listView"/>
</item>
<item row="1" column="1">
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Save data</string>
</property>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1106</width>
<height>21</height>
</rect>
</property>
</widget>
<widget class="QToolBar" name="mainToolBar">
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
</widget>
<widget class="QStatusBar" name="statusBar"/>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources>
<include location="BadprogMainWindow.qrc"/>
</resources>
<connections/>
</ui>

 

main.cpp

#include "BadprogMainWindow.h"
#include <QtWidgets/QApplication>

// Badprog.com

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

 

people.txt

Katy 10
John 47
Elon 1337

 

Result

As a result you have a window with 2 views: A TableView and a ListView with a PushButton.

On the TableView there are the name of each person taken from the file as well as their age.

The second column of the TableView is on yellow and the last row is in bold, just to show how it works.

On the right you have the ListView with the name from the file.

So as we set the both views with the same model each time we change a name from a view, instantly the other view updates its content with the new model.

At the end you can save the new model on the same file by clicking on the PushButton.

Conclusion

This QAbstractItemModel example seemed quite complex at first but by extracting the basics we have seen that it became something extremely powerful.

Once again, good job if you got it. cool

Add new comment

Plain text

  • No HTML tags allowed.
  • Lines and paragraphs break automatically.