How to associate QModelIndex with a new row?
Asked Answered
K

2

7

I've cooked up a QAbstractListModel whose model indexes contain a pointer I absolutely needed in order to process data. I add the data like so:

void PointListModel::addPoint(int frameNumber, QPoint const& pos)
{
    PointItem *pointItem = new PointItem( frameNumber, pos );
    QModelIndex newRow = this->createIndex( m_points.count(), 0, pointItem );

    qDebug() << newRow.internalPointer();

    beginInsertRows( newRow, m_points.count(), m_points.count() );
    m_points.insert( m_points.count( ), pointItem );
    endInsertRows();

    emit pointAdded( pointItem, pos );
}

It was only later that I realized that the argument to beginInsertRows is asking for the parent model index of the new row, not the new row's actual model index.

So, at this point in time, Qt has given me no way of supplying a QModelIndex to associate with this particular row. How do I create my own model index for this new row?

Kt answered 27/1, 2011 at 4:13 Comment(0)
P
6

Okay, I'm rewriting my answer as after some research I've found out that I got it wrong.

You shouldn't do anything special to create a new index when you add new data. You code should look like this:

PointItem *pointItem = new PointItem( frameNumber, pos );
// assume you insert a top level row, having no parent
beginInsertRows( QModelIndex(), m_points.count(), m_points.count() );
m_points.insert( m_points.count( ), pointItem );
endInsertRows();

Then you should implement the index() method which will create indexes on demand and the parent() method which will determine the parent of some index, but since you have a list model, it should probably always just return QModelIndex(). Here is a good article about creating custom models.

Here is a complete example of a working QAbstractListModel:

class MyModel: public QAbstractListModel {
  Q_OBJECT
  public:
    virtual QModelIndex index(int row, int column = 0,
        const QModelIndex &parent = QModelIndex()) const;
    virtual int rowCount(const QModelIndex &parent = QModelIndex()) const;
    virtual QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;
    void add(int i);
  private:
    QList<int> list;
};

void MyModel::add(int i)
{
  beginInsertRows(QModelIndex(), list.size(), list.size());
  list.append(i);
  endInsertRows();
}

QModelIndex MyModel::index(int row, int column,
        const QModelIndex &parent) const
{
  return hasIndex(row, column, parent) ? createIndex(row, column, (void*)&list[row])
    : QModelIndex();
}

int MyModel::rowCount(const QModelIndex &parent) const
{
  if (parent.isValid())
    return 0;
  return list.size();
}

QVariant MyModel::data(const QModelIndex &index,
    int role) const
{
  if (!index.isValid())
    return QVariant();
  if (role != Qt::DisplayRole)
    return QVariant();
  return QVariant(QString::number(*static_cast<int*>(index.internalPointer())));
}
Paltry answered 27/1, 2011 at 7:1 Comment(16)
This looks like it's getting closer to what I want, but from what I understand, the index() function isn't exclusively called for the creation of new rows. How do I differentiate between an index() call between a new row and an extant one? I have a QMap storing my data, but the QMap key is not going by row number at the moment.Kt
@nessup, the default QAbstractListModel::index() implementation always calls createIndex() for any valid coordinates so I guess it's supposed to create a new index for each call, although it seems strange to me too. Maybe index creation operation is considered inexpensive so it's perfectly fine to create multiple duplicate indexes. After all, your implementation will be the same as the default one with the only difference that you pass something meaningful as the data pointer to createIndex() instead of NULL (as the default implementation does).Paltry
Okay, thanks. I am going to attempt keeping a QMap<int /*row*/, QModelIndex> in my code and returning a new QModelIndex if the QMap does not contain the appropriate QModelIndex at a given row. I'll get back to you on how it goes.Kt
Hey--looks like index() is never called before data(), and by then the QModelIndex's internal pointer is zero :/Kt
@nessup, okay, I'll try to test it too and post the results. But I really don't think that a map is necessary.Paltry
@Sergey, neither do I. I've also tried accessing the parent QModelIndex passed to the data() in order to get the function pointer, but that too is zero! It's almost as though Qt is ignoring entirely the parent QModelIndex arg that I passed to beginInsertRows() in my addPoint() function.Kt
@nessup, I think the parent QModelIndex() should always be an invalid one for a list model as it doesn't support any hierarchy. If you really want to have parents, you need to reimplement parent(). Now, I have tested a simple model implementing index() and data(), and index() is correctly called, and I can access the internal pointer in the data(). I can't get QListView to display it, though, but that's probably because I'm doing something stupid that's irrelevant to models. Are you sure you've reimplemented index()? Perhaps you got the signature wrong (omitted const for example)?Paltry
@Sergey, well, this is getting even more interesting. Yeah, I did add 'const' and that seemed to have fixed it, but, just as you are now experiencing, nothing ever appears in the list! I am at this point clueless and somewhat angry that it is so difficult to do something that should be so easy in Qt.Kt
@nessup, aha, looks like it's the same problem I solved around 15 seconds ago. I forgot to check the role argument in the data(), so I was returning the same thing for all roles. But among them there is a "size hint", and when I returned some data for it, it apparently got interpreted as zero size, so it actually got displayed, but was invisible due to zero size. The solution is to only return meaningful data for Qt::DisplayRole and QVariant() for everything else (unless you really want other roles such as tool tips).Paltry
@Sergey, I'm afraid that that's not my issue :/ I've had an if statement returning QVariant() on anything that's not DisplayRole. Bleh. BTW, do you have IM?Kt
@nessup, does data() get called, and do you return a QString in a QVariant for Qt::DisplayRole? Do you return correct rowCount()?Paltry
@nessup, I've got Skype (alqualos), it can be used as IM as well, but it's getting terribly late here so I'm going to sleep now.Paltry
@nessup, see my edit for a working example. Perhaps it will give you some idea on what you're doing wrong. And I certainly don't think that learning model/view technique is easy in Qt (as well as in any other framework I'm aware of). The abstraction level is too high for it to be easy. But this is the price of its power, and once you get a hang of it, it becomes much easier.Paltry
@Sergey, thank you so much for your help. My error had to do with how I was storing information in my data structures (specifically, storing them with the wrong key values.) But yes, the example you posted above is absolutely FANTASTIC, and I wish it were readily available for any aspiring Qt model developer. It's a great struggle, but with time the payoff is certain. Again, thank you so much for your help. Might contact you on Skype someday (nessup) if I ever get it installed on this machine :)Kt
@nessup, to be fair, this example was made based on the simpletreemodel example that comes with Qt by removing unnecessary stuff that was relevant to trees only. So it is available to any developer, in a sense.Paltry
@nessup, also I don't usually run Skype or any kind of IM. Right now things are different because I'm away from home, but if you ever want to contact me the most reliable way is to use e-mail: [email protected]Paltry
C
2

I've cooked up a QAbstractListModel whose model indexes contain a pointer I absolutely needed in order to process data.

If you start with wrong requirements, you end up with wrong solutions :)

A list model is simple enough so that you don't need more than the QModelIndex's row() to uniquely define the data the index addresses.

So, given a QModelIndex mi, when you before did

PointItem * item = static_cast<PointItem*>(mi.internalPointer());

you can instead do

PointItem * item = plm->pointItemFromIndex(mi);

where plm is your PointListModel. If you don't have a pointer to it lying around when you need to access the PointItem, you can reconstruct it like this:

PointItemModel * plm = qobject_cast<PointItemModel*>(mi.model());
// check for !plm here (!mi.isValid() || qobject_cast fails)

In turn, PointListMode::pointItemFromIndex() would do the actual work:

PointItem * PointListMode::pointItemFromindex(const QModelIndex &mi) const {
    return mi.isValid() ? m_points[mi.row()] : 0 ;
}

This is the most important thing to realize when working with QAbstractListModel in Qt: Mentally replace QModelIndex with int row, ignore everything else it has (an invalid QModelIndex has row() == -1).

Same for QAbstractTableModel: mentally reduce the QModelIndex to int row, int column. Forget everything else.

The only time you need the full QModelIndex (including its internalPointer() or internalId() is when you implement a tree model (QAbstractItemModel).

Chare answered 24/6, 2012 at 8:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.