Qt5.1: [Solved] Problems with Qlist when using ListView QML
-
Hey there,
I'm learning some new C++ methods and I just simply can not understand the way how to display a bunch of items loaded from QList in ListView QML.
Actually, I am able to load the items, but when I clear it and enter new values it won't update it to the ListView QML. I tried to search for an answer and I found "this":http://qt-project.org/doc/qt-5/qtqml-cppintegration-contextproperties.html
I have never used Q_INVOKABLE before so it's completely new to me. I did read about it from "here":http://qt-project.org/doc/qt-5/qobject.html#Q_INVOKABLE and I think I got a pretty good understanding what it is and how it works. But now it gives me an error:
bq. Unable to assign a function to a property of any type other than var.
Now I'm completely confused...
Below you can see the two different approaches I've tried to use. First one will be the example where I was able to load the items from QList but not able to refresh ListView QML. The second one is about the problem where I get the error I mentioned above.
This is only a simplified version which includes nothing more than just the parts that are related to my problem. All codes are picked up from the original files and therefore may contain typos -- but I assume you get the point what I'm trying to achieve.
First case is that I'm unable to refresh ListView QML when the values of QList are changed.
@// File: "example.h"
class DataHolder : public QObject {
Q_OBJECTQ_PROPERTY ( QString name READ name WRITE newName NOTIFY nameChanged )
public:
explicit DataHolder ( QObject * parent );
DataHolder ( const QString &name, QObject * parent = 0 );QString name ( ) const; void newName ( const QString &name );
private:
QString s_name;signals:
void nameChanged ( );};
class DataList : public QObject {
Q_OBJECTQ_PROPERTY ( QList <QObject *> data READ data )
public:
explicit DataList ( QObject * parent = 0 );QList <QObject *> data ( ) const;
public slots:
void notWorking ( );private:
QList <QObject *> l_data;};@
@// File: "example.cpp"
#include <QDebug>
#include "example.h"
DataHolder :: DataHolder ( const QString &name, QObject * parent ) : QObject ( parent ), s_name ( name ) {
//
}QString DataHolder :: name ( ) const {
return s_name;
}void DataHolder :: newName ( const QString &name ) {
if ( s_name != name ) {
s_name = name;emit nameChanged ( ); }
}
DataList :: DataList ( QObject * parent ) : QObject ( parent ) {
l_data.append ( new DataHolder ( "First item" ));
l_data.append ( new DataHolder ( "Second item" ));
l_data.append ( new DataHolder ( "Third item" ));
}QList <QObject *> DataList :: data ( ) const {
return QList <QObject *> ( l_data );
}void DataList :: notWorking ( ) {
l_data.clear ( );
l_data.append ( new DataHolder ( "This doesn't refresh the ListView QML." ));qDebug ( ) << "But I know this is called because this will be executed.";
}@
@// File: "(project).cpp"
#include <QGuiApplication>
#include <QQuickView>
#include <QtQml>#include <sailfishapp.h>
#include "example.h"
int main ( int argc, char * argv [ ]) {
QGuiApplication * q_application = SailfishApp :: application ( argc, argv );
QQuickView * q_view = SailfishApp :: createView ( );qmlRegisterType <DataList> ( "Custom.Components", 1, 0, "DataList" ); DataList o_dataList; q_view -> rootContext ( ) -> setContextProperty ( "dataList", &o_dataList ); q_view -> setSource ( SailfishApp :: pathTo ( "qml/example.qml" )); q_view -> showFullScreen ( ); return q_application -> exec ( );
}@
The QML file is just a part of the original one. The rest of the code has nothing to do with the problem I have so I see no reason to include it here. But, of course, it has much more in it so it'd even work.
@// File: "(project).qml"
DataList {
id: dataList_Custom
}ListView {
id: listView_Datawidth: parent.width height: parent.height model: dataList.data delegate: ListItem { id: listItem_Data contentHeight: ( label_Data.height + ( 2.0 * Theme.paddingSmall )) Label { id: label_Data width: ( listItem_Data.width - ( 2.0 * Theme.paddingLarge )) x: Theme.paddingLarge text: model.modelData.name color: ( listItem_Data.highlighted ? Theme.highlightColor : Theme.primaryColor ) } onClicked: dataList_Custom.notWorking ( ); }
}@
Alrighty, so.. When I open up the application it gets the files from root like it's suppose to. However, when I click any of the ListItems it does absolutely nothing.
Below you'll see the other method I tried to use and it gives me an error (mentioned in the beginning).
Basically this is identical to the one I used above but with some minor changes. The only change were made in my header file.
@// File: "example.h"
class DataList : public QObject {
Q_OBJECT// Q_PROPERTY ( Qlist <QObject *> data READ data )
public:
explicit DataList ( QObject * parent = 0 );Q_INVOKABLE Qlist <QObject *> data ( ) const { return QList <QObject *> ( l_data ); }
public slots:
void notWorking ( );private:
QList <QObject *> l_data;};@
Of course, because I don't use
@Q_PROPERTY ( Qlist <QObject *> data READ data )@
in my second attempt, I have commented out the parts related to it from all the files.
So, I would like to know what I'm doing wrong in both cases. Am I using completely different method than I'm suppose to, or am I missing something important here?
Any help is appreciated.
Thank you! -
bq. Am I using completely different method than I’m suppose to, or am I missing something important here?
The best way would be to create a model by subclassing "QAbstractListModel":http://qt-project.org/doc/qt-5/qabstractlistmodel.html. Please go through "subclassing":http://qt-project.org/doc/qt-5/qabstractlistmodel.html#subclassing topic.
In this way to reload new items you will have to first "removeRows":qt-project.org/doc/qt-5/qabstractitemmodel.html#removeRows and then add new items after "beginInsertRows":http://qt-project.org/doc/qt-5/qabstractitemmodel.html#beginInsertRows so that the ListView gets notified and updates properly.
An example "here":http://qt-project.org/doc/qt-5/qtquick-modelviewsdata-cppmodels.html#qabstractitemmodel. -
Thanks for reply p3c0,
No matter how hard I tried to find a solution (without any success, obviously) for some reason I couldn't find the answer from "QAbstractListModel":qt-project.org/doc/qt-5/qabstractlistmodel.html class. However, the links you gave me are indeed useful - at least when you quickly browse through them...
I'll try this out in a few days and let you know how things worked out.
-
Okay, so...
I read the whole "QAbstractListModel":http://qt-project.org/doc/qt-5/qabstractlistmodel.html and I tried to follow the example you linked and I'm having some issues without even getting to the part I actually need.
To be honest, due all respect, I hate when there are suppose to be examples but the authors have left some of the critical parts out of them thinking that users know them already.
Well, I started to study C++ like only few weeks a go and in the example there have been left out some important things. And I just can't figure them out how to write them.
But before we go to the parts I know nothing about (or something, but not enough ...) here's my problem at the moment.
Without any functions I tried to simply using the class. Below you can see my code.
@// File: list.h
#include <QAbstractItemModel>
// I think something's really wrong with this class, right?
class File {
public:
File ( const QString &file );
};// And for some reason this seems incomplete as well ...
class FileModel : public QAbstractItemModel {
Q_OBJECTpublic:
FileModel ( QObject * parent = 0 );
};@@// File: list.cpp
#include "list.h"
FileModel :: FileModel ( QObject * parent ) : QObject ( parent ) {
//
}@@#include <QGuiApplication>
#include <QQuickView>#include <sailfishapp.h>
#include "list.h"
int main ( int argc, char * argv [ ]) {
QGuiApplication * q_application = SailfishApp :: application ( argc, argv );
QQuickView * q_view = SailfishApp :: createView ( );// This is where I get my error - see below for quote. FileModel c_fileModel; q_view -> setSource ( SailfishApp :: pathTo ( "qml/cpp-listview.qml" )); q_view -> showFullScreen ( ); return q_application -> exec ( );
}@
The error I get (in "cpp-listview.cpp" line #13):
bq. Cannot declare variable 'c_fileModel' to be of abstract type 'FileModel'
What's that suppose to mean? This is exactly like the one on the "example":http://qt-project.org/doc/qt-5/qtquick-modelviewsdata-cppmodels.html#qabstractitemmodel p3c0 wrote.
I also tried exactly the same code (without the dots, of course) and it gave me the same error and a bunch more.
So what am I missing here?
-
Few issues here
-
Why are you subclassing QAbstractItemModel ? For you purpose its good to subclass QAbstractListModel.
bq. FileModel :: FileModel ( QObject * parent ) : QObject ( parent ) {
//
}FileModel should call the base class constructor viz. QAbstractItemModel (in current case, but change it to QAbstractListModel) instead of QObject.
bq. Cannot declare variable ‘c_fileModel’ to be of abstract type ‘FileModel’
For the custom model to work you should atleast reimplement "rowCount":http://qt-project.org/doc/qt-5/qabstractitemmodel.html#rowCount and "data()":http://qt-project.org/doc/qt-5/qabstractitemmodel.html#data
Please go through "description":http://qt-project.org/doc/qt-5/qabstractlistmodel.html#details again.
bq. 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.
-
-
Thanks for your effor, p3c0
-
I don't really understand what you're trying to tell me. First you ask why I'm subclassing (like it's the wrong way to do) but right after it you say it's the right way.
-
Sorry, my bad. Completely forgot to change it to the header file as well.
-
I'm still getting this very same error no matter I've defined "rowCount()":http://qt-project.org/doc/qt-5/qabstractitemmodel.html#rowCount and "data()":http://qt-project.org/doc/qt-5/qabstractitemmodel.html#data functions. I've also used "roleNames()":http://qt-project.org/doc/qt-5/qabstractitemmodel.html#roleNames because I know I'm going to need it later on this project.
The error I'm getting also says the following:
bq. Because the following virtual functions are pure within 'FileModel':
virtual QModelIndex QAbstractItemModel::index(int, int, const QModelIndex&) const
virtual QModelIndex QAbstractItemModel::parent(const QModelIndex&) const
virtual int QAbstractItemModel::columnCount(const QModelIndex&) constWhat does it mean when (virtual) functions are pure within the class I've created.
Below you can see the source codes:
@// File: "list.h"
class FileModel : public QAbstractItemModel {
Q_OBJECTpublic:
explicit FileModel ( QAbstractItemModel * parent = 0 );enum Roles { Role }; virtual QHash <int, QByteArray> roleNames ( ) const; virtual int rowCount ( const QModelIndex &parent = QModelIndex ( )) const; virtual QVariant data ( const QModelIndex &index, int i_role ) const;
private:
QList <QString> l_files;
};@@// File: "list.cpp"
FileModel :: FileModel ( QAbstractItemModel * parent ) : QAbstractItemModel ( parent ) {
beginInsertRows ( QModelIndex ( ), rowCount ( ), rowCount ( ));
//
endInsertRows ( );
}QHash <int, QByteArray> FileModel :: roleNames ( ) const {
QHash <int, QByteArray> h_roles;
h_roles [ Role ] = "file";return h_roles;
}
int FileModel :: rowCount ( const QModelIndex &/* parent */ ) const {
return l_files.size ( );
}QVariant FileModel :: data ( const QModelIndex &q_index, int i_role ) const {
if ( !q_index.isValid ( ) || q_index.row ( ) < 0.0 || q_index.row ( ) >= rowCount ( )) {
return QVariant ( );
}switch ( i_role ) { /* case Qt :: DisplayRole : return "Qt :: DisplayRole"; break; */ case Role : return "Role"; break; default: return QVariant ( ); }
}@
@// File "(project).cpp"
int main ( int argc, char * argv [ ]) {
QGuiApplication * q_application = SailfishApp :: application ( argc, argv );
QQuickView * q_view = SailfishApp :: createView ( );// This is where the error comes! FileModel c_fileModel; // q_view -> rootContext ( ) -> setContextProperty ( "filesModel", &c_fileModel ); q_view -> setSource ( SailfishApp :: pathTo ( "qml/cpp-listview.qml" )); q_view -> showFullScreen ( ); return q_application -> exec ( );
}@
I also have a question - or an issue if you prefer. In the "example":http://qt-project.org/doc/qt-5/qtquick-modelviewsdata-cppmodels.html#qabstractitemmodel the author uses addAnimal() function which obviously is a custom function inside Animal class. Why that part has been wiped off from this example? I have absolutely no clue (at this point) what there could possibly be...
I do know that my beginInsertRows() and endInsertRows() functions should be there, but at first I'd just like to get some data pushed into my FileModel class and that I'm able to use this in my QML.
And do I need nothing more than just the part written in the example when I'm creating my own Animal type of class, e.g. File? Because it seems a little bit too weird in my case to work as is and the author has placed some dots (argh!) that there is something.
-
-
bq. 1. I don’t really understand what you’re trying to tell me. First you ask why I’m subclassing (like it’s the wrong way to do) but right after it you say it’s the right way.
What i'm saying is you need to subclass QAbstractListModel and not QAbstractItemModel and Cannot declare variable ‘c_fileModel’ to be of abstract type ‘FileModel’ error should go.
bq. I also have a question – or an issue if you prefer. In the example [qt-project.org] the author uses addAnimal() function which obviously is a custom function inside Animal class. Why that part has been wiped off from this example? I have absolutely no clue (at this point) what there could possibly be…
That's not a complete code. "Here":http://qt-project.org/doc/qt-5/qtquick-models-abstractitemmodel-example.html is the complete one.
Also you would get the complete source code in the Installation path of your Qt.bq. The complete source code for this example is available in examples/quick/modelviews/abstractitemmodel within the Qt install directory.
Also please check how the Roles are defined properly in the example.
-
Oops, how the heck I could've read it wrong so many times? ~Shame on me! ~ And the reason why I couldn't find any complete source is that I don't actually have Qt5.1, I have *SailfishSDK *and it doesn't provide any other examples than those which are specially created for *Sailfish *applications. :)
But hey, I finally got it working - kind of...
The result is actually exactly the same than I had in the very beginning; I am able to create a model in *C++ *which can be used in QML. I'm also able to execute correct function to add new items to the list from QML. But the problem is, that the list doesn't update -- I'm not able to see any of the new items.
Below you can see the complete source code which I've tried:
@// File: "list.h"
#ifndef LIST_H
#define LIST_H#include <QAbstractItemModel>
#include <QHash>
#include <QByteArray>class File {
public:
File ( const QString &filename );QString filename ( ) const;
private:
QString s_file;};
class FileModel : public QAbstractListModel {
Q_OBJECTpublic:
explicit FileModel ( QAbstractListModel * parent = 0 );enum Roles { Filename }; void _addFile ( const File &filename ); int rowCount ( const QModelIndex &parent = QModelIndex ( )) const; QVariant data ( const QModelIndex &index, int i_role = Qt :: DisplayRole ) const; QList <File> l_files;
protected:
QHash <int, QByteArray> roleNames ( ) const;private:
//};
class Files : public QObject {
Q_OBJECTpublic:
explicit Files ( QObject * parent = 0 );public slots:
void addFile ( const QString &s_filename );signals:
void countChanged ( );};
#endif // LIST_H@
@// File: "list.cpp"
#include <QDebug>
#include "list.h"
File :: File ( const QString &filename ) : s_file ( filename ) {
//
}QString File :: filename ( ) const {
return s_file;
}FileModel :: FileModel ( QAbstractListModel * parent ) : QAbstractListModel ( parent ) {
//
}void FileModel :: _addFile ( const File &s_filename ) {
beginInsertRows ( QModelIndex ( ), rowCount ( ), rowCount ( ));l_files << s_filename; endInsertRows ( ); qDebug ( ) << "C++ function";
}
int FileModel :: rowCount ( const QModelIndex &parent ) const {
Q_UNUSED ( parent );return l_files.size ( );
}
QVariant FileModel :: data ( const QModelIndex &q_index, int i_role ) const {
if ( !q_index.isValid ( ) || q_index.row ( ) < 0.0 || q_index.row ( ) >= l_files.count ( )) {
return QVariant ( );
}const File &file = l_files [ q_index.row ( )]; if ( i_role == Filename ) { return file.filename ( ); } return QVariant ( );
}
QHash <int, QByteArray> FileModel :: roleNames ( ) const {
QHash <int, QByteArray> h_roles;
h_roles [ Filename ] = "filename";return h_roles;
}
Files :: Files ( QObject * parent ) : QObject ( parent ) {
//
}void Files :: addFile ( const QString &s_filename ) {
FileModel c_fileModel;
c_fileModel._addFile ( File ( s_filename ));qDebug ( ) << "QML function";
}@
@// File: "(project).h"
#ifdef QT_QML_DEBUG
#include <QtQuick>
#endif#include <QGuiApplication>
#include <QQuickView>
#include <QtQml>
#include <QQmlContext>#include <sailfishapp.h>
#include "list.h"
int main ( int argc, char * argv [ ]) {
QGuiApplication * q_application = SailfishApp :: application ( argc, argv );
QQuickView * q_view = SailfishApp :: createView ( );qmlRegisterType <Files> ( "Custom.Component", 1, 0, "Files" ); FileModel c_fileModel; c_fileModel._addFile ( File ( "Default item" )); c_fileModel._addFile ( File ( "Default item" )); c_fileModel._addFile ( File ( "Default item" )); QQmlContext * q_qmlContext = q_view -> rootContext ( ); q_qmlContext -> setContextProperty ( "filesModel", &c_fileModel ); q_view -> setSource ( SailfishApp :: pathTo ( "qml/cpp-listview.qml" )); q_view -> showFullScreen ( ); return q_application -> exec ( );
}@
@// File: "(project).qml"
// NOTICE: This is just a part of my QML code.import Custom.Component 1.0
Page {
id: page_FirstFiles { id: files_CustomComponent } SilicaListView { id: silicaListView anchors.fill: page_First header: PageHeader { title: "Custom model" } model: filesModel delegate: ListItem { id: listItem Label { width: ( listItem.width - ( 2.0 * Theme.paddingLarge )) height: listItem.height x: Theme.paddingLarge text: filename color: ( listItem.highlighted ? Theme.highlightColor : Theme.primaryColor ) verticalAlignment: Text.AlignVCenter } onClicked: { files_CustomComponent.addFile ( "New item" ) console.log ( silicaListView.count ) } } }
}@
So any ideas why the *SilicaListView *won't update the items in the list? Debugging shows me that those functions are indeed executed when I click on any of the excisting items in the list.
-
bq. Files {
id: files_CustomComponent
}This will create a new Files Component, don't use it to add new item
It will be nice if the Files Class contain only setter and getter methods for eg. in your case set a filename.
You should create a new "Q_INVOKABLE ":http://qt-project.org/doc/qt-5/qobject.html#Q_INVOKABLE function. This will allow you to call a C++ function from QML.
So for eg. you can move addFile("New item") to the model itself and make it Q_INVOKABLE
@
Q_INVOKABLE void addItem(QString filename);
@Then you need to add the file there encapsulated in beginInsertRows(QModelIndex(), rowCount(), rowCount()) and endInsertRows() just as you have done in _addFile function.
-
Thanks for helping me with this!
Unfortunately I'm still not able to update the list from QML. I did what you suggested and used Q_INVOKABLE instead, but nothing actually changes. All the functions are normally executed and the string (given in QML) is recognized as it should be.
I haven't used Q_INVOKABLE before, actually, so this was new to me. Of course I've read about it, but never had to use one. So I read "this":http://developer.nokia.com/community/wiki/Calling_Qt_class_methods_from_QML tutorial and the functions do work properly.
Any idea?
-
Did you use beginInsertRows and endInsertRows when adding new elements ?
I have created the following complete working example depending upon your example above, the only change is it doesnot use SailFish SDK. But i think that should not be an issue, or you can adjust it using SailFish's SDK.
main.cpp
@
#include <QGuiApplication>
#include <QtQuick>
#include "filemodel.h"int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);QQuickView view; QObject::connect(view.engine(), SIGNAL(quit()), &app, SLOT(quit())); FileModele model(view.engine()); view.rootContext()->setContextProperty("filesModel",&model); view.setSource(QUrl(QStringLiteral("qrc:///main.qml"))); view.show(); return app.exec();
}
@filemodel.h
@
#ifndef FILEMODEL_H
#define FILEMODEL_H#include <QAbstractListModel>
#include "file.h"class FileModele : public QAbstractListModel
{
Q_OBJECT
public:
explicit FileModele(QObject *parent = 0);enum Roles { FileNameRole = Qt::UserRole + 10, }; virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const; virtual int rowCount(const QModelIndex &parent = QModelIndex()) const; Q_INVOKABLE void addItem(QString filename); Q_INVOKABLE void init(); void loadItems(); void newItem(QString filename); //Internal
signals:
public slots:
protected:
QHash<int, QByteArray> roleNames() const;private:
QList<File*> m_items;
QStringList m_list;
int m_count;};
#endif // FILEMODEL_H
@filemodel.cpp
@
#include "filemodel.h"FileModele::FileModele(QObject *parent) :
QAbstractListModel(parent)
{
m_count = 0;
m_list << "File1" << "File2" << "File3" << "File4";
}QVariant FileModele::data(const QModelIndex &index, int role) const
{
if (index.row() < 0 || index.row() >= m_items.count())
return QVariant();File *item = m_items.at(index.row()); switch (role) { case FileNameRole: return QVariant::fromValue(item->filename()); }
}
int FileModele::rowCount(const QModelIndex &parent) const
{
return m_count;
}QHash<int, QByteArray> FileModele::roleNames() const
{
QHash<int, QByteArray> roles = QAbstractItemModel::roleNames();
roles.insert(FileNameRole, QByteArray("filename"));
return roles;
}void FileModele::addItem(QString filename)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
newItem(filename);
endInsertRows();
}void FileModele::init()
{
beginResetModel();
loadItems();
endResetModel();
}void FileModele::loadItems()
{
beginInsertRows(QModelIndex(), rowCount(), rowCount());
for(int i=0; i<m_list.count();i++)
newItem(m_list.at(i));
endInsertRows();
}void FileModele::newItem(QString filename)
{
File *item = new File;
item->setFilename(filename);
++m_count;
m_items.append(item);
}
@file.h
@
#ifndef FILE_H
#define FILE_H#include <QObject>
class File : public QObject
{
Q_OBJECT
public:
explicit File(QObject *parent = 0);QString filename() const; void setFilename(const QString &filename);
signals:
public slots:
private:
QString m_filename;};
#endif // FILE_H
@file.cpp
@
#include "file.h"File::File(QObject *parent) :
QObject(parent)
{
}QString File::filename() const
{
return m_filename;
}void File::setFilename(const QString &filename)
{
m_filename = filename;
}
@main.qml
@
import QtQuick 2.2
import QtQuick.Controls 1.2Item {
width: 300
height: 400ListView { anchors.fill: parent model: filesModel delegate: Text { text: model.filename } } Button { width: 300 height: 50 text: "Add Item" anchors.left: parent.left anchors.bottom: parent.bottom onClicked: filesModel.addItem("NewItem") } Component.onCompleted: filesModel.init()
}
@To add new item just click on the Button and it should add new Item to the ListView.
Hope this helps you ... -
Thank you for this p3c0
At first look at your code (I don't have my own code with me at the moment ..) I haven't forgot anything. Therefore I assume that I have forgot to link some functions together, or so..
I'll check it later and come back at you. Thank you again!
.
-- By the way, in some examples I've seen people using
@Qt :: UserRole + 1@in their examples, but you are using @Qt :: UserRole +10@
And to be honest, I have never read any good explanation what this actually does. I do know it's somehow linked to the first (default?) role, but that's all there is to is.
-
There are some predefined Role's used by Qt, for user defined Role's Qt::UserRole's are used. It doesnot matter if it starts from UserRole + 1 or UserRole + 100 or UserRole + 1000 unless it doesnot clashes with other Qt::UserRole's.