I have implemented my QTreeView & QAbstractItemModel according to editabletreemodel example plus the advice here: QTreeView & QAbstractItemModel & insertRow.
It works, ok.
But there is a difference I do not understand.
To make my application work properly when inserting child rows I had to emit layoutChanged(). I had to. If I don't emit - inserting a child works not good: a new child correctly appears in model, it is correctly saved (when I save), but it is NOT shown in view.
The editabletreemodel example does not contain "emit layoutChanged()", and it works fine with inserting children.
Here is my insertRows method of QAbstractItemModel sibling class:
bool ExTree::insertRows(int position, int rows, const QModelIndex &parent)
{
ExObject *parentItem = getItem(parent);
bool success;
beginInsertRows(parent, position, position + rows - 1);
success = parentItem->insertChildren(position, rows, rootItem->columnCount());
endInsertRows();
if(success) emit layoutChanged();
return success;
}
It is almost like those in editabletreeview example, the only difference is the emit.
Why, please?
I want to understand, WHY exactly I needed to emit layoutChanged(), while editabletreeview does not do this. Will appreciate greatly if anyone can explain this point.
I do not need understanding why emit. I need understanging why I need to emit and editabletreeview does not need it.
Related
TreeView Shows my data not correctly. What is wrong?
Here is the function of adding a child
bool TreeModel::addChild(const QVariant &data, const QModelIndex &parent)
{
Task *parentTask;
if (!parent.isValid()){
qDebug() << "addChild() - parent is not valid";
parentTask = rootItem;
}
else
parentTask = static_cast<Task*>(parent.internalPointer());
Task *childTask = new Task(data, parentTask);
qDebug() << QString::number((int)childTask);///
int childCount = childTask->childCount();
emit beginInsertRows(parent, childCount, childCount);
parentTask->appendChild(childTask);
emit endInsertRows();
//emit dataChanged(parent, parent);
return true;
}
In some situations calling this function from QML makes a mess with Indexes in QML. It start show wrong items or invalid items, especially when Parent has tasks with children (2 levels tree).
What is wrong?
You're indicating the wrong number of children to the model's users.
Instead of int childCount = childTask->childCount();, you should have
int childCount = parentTask->childCount();
Since this is a structural change only, you should never emit the dataChanged signal. The parent's data has not changed. Its structure has. Qt's models discriminate between structural and data changes. The begin.../end... methods indicate structural changes. The only place you should be emitting dataChanged is from a location that has the effects of calling Model::setData on an existing item. See this answer, for example, for details.
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.
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();
}
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.
I'm still struggling with using QListView, I'm trying to select one particular row in the view and I cannot figure out how to do this.
I found a similar question on StackOverflow which recommends using the createIndex() method of the model, however this method is protected (perhaps it used to be public but is not anymore) so that doesn't work for me. Any suggestion?
You can get the index of anything by just calling
QModelIndex indexOfTheCellIWant = model->index(row, column, parentIndex);
Then you can call setCurrentIndex(indexOfTheCellIWant) as bruno said in his answer.
If model contains just a standard list of items as opposed to a tree structure, then it's even easier. Because we can assume that the item is a root item - no parent.
QModelIndex indexOfTheCellIWant = model->index(row, column);
With a tree structure it is a little trickier, because we can't just specify a row and a column, we need to specify these with respect to a parent. If you need to know about this part let me know and I'll explain more.
Only one more thing to note. Selection is based on cells, not really rows. So if you want to ensure that when the user selects a cell (or you do through code) that the whole row is selected you can do that by setting the "selectionBehavior" on the itself.
list->setSelectionBehavior(QAbstractItemView::SelectRows);
You can use QAbstractItemView::setCurrentIndex ( const QModelIndex & index )
Fetch an instance of QModelIndex from the QListView's model and select it:
void selectRowInQListView(int row, QListView *listView) {
QModelIndex index = listView->model()->index(row, 0);
if (index.isValid()) {
//listView->selectionModel()->select(index, QItemSelectionModel::Select);
//listView->selectionModel()->select(index, QItemSelectionModel::Current);
listView->setCurrentIndex(index);
}
}