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. Making QAbstractListModel items individually accessible
Forum Updated to NodeBB v4.3 + New Features

Making QAbstractListModel items individually accessible

Scheduled Pinned Locked Moved Solved General and Desktop
qabstractlistmoqobjectqml
2 Posts 1 Posters 2.6k 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.
  • romsharkovR Offline
    romsharkovR Offline
    romsharkov
    wrote on last edited by
    #1

    Hi again,

    Writing a simple message list viewer, where the message has a title, a message text, and a unique identifier I faced a problem trying to make the individual messages stored in the QAbstractListModel derived MessageList accessible by their identifier from QML.

    I tried to implement the QML invokable index operator on the list model which would perform a lookup on a map that's associating identifier strings to QList::iterator of the certain object, but that requires the Message class to derive from QObject, which by design is non-copyable and what happens if the list model changes, but the map is untouched and the iterators are corrupted? I have that feeling of doing it totally wrong again...

    Could someone help me design a working structure to elegantly solve this kind of problem? The complete source code is attached below

    main.qml:

    import QtQuick 2.7
    import QtQuick.Controls 2.0
    import QtQuick.Layouts 1.0
    
    ApplicationWindow {
    	visible: true
    	width: 640
    	height: 480
    	title: qsTr("Messages")
    
    	Rectangle {
    		id: menu
    		color: Qt.rgba(0.9, 0.9, 0.9, 1)
    		height: 32
    		anchors.top: parent.top
    		anchors.left: parent.left
    		anchors.right: parent.right
    		Text {
    			anchors.left: parent.left
    			anchors.margins: 8
    			anchors.verticalCenter: parent.verticalCenter
    			//THIS DOESNT YET WORK
    			//text: "at id 'a' there is: " + MessageList["a"].title
    		}
    	}
    
    	ListView {
    		model: MessageList
    		anchors.top: menu.bottom
    		anchors.left: parent.left
    		anchors.right: parent.right
    		anchors.bottom: parent.bottom
    		clip: true
    		delegate: Rectangle {
    			height: 32
    			Column {
    				anchors.fill: parent
    				anchors.margins: 8
    				spacing: 2
    				Text {
    					text: title + " (" + identifier + ")"
    					font.bold: true
    				}
    				Text {
    					text: message
    				}
    			}
    		}
    	}
    }
    

    MessageList.cpp:

    #ifndef MESSAGELIST_HPP
    #define MESSAGELIST_HPP
    
    #include "Message.hpp"
    #include <QObject>
    #include <QAbstractListModel>
    #include <QModelIndex>
    #include <QList>
    #include <QHash>
    #include <QVariant>
    #include <QMap>
    
    class MessageList : public QAbstractListModel {
    	Q_OBJECT
    public:
    	enum Roles {
    		IdentifierRole,
    		TitleRole,
    		MessageRole,
    	};
    	typedef QMap<QString, QList<Message>::iterator> IdMap;
    
    protected:
    	QList<Message> _list;
    	IdMap _byId;
    public:
    	MessageList();
    	int rowCount(const QModelIndex &parent) const;
    	QHash<int, QByteArray> roleNames() const;
    	QVariant data(const QModelIndex &index, int role) const;
    	bool insert(
    		const QList<Message>& messages,
    		int position = 0
    	);
    	Q_INVOKABLE QVariant operator[](const QString& identifier) const;
    	bool reset();
    };
    #endif // MESSAGELIST_HPP
    

    MessageList.cpp:

    #include "MessageList.hpp"
    #include <QObject>
    #include <QModelIndex>
    #include <QVariant>
    #include <QHash>
    #include <QByteArray>
    
    MessageList::MessageList() :
    	QAbstractListModel(nullptr)
    {
    
    }
    
    int MessageList::rowCount(const QModelIndex &parent) const {
    	Q_UNUSED(parent)
    	return _list.size();
    }
    
    QHash<int, QByteArray> MessageList::roleNames() const {
    	QHash<int, QByteArray> roles;
    	roles[IdentifierRole] = "identifier";
    	roles[TitleRole] = "title";
    	roles[MessageRole] = "message";
    	return roles;
    }
    
    QVariant MessageList::data(const QModelIndex &index, int role) const {
    	if(!index.isValid()
    		|| index.row() >= _list.size()
    		|| index.row() < 0
    	) {
    		return QVariant();
    	}
    	switch(role) {
    	case IdentifierRole:
    		return _list.at(index.row()).identifier();
    		break;
    	case TitleRole:
    		return _list.at(index.row()).title();
    		break;
    	case MessageRole:
    		return _list.at(index.row()).message();
    		break;
    	default:
    		return QVariant();
    	}
    }
    
    bool MessageList::insert(
    	const QList<Message>& messages,
    	int position
    ) {
    	beginInsertRows(QModelIndex(), position, position + messages.size() - 1);
    	for(int row = 0; row < messages.size(); ++row) {
    		_list.insert(position, messages.at(row));
    		QList<Message>::iterator listItr(_list.begin());
    		listItr += position;
    		_byId.insert(messages.at(row).identifier(), listItr);
    	}
    	endInsertRows();
    	return true;
    }
    
    bool MessageList::reset() {
    	beginResetModel();
    	_list.clear();
    	endResetModel();
    	return true;
    }
    
    QVariant MessageList::operator[](const QString& identifier) const {
    	IdMap::const_iterator itr(_byId.find(identifier));
    	if(itr != _byId.constEnd()) {
    		return QVariant(QVariant::fromValue(*(*itr)));
    	}
    	return QVariant();
    }
    

    Message:

    #ifndef MESSAGE_HPP
    #define MESSAGE_HPP
    
    #include <QObject>
    #include <QString>
    
    class Message : public QObject {
    	Q_OBJECT
    	Q_PROPERTY(QString identifier READ identifier NOTIFY identifierChanged)
    	Q_PROPERTY(QString title READ title NOTIFY titleChanged)
    	Q_PROPERTY(QString message READ message NOTIFY messageChanged)
    
    protected:
    	QString _identifier;
    	QString _title;
    	QString _message;
    
    public:
    	Message();
    	Message(
    		const QString& identifier,
    		const QString& title,
    		const QString& message
    	);
    	const QString& identifier() const;
    	const QString& title() const;
    	const QString& message() const;
    
    signals:
    	void identifierChanged();
    	void titleChanged();
    	void messageChanged();
    };
    
    Q_DECLARE_METATYPE(Message)
    
    #endif // MESSAGE_HPP
    

    Message.cpp:

    #include "Message.hpp"
    #include <QObject>
    #include <QString>
    
    Message::Message() :
    	QObject(nullptr)
    {
    
    }
    
    Message::Message(
    	const QString& identifier,
    	const QString& title,
    	const QString& message
    ) :
    	QObject(nullptr),
    	_identifier(identifier),
    	_title(title),
    	_message(message)
    {
    
    }
    
    const QString& Message::identifier() const {
    	return _identifier;
    }
    
    const QString& Message::title() const {
    	return _title;
    }
    
    const QString& Message::message() const {
    	return _message;
    }
    
    1 Reply Last reply
    0
    • romsharkovR Offline
      romsharkovR Offline
      romsharkov
      wrote on last edited by romsharkov
      #2

      After hours of google, trial and error I'm proud to finally have solved it myself as I am still pretty new to Qt!

      To help people with a similar problem I'll quickly sum up all I had to do to make it work:

      1. turn the internal QList<Message> into a QList<QSharedPointer<Message>>

      2. define a QMap<QString, QSharedPointer<Message>> to hold index ids to the individual messages

      3. when inserting, insert in the indexMap as well

      4. define a method in the QAbstractListModel derived MessageList which simply returns a QVariantMap

      P.S. Below is the new working version of the example app, in case anyone notices any misconceptions, please let me know

      MessageList.hpp:

      #ifndef MESSAGELIST_HPP
      #define MESSAGELIST_HPP
      
      #include "Message.hpp"
      #include <QObject>
      #include <QAbstractListModel>
      #include <QModelIndex>
      #include <QList>
      #include <QHash>
      #include <QVariant>
      #include <QMap>
      #include <QSharedPointer>
      
      class MessageList : public QAbstractListModel {
      	Q_OBJECT
      public:
      	typedef QSharedPointer<Message> MessagePointer;
      	typedef QList<MessagePointer> MessagePointerList;
      	typedef QMap<QString, MessagePointer> IndexMap;
      	enum Roles {
      		IdentifierRole,
      		TitleRole,
      		MessageRole,
      	};
      
      protected:
      	MessagePointerList _list;
      	IndexMap _indexMap;
      
      public:
      	MessageList();
      	int rowCount(const QModelIndex& parent) const;
      	QHash<int, QByteArray> roleNames() const;
      	QVariant data(const QModelIndex& index, int role) const;
      	bool insert(
      		const QList<Message>& messages,
      		int position = 0
      	);
      	bool reset();
      	Q_INVOKABLE QVariantMap get(const QString& identifier) const;
      	const Message& at(int index) const;
      };
      
      #endif // MESSAGELIST_HPP
      

      MessageList.cpp:

      #include "MessageList.hpp"
      #include <QObject>
      #include <QModelIndex>
      #include <QVariant>
      #include <QHash>
      #include <QByteArray>
      
      MessageList::MessageList() :
      	QAbstractListModel(nullptr)
      {
      
      }
      
      int MessageList::rowCount(const QModelIndex& parent) const {
      	Q_UNUSED(parent)
      	return _list.size();
      }
      
      QHash<int, QByteArray> MessageList::roleNames() const {
      	QHash<int, QByteArray> roles;
      	roles[IdentifierRole] = "identifier";
      	roles[TitleRole] = "title";
      	roles[MessageRole] = "message";
      	return roles;
      }
      
      QVariant MessageList::data(const QModelIndex& index, int role) const {
      	if(!index.isValid()
      		|| index.row() >= _list.size()
      		|| index.row() < 0
      	) {
      		return QVariant();
      	}
      	switch(role) {
      	case IdentifierRole:
      		return _list.at(index.row())->identifier();
      		break;
      	case TitleRole:
      		return _list.at(index.row())->title();
      		break;
      	case MessageRole:
      		return _list.at(index.row())->message();
      		break;
      	default:
      		return QVariant();
      	}
      }
      
      bool MessageList::insert(
      	const QList<Message>& messages,
      	int position
      ) {
      	beginInsertRows(QModelIndex(), position, position + messages.size() - 1);
      	for(int row = 0; row < messages.size(); ++row) {
      		IndexMap::const_iterator indexMapItr(_indexMap.constFind(messages.at(row).identifier()));
      		if(indexMapItr == _indexMap.constEnd()) {
      			MessagePointer newMessage(new Message(messages.at(row)));
      			_list.insert(position, newMessage);
      			_indexMap.insert(messages.at(row).identifier(), newMessage);
      		}
      	}
      	endInsertRows();
      	return true;
      }
      
      bool MessageList::reset() {
      	beginResetModel();
      	_list.clear();
      	__indexMap.clear();
      	endResetModel();
      	return true;
      }
      
      QVariantMap MessageList::get(const QString& identifier) const {
      	QVariantMap result;
      	IndexMap::const_iterator indexMapItr(_indexMap.constFind(identifier));
      	if(indexMapItr != _indexMap.constEnd()) {
      		result["identifier"] = QVariant(indexMapItr->data()->identifier());
      		result["title"] = QVariant(indexMapItr->data()->title());
      		result["message"] = QVariant(indexMapItr->data()->message());
      	}
      	return result;
      }
      
      const Message& MessageList::at(int index) const {
      	return *(_list.at(index).data());
      }
      

      main.cpp:

      #include <QGuiApplication>
      #include <QQmlApplicationEngine>
      #include <QQmlContext>
      #include "Message.hpp"
      #include "MessageList.hpp"
      
      int main(int argc, char *argv[])
      {
      	QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);
      	QGuiApplication app(argc, argv);
      
      	MessageList messageList;
      	messageList.insert(QList<Message> {
      		Message("a", "first message", "this is a sample text message of the first message"),
      		Message("b", "second message", "another sample text message of the second message"),
      		Message("c", "third message", "yet a third text message sample"),
      		Message("d", "fouth message", "last sample message")
      	});
      	QQmlApplicationEngine engine;
      	engine.rootContext()->setContextProperty("MessageList", &messageList);
      	engine.load(QUrl(QLatin1String("qrc:/main.qml")));
      
      	return app.exec();
      }
      

      main.qml:

      import QtQuick 2.7
      import QtQuick.Controls 2.0
      import QtQuick.Layouts 1.0
      
      ApplicationWindow {
      	visible: true
      	width: 640
      	height: 480
      	title: qsTr("Messages")
      
      	Rectangle {
      		id: menu
      		color: Qt.rgba(0.9, 0.9, 0.9, 1)
      		height: 32
      		anchors.top: parent.top
      		anchors.left: parent.left
      		anchors.right: parent.right
      		Text {
      			anchors.left: parent.left
      			anchors.margins: 8
      			anchors.verticalCenter: parent.verticalCenter
      			text: "at id 'b' there is: " + MessageList.get("b").title
      		}
      	}
      
      	ListView {
      		model: MessageList
      		anchors.top: menu.bottom
      		anchors.left: parent.left
      		anchors.right: parent.right
      		anchors.bottom: parent.bottom
      		clip: true
      		delegate: Rectangle {
      			height: 32
      			Column {
      				anchors.fill: parent
      				anchors.margins: 8
      				spacing: 2
      				Text {
      					text: title + " (" + identifier + ")"
      					font.bold: true
      				}
      				Text {
      					text: message
      				}
      			}
      		}
      	}
      }
      
      1 Reply Last reply
      0

      • Login

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