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
QtWS25 Last Chance

Making QAbstractListModel items individually accessible

Scheduled Pinned Locked Moved Solved General and Desktop
qabstractlistmoqobjectqml
2 Posts 1 Posters 2.5k 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.
  • R Offline
    R Offline
    romsharkov
    wrote on 30 Jun 2016, 13:12 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
    • R Offline
      R Offline
      romsharkov
      wrote on 30 Jun 2016, 18:37 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

      2/2

      30 Jun 2016, 18:37

      • Login

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