Using QAbstractListModel in ListView - qt

I'm new with Qt , so please bear with me .
I've successfully managed to populate a ListView from a StringList and a QList of Object*
What I'm struggling now with is to populate a ListView in QML using a class defined in C++ that derives QAbstractListModel.
Here's the prototype of my CPP class :
class MessageListEntryModel : public QAbstractListModel
{
Q_OBJECT
public:
enum eMLERoleTypes
{
MLERT_MSG = Qt::UserRole+1,
MLERT_COLOR
};
MessageListEntryModel(QObject* parent=0);
virtual ~MessageListEntryModel();
void AddEntry(QString aMessage, QColor aColor);
// pure virtuals implementations
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
int rowCount(const QModelIndex &parent = QModelIndex()) const ;
int columnCount(const QModelIndex &parent = QModelIndex()) const ;
QModelIndex index(int row, int column, const QModelIndex &parent = QModelIndex()) const;
QModelIndex parent(const QModelIndex &child) const ;
QHash<int,QByteArray> roleNames();
private:
QList<MessageEntry*> m_vpMessages;
MessageEntry is a simple class that contains 2 memebers , a QColor and a QString (the class doesn't extend QObject).
I had to implement all the above functions since they're pure virtual in an underlying class (is this normal? so far in tutorials/samples people mentioned only about roleNames and data).
The implementation of roleNames and data are as following :
QHash<int,QByteArray> MessageListEntryModel::roleNames()
{
QHash<int,QByteArray> rez;
rez[MLERT_MSG]="message";
rez[MLERT_COLOR]="messagecolor";
return rez;
}
QVariant MessageListEntryModel::data(const QModelIndex &index, int role) const
{
qDebug()<<" Data asked for "<<index.row()<<" and role "<<role;
if (index.row()<0 || index.row()>=m_vpMessages.size())
{
return QVariant();
}
MessageEntry* entry = m_vpMessages[index.row()];
if (role == MLERT_MSG)
{
return QVariant::fromValue(entry->message);
} else if (role == MLERT_COLOR)
{
return QVariant::fromValue(entry->messageColor);
}
// should be unreachable code
return QVariant();
}
The QML portion of the List View is something like this :
ListView {
id: quickMessageListdata
model: quickListModel
delegate: Rectangle {
width: 400
height: 25
color:"#000000"
Text{
text: model.message
color: model.messagecolor
}
}
So far this is my understanding on how to implement things in CPP and QML.
For linking these two, I use the following code :
MessageListEntryModel* model =new MessageListEntryModel();
// Add various entries
...
// assign model in QML
m_pViewRef->rootContext()->setContextProperty("quickListModel",model);
With the code above, when running nothing is displayed in the ListView and I'm getting the following errors :
Unable to assign [undefined] to QString
Unable to assign [undefined] to QColor
I'm also registering the model class to be exported to QML (don't know if this is necessary) :
qmlRegisterType<MessageListEntryModel> ("dlti.exported",1,0,"MessageListEntryModel");
So it's quite obvious that either I missuderstood the proper use of a QAbstractListItem derived class OR I miss a simple vital key information.
I would appreciate some pointers to some relevant samples / tutorials (one that also shows you how to properly access data from the model in QML, since I've noticed that in CPP it never passes through the data function).
Also please notice that I'm using qt5 , so qt4.8 samples won't do the trick.
EDIT
After long hours of frustrations, I finally managed what was wrong with the damn thing :
My roleNames function signature was wrong !
The correct signature for overload is :
protected :
QHash<int,QByteArray> roleNames() const;
Please notice the protected and the const modifiers.
After declaring the function the correct way , it all worked fine.
For further notice, implementing data and rowCount was sufficient :).
Thanks for the help.
I will accept BaCaRoZzo's answer since I only manged to figure this out after looking over the code from the example.
As a side note, it works well with both message and model.message.

How do you implement the addition method? You should use a method like in the example provided in my comment.
From the docs:
An insertRows() implementation must call beginInsertRows() before
inserting new rows into the data structure, and it must call
endInsertRows() immediately afterwards.
You should have something like:
void MessageListEntryModel::add(params...)
{
beginInsertRows(QModelIndex(), rowCount(), rowCount()); // kindly provided by superclass
// object creation based on params...
m_vpMessages << objectCreated;
endInsertRows(); // kindly provided by superclass
}
with
int MessageListEntryModel::rowCount(const QModelIndex & parent) const {
Q_UNUSED(parent);
return m_vpMessages.count();
}
Also, #Jonathan Mee comment is correct: use just the role names inside the delegate as you defined them in the model. If everything else is correct that is the way to access data.
One of the best documentation piece to understand C++ models implementation and usage is the Model subclassing reference. In this document is clearly depicted which are the most important methods to subclass and for what purposes.
As for the methods to implement, it really depends on the needs. Depending on the possible actions on the model, different methods should be implemented (refer to the above link for full details). A model for a ListView in which items can be added/removed can inherit from QAbstractListModel and just rely on the default implementations for most of the functions. You just need data(), roleNames() and rowCount(), as you already saw in most examples.
If instead you also need to edit data not just adding/removing it, then you also need other functions, particularly setData(). It is also your duty to notify the attached view(s) with any modification of the model occurred in setData(), via the signal dataChanged(). Refer again to the above provide subclassing reference.
Note also that, if the add method is modified with the Q_INVOKABLE modifier, i.e. it is declared as
Q_INVOKABLE void add(params...);
in the model header, you can also call it from QML (since the model is set as a context property) and you can write, for instance:
ListView {
id: quickMessageListdata
model: quickListModel
delegate: Rectangle {
width: 400
height: 25
color:"#000000"
Text{
text: model.message
color: model.messagecolor
}
}
Component.onCompleted: {
quickListModel.add(params)
quickListModel.add(params)
}
}
to insert items in the view as soon as the view is created. Clearly the same approach can be applied to other QML signals so that you can react to QML events and trigger addition/removal behaviours.
Finally, you don't need to register the model with qmlRegisterType. For your current requirement it is superfluous.

Hmmm... I'm not super familiar with QML but I believe that this is your problem: http://qt-project.org/doc/qt-5/qtquick-modelviewsdata-cppmodels.html#qabstractitemmodel
According to the link it looks like you need to change your Text block to:
Text{
text: message
color: messagecolor
}
Further reading: http://qt-project.org/forums/viewthread/5491

I've also experienced hard times before when creating own list models with Qt C++. To avoid the development overhead for a C++ model, I started to use QSyncable (a existing QAbstractListModel implementation by Ben Lau). You can find it on GitHub here.
The best part of the project is the JsonListModel QML type. It can transform any variant JSON list that you create or fetch in QML to a full-featured QML ListModel. This saves a lot of time and effort for applications that e.g. work with JSON or REST services. You can find a detailed guide how it works here.

Related

How can I pass a QML object reference into Qt C++?

I am attempting to create something like jQuery's autocomplete as a widget in QML and Qt C++. Toward that end, I created a C++ AutoCompleteListener child of QObject and then register it with:
qmlRegisterType<AutoCompleteListener>(
"foo.AutoCompleteListener",0,1,"AutoCompleteListener");
Then, I instantiate the listener and the AutoCompleteForm like:
import QtQuick 2.5
import com.foo.AutoCompleteListener 0.1
Item {
AutoCompleteForm { id: autocomplete_form }
AutoCompleteListener { id: listener }
}
How can I pass a reference to the QML object AutoCompleteForm into AutoCompleteListener?
I tried passing the autocomplete_form field into:
Q_INVOKABLE void set_autocomplete_form(QQmlComponent *autocomplete_form);
on the onCompleted signal:
Item {
AutoCompleteForm {
id: autocomplete_form
Component.onCompleted: {
console.log("AutoCompleteForm completed");
listener.set_autocomplete_form(autocomplete_form);
}
}
AutoCompleteListener {
id: listener
Component.onCompleted: {
console.log("AutoCompleteListener completed");
}
}
}
However, the reference is a nullptr even though both AutoCompleteListener and AutoCompleteForm have been instantiated:
Instantiating AutoCompleteListener and parent is QObject(0x0)
qml: AutoCompleteListener completed
qml: AutoCompleteForm completed
Setting autocomplete_form = QObject(0x0)
How can I get a reference to the AutoCompleteForm or AutoCompleteListener's QML parent? I want to avoid crawling down the entire QML hierarchy with something like:
QObject* f = mView.rootObject();->findChild<QObject *>("AutoCompleteForm");
I plan to support having multiple AutoComplete widgets instantiated in parallel so a relative path (../AutoCompleteForm) to manipulate the QML objects seems better than having to crawl through the tree.
How can I pass a QML object reference into Qt C++?
You can't, the language was not designed for that. But you can get references from within C++ using findChild and findChildren. But read below for your real solution.
Also, I think your question is about an XY Problem. You have a problem X and you think Y solves it, so you ask for Y.
Correct solution for your original problem:
I am attempting to create something like jQuery's autocomplete as a
widget in QML and Qt C++.
In order to solve your real problem you need to use property bindings correctly. QML is a declarative language and self obsession with imperative programming makes it difficult to be straightforward.
Use this pattern for QML:
AutocompleteForm{
id: form
text: "Search here..."
suggestedTerms: helper.results
}
AutoCompleteHelper{
id: helper
searchFor: form.text
}
And for C++ implement
class AutoCompleteHelper : public QObject
{
Q_OBJECT
Q_PROPERTY(QString searchFor READ searchFor WRITE setSearchFor NOTIFY searchForChanged)
Q_PROPERTY(QStringList results READ results NOTIFY resultsChanged)
public:
AutoCompleteHelper() {}
virtual ~AutoCompleteHelper() {}
QString searchFor() const { return m_searchFor; }
QStringList results() const { return m_results; }
public slots:
void setSearchFor(QString searchFor)
{
if (m_searchFor == searchFor)
return;
m_searchFor = searchFor;
emit searchForChanged();
// 1. Search for it...
// 2. Some time later fill m_results
// 3. Then: emit resultsChanged()
}
signals:
void searchForChanged();
void resultsChanged();
private:
QString m_searchFor;
QStringList m_results;
};
Then you will see it magically works, because as soon as you change the form.text the binding sets the value in helper.searchFor, which then immediately fires the C++ slot where you can react even instantaneously, and in C++ the emission of resultsChanged() magically fills form.suggestedTerms.
You will find this pattern is extremely efficient so you'll even want to delay it by restarting a timer in C++. Furthermore, it is also beautifully declarative and clean.
How can I get a reference to the AutoCompleteForm or
AutoCompleteListener's QML parent?
The only alternative to rootObject()->findChild() appears to be QQmlProperty::read. There is several of read() function overloads so you can specify the context more precise. You have to provide the name for the object you would like to fetch and make it a property of some root object to start with. I like the general article on this subject of interfacing between QML and C++.
To accomplish precisely what you want or access the parent of certain known property you can try QQmlContext::parentContext together with QQmlProperty::read that accepts the context and see if the empty object name allows to resolve the object then.
You can pass it as a QVariant:
Q_INVOKABLE void QmlLink::pass_object(QVariant v)
{
YourObject* tempObject = (YourObject*) v.value<void *>();
...
}
qml:
my_bound_property.pass_object(the_object);

QT Model/View Project doesnt show Strings from QList

My QTableView doesn't show strings from a QStringList.
In QTableWidget I have QTableWidgetItems. Must I set the strings manually or will the view show them automatically? In all the tutorials I don't see a "->setItem", they appear automatically.
I have 2 QLineEdits that give the QStrings to my Model :
void View::pushButtonClicked() {
meinModel->setData(txtname->text(), txtvalue->text());
}
In setData I push the Strings in two QLists.
names.push_back(name);
values.push_back(value);
I emit a dataChanged signal with the index from topleft and bottomright.
QModelIndex topLeft = createIndex(names.size()+1,0);
QModelIndex bottomRights = createIndex(names.size()-1,1);
emit dataChanged(topLeft, bottomRights);
I have a QAbstractTableModel and so i override the columnCount, rowCount and data Method.
In my data() Method I return my value and name:
QString returnValue;
if(0 == index.column()) { returnValue = names.at(index.row()); }
All of this compiles without warnings, but doesn't work correctly :( Is there something I'm doing obviously wrong?
One obvious problem is that you didn't get the semantics of dataChanged correctly. dataChanged means that an existing item has changed its value. When you change the structure of the model by adding/removing rows or columns, you have to enclose the modification in beginXxx and endXxx calls - see this answer for details.
For example:
void MyModel::setData(const QString & name, const QString & value) {
beginInsertRows(QModelIndex(), names.size(), names.size());
names.push_back(name);
values.push_back(value);
endInsertRows();
}

Reediting a QTableView cell when the given value is not acceptable

I have a QTableView that obtains the data from a custom model and it's edited using a custom delegate.
//...
view->setModel(stockModel);
view->setItemDelegateForColumn(0, nameDelegate);
When the user edits a specific cell it types some text (name for an object) and this text could be accepted by the program or not (the program doesn't want to have repeated names).
My solution for this was to the custom delegate to have a signal: notValidText(QModelIndex) and use the signal/slot mechanism to connect the signal to the tableview edit(QModelIndex) slot. This, from what i know, should reedit the cell in question:
//implementation of the delegate
void NameDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index)
{
QLineEdit *line = static_cast<QLineEdit*>(editor);
if(!model->setData(index, line->text(), Qt::EditRole))
{
emit notValidData(index);
}
}
//connection of the view with the delegate
connect(nameDelegate, SIGNAL(notValidData(QModelIndex)), view, SLOT(edit(QModelIndex)));
Unfortunately this is not what happens, so i'm doing something wrong. If someone could give me a tip i would really appreciate.
I just put here some code to show what i did, ask if you need me to post anymore.
Thanks in advance

Adding a 'None' option to a QComboBox linked to a model

I have a QComboBox so the user can a network name from from a model column. I'm using code like this:
self.networkSelectionCombo = QtGui.QComboBox()
self.networkSelectionCombo.setModel(self.model.worldLinks)
self.networkSelectionCombo.setModelColumn(WLM.NET_NAME)
I'm using PySide, but this is realy a Qt question. Answers using C++ are fine.
I need to give the user the option of not selecting any network. What I'd like to do is add an extra item to the combo box called 'None'. However this will just get overridden by the model contents.
The only way I can think of is to create an intermediate custom view on this model column and use that to update the combo, then the view can handle adding in the extra 'magic' item. Does anyone know a more elegant way of doing this?
One possible solution is to subclass the model you are using in order to add there the extra item. The implementation is straight forward. If you call your model MyModel then the subclass would look like this (C++ used):
class MyModelWithNoneEntry : public MyModel
{
public:
int rowCount() {return MyModel::rowCount()+1;}
int columnCount() {return MyModel::columnCOunt();}
QVariant data(const QModelIndex & index, int role = Qt::DisplayRole) const
{
if (index.row() == 0)
{
// if we are at the desired column return the None item
if (index.column() == NET_NAME && role == Qt::DisplayRole)
return QVariant("None");
// otherwise a non valid QVariant
else
return QVariant();
}
// Return the parent's data
else
return MyModel::data(createIndex(index.row()-1,index.col()), role);
}
// parent and index should be defined as well but their implementation is straight
// forward
}
Now you can set this model to the combo box.

Using a delegate with a QDataWidgetMapper and QLabel

I'm trying to use a delegate to customize the way data from a model is displayed when using a QDataWidgetMapper.
I have two different versions of a widget, one is view-only (the data is displayed in QLabels) and the other is used to edit the data (the data is displayed in appropriate editors).
The latter one works perfectly with the delegate, everything is fine.
As you may have guessed the problem arises with the first one... When mapping the sections of my model to QLabels using the QDataWidgetMapper, the delegate is never called and the data is displayed correctly for the sections with regular data (strings, ints,...) but no data is displayed for the sections of my model with a custom data type (a kind of list) which I would like to format as a string using the delegate.
I've already performed this operation successfully when the same data is displayed in a QTableView (the method paint() of the delegate is called when the data is displayed).
After having looked at it a little bit closer, I've been able to see that, when using QLabels to display the data, the delegate is never called though I've explicitly associated a delegate to the QDataWidgetMapper using its method setItemDelegate().
So in synthesis, assume a class CustomItemDelegate which inherits QStyledItemDelegate with virtual methods:
void CustomItemDelegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const {
qDebug() << "DELEGATE: PAINT" << index.column();
QStyledItemDelegate::paint(painter, option, index);
}
void CustomItemDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const {
qDebug() << "DELEGATE: SET EDITOR DATA" << index.column();
QStyledItemDelegate::setEditorData(editor, index);
}
and a widget with following in it:
QDataWidgetMapper* mapper = new QDataWidgetMapper();
CustomItemDelegate* delegate = new CustomItemDelegate();
mapper->setModel(model);
mapper->setItemDelegate(delegate);
mapper->addMapping(editorWidget, 1);
mapper->addMapping(label, 2, "text");
mapper->toFirst();
QTableView* view = new QTableView();
CustomItemDelegate* delegate2 = new CustomItemDelegate();
view->setModel(model);
view->setItemDelegate(delegate2);
the code outputs:
DELEGATE: SET EDITOR DATA 1
// NOTHING ?!
DELEGATE: PAINT 1
DELEGATE: PAINT 2
and as a result I got
my editorWidget with the correct data in it (whatever data type the section contains: regular or custom, as long as the editor handles the type of course),
my label only displays the data if the section contains a regular type of the data as the delegate is not called
my view would display everything fine as the delegate is called for each section
So my questions are:
why isn't the delegate called when the mapped widget is a QLabel?
in this case, how come the data is even displayed when the data type is regular? Magic?
Thanks very much and I apologize in advance if the answer is obvious (but even then, thank you for pointing it out :P),
ixM
This is the code from QT that populates widgets
void QDataWidgetMapperPrivate::populate(WidgetMapper &m)
{
if (m.widget.isNull())
return;
m.currentIndex = indexAt(m.section);
if (m.property.isEmpty())
delegate->setEditorData(m.widget, m.currentIndex);
else
m.widget->setProperty(m.property, m.currentIndex.data(Qt::EditRole));
}
In the first case when you do not specify a property delegate is used whereas in the second case the data is set to widget directly by passing your delegate.
I don't know why it was designed this way but this is how it works currently !

Resources