PDA

View Full Version : QAbstractItemModel newbie question



okellogg
16th December 2007, 18:42
Dear Qt masters,

I have the following very simple "model":



typedef QPair<QString,QStringList> EnumDefinition;
typedef QList<EnumDefinition> EnumDefinitionList;
EnumDefinitionList enumTypes;

// ... example:
enumTypes[0].first = "Boolean";
enumTypes[0].second[0] = "False";
enumTypes[0].second[1] = "True";
enumTypes[1].first = "Color";
enumTypes[1].second[0] = "Red";
enumTypes[1].second[1] = "Green";
enumTypes[1].second[2] = "Blue";


Now I would like this to display in a tree view as:

- Boolean
+-- False
+-- True
- Color
+-- Red
+-- Green
+-- Blue

Can this be done using the QAbstractItemModel?
(I am a little confused about the mapping between QModelIndex
and the indexes in my EnumDefinitionList)

Many thanks,

Oliver

jacek
16th December 2007, 18:59
The mapping between model indices and values in your case looks this way:

index( 0, 0, QModelIndex() ) -> Boolean

index( 0, 0, index( 0, 0, QModelIndex() ) ) -> False
index( 1, 0, index( 0, 0, QModelIndex() ) ) -> True

index( 1, 0, QModelIndex() ) -> Color

index( 0, 0, index( 1, 0, QModelIndex() ) ) -> Red
index( 1, 0, index( 1, 0, QModelIndex() ) ) -> Green
index( 2, 0, index( 1, 0, QModelIndex() ) ) -> Blue

The parameters of index() method are respectively row, column and parent index.

okellogg
16th December 2007, 23:39
Thanks Jacek.
I was trying to put the enumTypes as a member variable in my EnumItemModel
(AbstractItemModel subtype) but that didn't work out.
So instead, I use public methods in the EnumItemModel:

void populateModelFromEnumDefs(EnumDefinitionList l);
EnumDefinitionList extractEnumDefs();

that will convert the model info to the external format, EnumDefinitionList.

wysota
17th December 2007, 01:19
Why not wrap the model directly over the "external format" instead of doing conversions?

okellogg
17th December 2007, 05:41
Maybe I gave up too early - but I can't seem to find why this code is not working:



// file: enumitemmodel.h
#ifndef ENUMITEMMODEL_H
#define ENUMITEMMODEL_H

#include <QAbstractItemModel>
#include <QModelIndex>
#include <QVariant>
#include <QPair>
#include <QList>
#include <QStringList>

typedef QPair<QString,QStringList> EnumDefinition;
typedef QList<EnumDefinition> EnumDefinitionList;


class EnumItemModel : public QAbstractItemModel
{
Q_OBJECT

public:
EnumItemModel(QObject *parent = 0);
virtual ~EnumItemModel();

QVariant data(const QModelIndex &index, int role) const;
Qt::ItemFlags flags(const QModelIndex &index) const;
QModelIndex index(int row, int column,
const QModelIndex &parent = QModelIndex()) const;
QModelIndex parent(const QModelIndex &index) const;
int rowCount(const QModelIndex &parent = QModelIndex()) const;
int columnCount(const QModelIndex &parent = QModelIndex()) const;

EnumDefinitionList enumTypes;
private:
void setupModelData();
};
#endif

// file: enumitemmodel.cpp
#include <QtGui>
#include <iostream>

#include "enumitemmodel.h"

EnumItemModel::EnumItemModel(QObject *parent)
: QAbstractItemModel(parent)
{
setupModelData();
}

EnumItemModel::~EnumItemModel()
{
}

int EnumItemModel::columnCount(const QModelIndex &parent) const
{
return 1;
}

QVariant EnumItemModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();

if (role != Qt::DisplayRole)
return QVariant();

const QModelIndex &parentIndex = index.parent();

if (!parentIndex.isValid())
return QVariant(enumTypes[index.row()].first);

int parentRow = parentIndex.row();
return QVariant(enumTypes[parentRow].second[index.row()]);
}

Qt::ItemFlags EnumItemModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return 0;

return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}

QModelIndex EnumItemModel::index(int row, int column, const QModelIndex &parent)
const
{
if (!hasIndex(row, column, parent))
return QModelIndex();

return createIndex(row, column, NULL);
}

QModelIndex EnumItemModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return QModelIndex();

return index.parent();
}
int EnumItemModel::rowCount(const QModelIndex &parent) const
{
if (parent.column() > 0)
return 0;

if (!parent.isValid())
return 0;

return enumTypes[parent.row()].second.count();
}

void EnumItemModel::setupModelData()
{
QStringList booleanValues;
booleanValues.append("False");
booleanValues.append("True");
enumTypes.append(EnumDefinition("Boolean", booleanValues));

QStringList colorValues;
colorValues.append("Red");
colorValues.append("Green");
colorValues.append("Blue");
enumTypes.append(EnumDefinition("Color", colorValues));
}

// file: main.cpp
#include <QtGui>

#include "enumitemmodel.h"
int main(int argc, char *argv[])
{
QApplication app(argc, argv);

EnumItemModel model;

QTreeView view;
view.setModel(&model);
view.setWindowTitle(QObject::tr("Simple Tree Model"));
view.show();
return app.exec();
}

wysota
17th December 2007, 08:10
I don't have much time now, so only quick remarks:

your parent() implementation is invalid - you can't return an index by using QModelIndex::parent() because this method will call the model's parent() method to find the proper parent index
rowCount() will take an invalid model index for top level items, thus row() will return -1 and your rowCount() method will break


Correct these and then we'll continue.

okellogg
17th December 2007, 20:25
Many thanks for replying, wysota.

> your parent() implementation is invalid - you can't return an index by using
> ModelIndex::parent() because this method will call the model's parent() method
> to find the proper parent index

Okay. The problem then is, how do I get to the parent index when only given the
child index.
I resorted to a trick with internalPointer inEnumItemModel::index() :
on a top level item, call createIndex() with (void*)0 as the internalPointer
on a non toplevel item, call createIndex() with (parent.row() + 1) as the internalPointer

I would hope that there's a better way to get the parent index but I can't think of one...

> rowCount() will take an invalid model index for top level items, thus row() will
> return -1 and your rowCount() method will break

I'm not sure I understand that comment.
Here's my modified enumitemmodel.cpp:



#include <QtGui>
#include <iostream>

#include "enumitemmodel.h"

EnumItemModel::EnumItemModel(QObject *parent)
: QAbstractItemModel(parent)
{
setupModelData();
}

EnumItemModel::~EnumItemModel()
{
}

int EnumItemModel::columnCount(const QModelIndex &parent) const
{
return 1;
}

QVariant EnumItemModel::data(const QModelIndex &index, int role) const
{
if (!index.isValid())
return QVariant();

if (role != Qt::DisplayRole)
return QVariant();

const QModelIndex &parentIndex = index.parent();

if (!parentIndex.isValid())
return QVariant(enumTypes[index.row()].first);

int parentRow = parentIndex.row();
return QVariant(enumTypes[parentRow].second[index.row()]);
}

Qt::ItemFlags EnumItemModel::flags(const QModelIndex &index) const
{
if (!index.isValid())
return 0;

return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}

QModelIndex EnumItemModel::index(int row, int column, const QModelIndex &parent)
const
{
if (!hasIndex(row, column, parent))
return QModelIndex();

if (!parent.isValid())
return createIndex(row, column, NULL);

long parentRowPlusOne = (long) parent.row() + 1;
return createIndex(row, column, (void*)parentRowPlusOne);
}

QModelIndex EnumItemModel::parent(const QModelIndex &index) const
{
if (!index.isValid())
return QModelIndex();

long parentRowPlusOne = (long)index.internalPointer();
if (!parentRowPlusOne) // Top level item
return QModelIndex();

return createIndex((int)parentRowPlusOne - 1, 0, NULL);
}

int EnumItemModel::rowCount(const QModelIndex &parent) const
{
if (!parent.isValid())
return 0;

if (parent.column() > 0)
return 0;

//return enumTypes[parent.row()].second.count(); // or perhaps:
long parentRowPlusOne = (long)parent.internalPointer();
if (!parentRowPlusOne)
{
std::cerr << "EnumItemModel::rowCount(row=" << parent.row()
<< "): parentRowPlusOne is 0 ?!?" << std::endl;
return 0;
}
return enumTypes[parentRowPlusOne - 1].second.count();
}

void EnumItemModel::setupModelData()
{
QStringList booleanValues;
booleanValues.append("False");
booleanValues.append("True");
enumTypes.append(EnumDefinition("Boolean", booleanValues));

QStringList colorValues;
colorValues.append("Red");
colorValues.append("Green");
colorValues.append("Blue");
enumTypes.append(EnumDefinition("Color", colorValues));
}

jacek
17th December 2007, 20:50
> rowCount() will take an invalid model index for top level items, thus row() will
> return -1 and your rowCount() method will break

I'm not sure I understand that comment.
"model.rowCount()" (or more precisely: "model.rowCount( QModelIndex() )" ) should return the number of top-level items and you have two such items ("Boolean" and "Color").

okellogg
17th December 2007, 21:10
"model.rowCount()" (or more precisely: "model.rowCount( QModelIndex() )" ) should return the number of top-level items and you have two such items ("Boolean" and "Color").

Yippie!


int EnumItemModel::rowCount(const QModelIndex &parent) const
{
if (!parent.isValid())
return enumTypes.count();
[...]


With that change, I now got the top level items displayed:

- Boolean
- Color

And with the following, I got everything displayed:


int EnumItemModel::rowCount(const QModelIndex &parent) const
{
if (parent.column() > 0)
return 0;
if (!parent.isValid())
return enumTypes.count();
return enumTypes[parent.row()].second.count();
}


Last remaining problem: The leaf items ("False", "True", etc.) have a
"+" symbol and are expandable, and when expanding they show again
the same leaf items, again with "+" ad infinitum. (I'm sure that's an
easy one :)

Thanks again for your help, jacek and wysota.

jacek
17th December 2007, 21:17
Last remaining problem: The leaf items ("False", "True", etc.) have a
"+" symbol and are expandable, and when expanding they show again
the same leaf items, again with "+" ad infinitum. (I'm sure that's an
easy one :)
Try this: http://labs.trolltech.com/page/Projects/Itemview/Modeltest.

okellogg
17th December 2007, 23:10
Just for completeness, the rowCount() was still faulty. Here's the corrected version:


int EnumItemModel::rowCount(const QModelIndex &parent) const
{
if (parent.column() > 0)
return 0;
if (!parent.isValid()) // return total top level items
return enumTypes.count();
long parentRowPlusOne = (long)parent.internalPointer();
if (!parentRowPlusOne) // type name, e.g. "Color"
return enumTypes[parent.row()].second.count();
// leaf item, e.g. "Red"
return 0;
}

wysota
18th December 2007, 00:13
long parentRowPlusOne = (long)index.internalPointer();
How about QModelIndex::internalId() instead?

BTW. a trivial rowCount() implementation:

int Model::rowCount(const QModelIndex &parent) const {
// if parent is invalid, return number of top level items
if(!parent.isValid())
return enumTypes.count();
// if parent's parent is invalid, this is level one - return components' count
if(!parent.parent().isValid() && hasIndex(parent))
return enumTypes[parent.row()].second.count();
// otherwise at least level 2 - none in our case
return 0;
}

No need for internal data and you can continue the descent ad infinitum depending on your needs. (for instance root -> QRect -> QPoint -> (int, int))

rickbsgu
18th December 2007, 10:58
How about QModelIndex::internalId() instead?

BTW. a trivial rowCount() implementation:

int Model::rowCount(const QModelIndex &parent) const {
// if parent is invalid, return number of top level items
if(!parent.isValid())
return enumTypes.count();
// if parent's parent is invalid, this is level one - return components' count
if(!parent.parent().isValid() && hasIndex(parent))
return enumTypes[parent.row()].second.count();
// otherwise at least level 2 - none in our case
return 0;
}

No need for internal data and you can continue the descent ad infinitum depending on your needs. (for instance root -> QRect -> QPoint -> (int, int))

Following along, here - very interesting. Can you explain what the 'hasIndex' call does/verifies in this context?

thanx,
rickb

wysota
18th December 2007, 11:19
Whether parent.row() is within the valid range - [0 - rowCount(parent.parent())-1]. Instead you could use (enumTypes.count()>parent.row() && parent.row()>=0).

ganeshshenoy
18th February 2008, 12:30
Can you please post the updated code for this problem?