PDA

View Full Version : Help with getting my custom model working



thomaspu
28th July 2007, 17:58
Greetings,

I've been reading over Qt's model/view stuff overt the last week and have been looking at examples and such. I've been trying to get my custom model up and running. The problem I have is that I'm not sure how to set the data. The class expects the data to come in as a QVariant. Well, how would I send it data if the data consisted of a struct?

So If I had part of the model defined like this:
class ConnectionListModel public QAbstractTableModel{
//...
private:
struct connectionData {
QString name;
QString hostAddress;
QString username;
QString password;
int port;
sessionTypesEnum sessionType;
};
QList<customType> _data;
public:
void init_data();
};

Now I'm really unsure of how to do my setData function. I had this:
bool ConnectionListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.isValid() && role == Qt::DisplayRole)
{
int row = index.row();
//Lets cast our QVariant so we can get the data out
connectionData newData = qvariant_cast<connectionData>( value );

connectionList[row].name = newData.name;
connectionList[row].hostAddress = newData.hostAddress;
connectionList[row].username = newData.username;
connectionList[row].password = newData.password;
connectionList[row].port = newData.port;
connectionList[row].sessionType = newData.sessionType;

emit dataChanged(index, index);
return true;
}
return false;
}
But the cast doesn't work since QVariant doesn't know what my connectionData object is. How do I set it?

Thanks,
Paul

fullmetalcoder
28th July 2007, 18:19
You can either register your structure to the meta object system (through Q_DECLARE_METATYPE if I remember well) or (recommended) use roles. Assign a role (integer value, higher than Qt::UserRole) to each member of your structure so that you can set/get properties one at a time which fits better into the model view infrastructure...

Also, I believe that what you're trying to do is having a collection of connectionData structure inside your model, each one representing a row. Is that true? In this case you must not do it as you tried but instead setup an underlying data structure (e.g. a list of connection data) without using setData() and using them to feed the view through data()...

jacek
28th July 2007, 18:19
I would represent every field as a column and add two convenience methods that would operate on whole structs:
class ConnectionListModel : public QAbstractTableModel
{
...
enum Column
{
Name,
HostAddress,
Port,
Username,
Password,
SessionType
};
...
// below index points to a certain field in certain connection
bool setData( const QModelIndex & index, const QVariant & value, int role );
...
bool setConnection( int row, const ConnectionData & conn );
const ConnectionData & connection( int row ) const;
...
};

thomaspu
28th July 2007, 19:15
Also, I believe that what you're trying to do is having a collection of connectionData structure inside your model, each one representing a row. Is that true? In this case you must not do it as you tried but instead setup an underlying data structure (e.g. a list of connection data) without using setData() and using them to feed the view through data()...
Yes, I was going for a list of items stored in a QList and each item in the QList represents an item in my ListView. This is what I currently have:

class ConnectionListModel : public QAbstractListModel
{
Q_OBJECT

enum sessionTypesEnum {
secureShell,
remoteFileBrowser
};

struct connectionData {
QString name;
QString hostAddress;
QString username;
QString password;
int port;
sessionTypesEnum sessionType;
};

public:
ConnectionListModel(QObject *parent = 0);

~ConnectionListModel();

//Required items to make this model work
int rowCount(const QModelIndex &parent = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;

//required items to be able to edit items
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
Qt::ItemFlags flags(const QModelIndex &index) const;


//Required to be able to resize the model (add & remove shit)
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex());
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());

private:
QList<connectionData> connectionList;
};

ConnectionListModel::ConnectionListModel(QObject *parent)
: QAbstractListModel(parent)
{
}


ConnectionListModel::~ConnectionListModel()
{
}

int ConnectionListModel::rowCount(const QModelIndex &parent) const
{
return connectionList.count();
}

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

return QAbstractItemModel::flags(index) | Qt::ItemIsEditable;
}

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

int row = index.row();
if ( row < 0 || row >= connectionList.count() ) //Is the row invalid?
return QVariant();

if (role == Qt::DisplayRole)
{
switch( index.column() ){
case 0: return connectionList[row].name;
case 1: return connectionList[row].hostAddress;
case 2: return connectionList[row].username;
case 3: return connectionList[row].password;
case 4: return connectionList[row].port;
case 5: return connectionList[row].sessionType;
return QVariant();
}
}
else
return QVariant();
}

bool ConnectionListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.isValid() && role == Qt::DisplayRole)
{
int row = index.row();
//Lets cast our QVariant so we can get the data out

const connectionData *newData = qvariant_cast<connectionData>( value );

//get a pointer to the list item so we can change its contents
connectionList[row].name = newData.name;
connectionList[row].hostAddress = newData.hostAddress;
connectionList[row].username = newData.username;
connectionList[row].password = newData.password;
connectionList[row].port = newData.port;
connectionList[row].sessionType = newData.sessionType;

emit dataChanged(index, index);
return true;
}
return false;
}


bool ConnectionListModel::insertRows(int row, int count, const QModelIndex &parent)
{
beginInsertRows(parent, row, row +count-1);
//Not finished!
endInsertRows();
}

bool ConnectionListModel::removeRows(int row, int count, const QModelIndex &parent)
{
beginRemoveRows(parent, row, row+count+1);
//Not finished!
endRemoveRows();
}
The problem that I have though is that the qvariant_cast<connectionData> doesn't work. I'm not really sure how to pass in the connectionData object using a QVariant.

Paul

jacek
28th July 2007, 20:37
The problem that I have though is that the qvariant_cast<connectionData> doesn't work. I'm not really sure how to pass in the connectionData object using a QVariant.
As fullmetalcoder wrote, use Q_DECLARE_METATYPE (http://doc.trolltech.com/4.3/qmetatype.html#Q_DECLARE_METATYPE).

thomaspu
28th July 2007, 21:18
OK, I declared my custom datatype outside the class and now that seems to work so that I can set items that are in my QList<connectionData> list.

class ConnectionListModel : public QAbstractListModel
{
Q_OBJECT

public:
struct connectionData {
QString name;
QString hostAddress;
QString username;
QString password;
int port;
sessionTypesEnum sessionType;
};

ConnectionListModel(QObject *parent = 0);
~ConnectionListModel();

private:
QList<connectionData> connectionList;
};
Q_DECLARE_METATYPE(ConnectionListModel::connection Data);
But now I'm lost as to how to add data to it. I understand that I have to do my own insertRows function. Is the purpose of the insertRows function just to insert a row, but not add the data to it?

In other words, to insert a row, I'd be expected to do something like this to the model:

conenctionData addMe;
//set items in addMe
myModel.insertRows(1, 1, QModelIndex ????); // <-what do i do here?
myModel.setData(QModelIndex ???, addMe);

Paul

jacek
28th July 2007, 23:04
myModel.insertRows(1, 1, QModelIndex ????); // <-what do i do here?
You can use the default value for the last parameter (i.e. QModelIndex()). Your items don't have a parent, since you have a flat list. If you had a tree, then you would have to specify it to add items below the root.


myModel.setData(QModelIndex ???, addMe);
Here you need a model index that points to that empty row, you have added in previous line. You can get it using the index() method:
myModel.setData( myModel.index( 0 ), addMe);

thomaspu
28th July 2007, 23:23
I'm making slow progress here. Understanding this stuff is frustrating. Now I can put stuff in the model (I think) but it doesn't show up in my associated view. I think I've got the setData() dataChanged signal all messed up. It keeps giving me comipler errors.


bool ConnectionListModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
if (index.isValid() && role == Qt::DisplayRole)
{
int row = index.row();
//Lets cast our QVariant so we can get the data out

connectionData newData = value.value<connectionData>();

//get a pointer to the list item so we can change its contents
connectionList[row].name = newData.name;
connectionList[row].hostAddress = newData.hostAddress;
connectionList[row].username = newData.username;
connectionList[row].password = newData.password;
connectionList[row].port = newData.port;
connectionList[row].sessionType = newData.sessionType;

emit dataChanged( index(row, 0), index(row, 0));
return true;
}
return false;
}

What am I doing wrong now?
Paul

thomaspu
29th July 2007, 01:00
Alright, I was able to fix my compile problems by changing the dataChanged signal call to
emit dataChanged( createIndex(row, 0), createIndex(row, 0));Now the problem that I have is when I add an item to my model, it doesn't show up in the view. I'm

So I'm adding in the data to my list model class like this:
ConnectionListModel::connectionData addMe;

m_pSessionModel->insertRows(0, 1);
m_pSessionModel->setData( m_pSessionModel->index(0), &addMe);Adn in my list model, I'm adding the new row through the insertRows:
bool ConnectionListModel::insertRows(int row, int count, const QModelIndex &parent)
{
beginInsertRows(parent, row, row +count-1);

//Let's add a new connectionData item to our list
connectionData newItem;
connectionList.insert(row, newItem);

endInsertRows();
emit dataChanged( createIndex(row, 0), createIndex(row, 0));
printf("There are now %d items in the list\n", connectionList.count() );
return true;
}

I thought that thats all I needed to do. Any ideas?
Paul

fullmetalcoder
29th July 2007, 08:21
You need to reimplement some more functions :


ConnectionListModel::rowCount(QModelIndex &idx)
{
return idx.isValid() ? 0 : connectionList.count();
}

QVariant ConnectionListModel::data(QModelIndex &idx, int role) const
{
if ( role == Qt::DisplayRole )
return idx.isValid() ? connectionList.at(idx.row()).<field corresponding to column value> : <header data>;
// maybe return other values for other fileds (e.g. Qt::DecorationRole)
return QVariant();
}

thomaspu
29th July 2007, 15:52
Thanks for the replies, cause I've really been struggling with this. I think I have reimplemented all needed functions. Here's whats in my header file so far and that I have defined in my source file.
ConnectionListModel(QObject *parent = 0);
~ConnectionListModel();

//Required items to make this model work
int rowCount(const QModelIndex &index = QModelIndex()) const;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const;

//required items to be able to edit items
bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
Qt::ItemFlags flags(const QModelIndex &index) const;

//Required to be able to resize the model (add & remove shit)
bool insertRows(int row, int count, const QModelIndex &parent = QModelIndex());
bool removeRows(int row, int count, const QModelIndex &parent = QModelIndex());

private:
QList<connectionData> connectionList;One thing that I noticed when stepping through my code while trying to add an item was that I'm never calling my setData funciton with a valid index, so it always skips over the code and never emits the signal that the data changed.

So now my question is how do I get a valid index? I thought that calling index(0) would give me the index for the item that I just inserted at the front of the list, but that isn't the case.

Here's the calling code
//where m_pSessoinModel is a connectionlistmodel
ConnectionListModel::connectionData addMe;

m_pSessionModel->insertRows(0, 1); //inserting 1 row before row #0
m_pSessionModel->setData( m_pSessionModel->index(0), &addMe);

Any ideas? The examples online are not very helpful here.
Paul

fullmetalcoder
29th July 2007, 16:00
So now my question is how do I get a valid index? I thought that calling index(0) would give me the index for the item that I just inserted at the front of the list, but that isn't the case.
index(0) shouldn't even compile AFAIK... it should be index(0, 0) (or index(0, 1) and so on...). An index obtained through the index() method represents a single cell, not a whole row...

besides, your header is slightly wrong... The default role for the setData() function should be Qt::DisplayRole as well or am I missing something?

thomaspu
29th July 2007, 16:16
The QAbstractListModel defines a default for the column:
virtual QModelIndex index ( int row, int column = 0, const QModelIndex & parent = QModelIndex() ) constBut when do you ever get a valid QModelIndex? When I'm calling the insertRows function, I don't know the parent at all. The description in the function reads
Returns the index of the data in row and column with parent.So If I never have the parent... thats probably why it doesn't work?

Yeah, the role should have just been Qt::DisplayRole

Paul

fullmetalcoder
29th July 2007, 16:47
When I'm calling the insertRows function, I don't know the parent at all. The description in the function reads So If I never have the parent... thats probably why it doesn't work?l
The point is that you never HAVE any parents anywhere since you're using a table and not a hierarchical (i.e. tree) model. When no parent is involved the parent is the header node which is represented by an invalid parent (which is the default parent passed to all methods that require a parent BTW... ;))

thomaspu
29th July 2007, 17:09
Ok, that makes sense since this is just a flat model. So any ideas why nothing shows up on my QListView after I add an item to my model?

Paul

fullmetalcoder
29th July 2007, 17:15
May I ask for the full code?

thomaspu
29th July 2007, 17:44
Sure, let me put it together. Just a few min...

thomaspu
29th July 2007, 17:55
Ok, all you need is attached.

Paul

fullmetalcoder
29th July 2007, 18:26
Ok I found the bug... You messed up the rowCount() function... The point in this one is to return the number of connections when the parent passed is the root node (so an invalid index) and to return 0 (no child) when the parent is valid (i.e. one of the connection...)

Note that before I found that I added a columnCount() function so you may also need to add it so as to have your model working properly...

thomaspu
29th July 2007, 18:35
Ah, yes, that was it! Yeah, you return the number of connections when the parent is invalid. Which in my case is always cause my list is flat and doesn't have a parent.

Thanks for all the help, I think I'm good to go for a bit!
Paul