Qt Qml Abstract Item Model using C++
The QAbstractListModel
class provides an abstract model that can be subclassed to create one-dimensional list models .
Detailed Description
QAbstractListModel provides a standard interface for models that represent their data as a simple non-hierarchical sequence of items. It is not used directly, but must be subclassed.
Since the model provides a more specialized interface than QAbstractItemModel, it is not suitable for use with tree views; you will need to subclass QAbstractItemModel if you want to provide a model for that purpose. If you need to use a number of list models to manage data, it may be more appropriate to subclass QAbstractTableModel instead.
Simple models can be created by subclassing this class and implementing the minimum number of required functions. For example, we could implement a simple read-only QStringList-based model that provides a list of strings to a QListView widget. In such a case, we only need to implement the rowCount() function to return the number of items in the list, and the data() function to retrieve items from the list.
Since the model represents a one-dimensional structure, the rowCount() function returns the total number of items in the model. The columnCount() function is implemented for interoperability with all kinds of views, but by default informs views that the model contains only one column.
Subclassing
When subclassing QAbstractListModel, you must provide implementations of the rowCount() and data() functions. Well behaved models also provide a headerData() implementation.
If your model is used within QML and requires roles other than the default ones provided by the roleNames() function, you must override it.
For editable list models, you must also provide an implementation of setData(), and implement the flags() function so that it returns a value containing Qt::ItemIsEditable.
Note that QAbstractListModel provides a default implementation of columnCount() that informs views that there is only a single column of items in this model.
Models that provide interfaces to resizable list-like data structures can provide implementations of insertRows() and removeRows(). When implementing these functions, it is important to call the appropriate functions so that all connected views are aware of any changes:
- An insertRows() implementation must call beginInsertRows() before inserting new rows into the data structure, and it must call endInsertRows() immediately afterwards.
- A removeRows() implementation must call beginRemoveRows() before the rows are removed from the data structure, and it must call endRemoveRows() immediately afterwards.
So Let’s Create a class which is abstract from QAbstractListModel.
PersonModel::PersonModel(QObject *parent) : QAbstractListModel(parent)
Let’s Look at the code :
personmodel.cpp
#include <QDebug>
#include "personmodel.h"
#include "person.h"
PersonModel::PersonModel(QObject *parent) : QAbstractListModel(parent)
{
addPerson(new Person("Aksh Singh","red",33));
addPerson(new Person("Laksh Kumar","cyan",26));
addPerson(new Person("Luccky The Raccer","yellow",44));
}
int PersonModel::rowCount(const QModelIndex &parent) const
{
Q_UNUSED(parent)
return mPersons.size();
}
QVariant PersonModel::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() >= mPersons.count())
return QVariant();
//The index is valid
Person * person = mPersons[index.row()];
if( role == NamesRole)
return person->names();
if( role == FavoriteColorRole)
return person->favoriteColor();
if( role == AgeRole)
return person->age();
return QVariant();
}
bool PersonModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
Person * person = mPersons[index.row()];
bool somethingChanged = false;
switch (role) {
case NamesRole:
{
if( person->names()!= value.toString()){
qDebug() << "Changing names for " << person->names();
person->setNames(value.toString());
somethingChanged = true;
}
}
break;
case FavoriteColorRole:
{
if( person->favoriteColor()!= value.toString()){
qDebug() << "Changing color for " << person->names();
person->setFavoriteColor(value.toString());
somethingChanged = true;
}
}
break;
case AgeRole:
{
if( person->age()!= value.toInt()){
qDebug() << "Changing age for " << person->names();
person->setAge(value.toInt());
somethingChanged = true;
}
}
}
if( somethingChanged){
emit dataChanged(index,index,QVector<int>() << role);
return true;
}
return false;
}
Qt::ItemFlags PersonModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return Qt::NoItemFlags;
return Qt::ItemIsEditable;
}
QHash<int, QByteArray> PersonModel::roleNames() const
{
QHash<int, QByteArray> roles;
roles[NamesRole] = "names";
roles[FavoriteColorRole] = "favoriteColor";
roles[AgeRole] = "age";
return roles;
}
void PersonModel::addPerson(Person *person)
{
const int index = mPersons.size();
beginInsertRows(QModelIndex(),index,index);
mPersons.append(person);
endInsertRows();
}
void PersonModel::addPerson()
{
Person *person = new Person("Added Person","yellowgreen",45,this);
addPerson(person);
}
void PersonModel::addPerson(const QString &names, const int &age)
{
Person *person=new Person(names,"yellowgreen",age);
addPerson(person);
}
void PersonModel::removePerson(int index)
{
beginRemoveRows(QModelIndex(), index, index);
mPersons.removeAt(index);
endRemoveRows();
}
void PersonModel::removeLastPerson()
{
removePerson(mPersons.size()-1);
}
personmodel.h
#ifndef PERSONMODEL_H
#define PERSONMODEL_H
#include <QAbstractListModel>
#include <QObject>
#include "person.h"
class PersonModel : public QAbstractListModel
{
Q_OBJECT
enum PersonRoles{
NamesRole = Qt::UserRole + 1,
FavoriteColorRole,
AgeRole
};
public:
explicit PersonModel(QObject *parent = nullptr);
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role) const;
bool setData(const QModelIndex &index, const QVariant &value, int role);
Qt::ItemFlags flags(const QModelIndex& index) const;
QHash<int, QByteArray> roleNames() const;
void addPerson( Person *person);
Q_INVOKABLE void addPerson();
Q_INVOKABLE void addPerson(const QString & names,const int & age);
Q_INVOKABLE void removePerson(int index);
Q_INVOKABLE void removeLastPerson();
signals:
public slots:
private :
QList<Person*> mPersons;
};
#endif // PERSONMODEL_H
person.h
#ifndef PERSON_H
#define PERSON_H
#include <QObject>
class Person : public QObject
{
Q_OBJECT
public:
explicit Person(QString names,QString favoriteColor,int age,QObject *parent = nullptr);
Q_PROPERTY(int age READ age WRITE setAge NOTIFY ageChanged)
Q_PROPERTY(QString favoriteColor READ favoriteColor WRITE setFavoriteColor NOTIFY favoriteColorChanged)
Q_PROPERTY(QString names READ names WRITE setNames NOTIFY namesChanged)
int age() const;
void setAge(int newAge);
const QString &favoriteColor() const;
void setFavoriteColor(const QString &newFavoriteColor);
const QString &names() const;
void setNames(const QString &newNames);
public slots:
signals:
void ageChanged();
void favoriteColorChanged();
void namesChanged();
private:
int m_age;
QString m_favoriteColor;
QString m_names;
};
#endif // PERSON_H
person.cpp
#include "person.h"
Person::Person(QString names,QString favoriteColor,int age,QObject *parent)
: QObject{parent}
,m_age{age}
,m_favoriteColor{favoriteColor}
,m_names{names}
{
}
int Person::age() const
{
return m_age;
}
void Person::setAge(int newAge)
{
if (m_age == newAge)
return;
m_age = newAge;
emit ageChanged();
}
const QString &Person::favoriteColor() const
{
return m_favoriteColor;
}
void Person::setFavoriteColor(const QString &newFavoriteColor)
{
if (m_favoriteColor == newFavoriteColor)
return;
m_favoriteColor = newFavoriteColor;
emit favoriteColorChanged();
}
const QString &Person::names() const
{
return m_names;
}
void Person::setNames(const QString &newNames)
{
if (m_names == newNames)
return;
m_names = newNames;
emit namesChanged();
}
Let’s register model into main.cpp
#include <QGuiApplication>
#include <QQmlContext>
#include <QQmlApplicationEngine>
#include "personmodel.h"
int main(int argc, char *argv[])
{
QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
QGuiApplication app(argc, argv);
PersonModel mModel;
QQmlApplicationEngine engine;
engine.rootContext()->setContextProperty("myModel",&mModel);
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
Create Input Dialog called InputDialog.qml
import QtQuick 2.12
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.12
import QtQuick.Controls.Imagine 2.3
Item {
id : rootId
function openDialog(){
inputDialog.open()
}
property alias personNames : addPerNamesField.text
property alias personAge: addPerAgeField.value
signal inputDialogAccepted()
Dialog {
id: inputDialog
x: (parent.width - width) / 2
y: (parent.height - height) / 2
width: parent.width/2 //Breaks the binding loop introduced in Qt 5.12.
parent: Overlay.overlay
focus: true
modal: true
title: "Person data"
standardButtons: Dialog.Ok | Dialog.Cancel
ColumnLayout {
spacing: 20
anchors.fill: parent
Label {
elide: Label.ElideRight
text: "Please enter the data:"
Layout.fillWidth: true
}
TextField {
id : addPerNamesField
focus: true
placeholderText: "Names"
Layout.fillWidth: true
}
SpinBox {
editable: true
id : addPerAgeField
value : 13
Layout.fillWidth: true
}
}
onAccepted: {
console.log("Accepted adding person")
rootId.inputDialogAccepted()
}
onRejected: {
console.log("Rejected adding person")
}
}
}
Final Code main.qml
import QtQuick 2.12
import QtQuick.Window 2.12
import QtQuick.Controls 2.5
import QtQuick.Layouts 1.12
import QtQuick.Dialogs
import QtQuick.Controls.Imagine 2.3
/* Here you need to follow the instruction
For Qt5.15.x --> import QtQuick.Dialogs 1.3
For Qt6.x.x --> import QtQuick.Dialogs
Note : I am using Qt6.3.x so i imported --> import QtQuick.Dialogs
*/
ApplicationWindow {
visible: true
width: 400
height: 600
minimumWidth: 400
minimumHeight: 600
title: qsTr("blogs.thearticleof.com")
Label{
text: qsTr("blogs.thearticleof.com")
font.pixelSize: 34
color: "grey"
opacity: 0.5
rotation: 360-75
anchors.centerIn: parent
anchors.topMargin: 200
}
ColumnLayout{
anchors.fill: parent
anchors.topMargin: 10
ListView{
id : mListView
Layout.fillWidth: true
Layout.fillHeight: true
model : myModel
delegate:RowLayout{
width: parent.width
spacing: 10
Layout.margins: 10
TextField{
text : names
Layout.fillWidth: true
Layout.leftMargin: 10
onEditingFinished: {
console.log("Editing finished, new text is :"+ text + " at index :" + index)
model.names = text //The roles here are defined in model class
}
}
SpinBox{
id : mSpinbox
editable: true
Layout.fillWidth: true
onValueChanged: {
model.age = value;
}
Component.onCompleted: {
mSpinbox.value = model.age
}
}
Rectangle{
width : 30
height: 30
radius: width/2
color: model.favoriteColor
Layout.rightMargin: 10
MouseArea{
anchors.fill: parent
ColorDialog{
id: colorDialog
title: "Please choose a color"
onAccepted: {
console.log("You choose: " + colorDialog.color)
model.favoriteColor = colorDialog.color
}
onRejected: {
console.log("Canceled")
}
}
onClicked: {
colorDialog.open()
}
}
}
}
}
RowLayout{
width : parent.width
Button{
Layout.fillWidth: true
height: 50
text : "Add Person";
highlighted: true
onClicked: {
input.openDialog()
}
InputDialog{
id : input
onInputDialogAccepted: {
console.log("Here in main, dialog accepted");
console.log( " names : " + personNames + " age :" + personAge)
myModel.addPerson(personNames,personAge)
}
}
}
Button{
Layout.fillWidth: true
height: 50
highlighted: true
text : "Remove Last";
onClicked: {
myModel.removeLastPerson()
}
}
}
}
}
Let’s try build and run the project
Let’s try to add one person :
After Adding Person Look Model Updated :
Note: You can find the whole source code on my github.