Search code examples
c++qtqstandarditemmodelqstandarditem

QStandardItemModel & binding to custom object


I tried to use the cities-standarditem of the Qt exemple and adapt it to my exemple. I have a strange result:

Here is my User class:

class User{
public:
User();

QString getFirstname() const;
void setFirstname(const QString &value);

QString getLastname() const;
void setLastname(const QString &value);

int getAge() const;
void setAge(int value);

private:
QString firstname;
QString lastname;
int age;
};

and i have declared a usermodel.h:

class UserModel: public QStandardItemModel
{
    Q_OBJECT
public:
    UserModel(QList<User> users, QObject *parent = Q_NULLPTR);

    QHash<int, QByteArray> roleNames() const;
};

And here is the implementations of the constructor and the roleNames functions:

enum ItemRoles {
    FirstnameRole,
    LastnameRole,
    AgeRole,
};


UserModel::UserModel(QList<User> users, QObject *parent) : QStandardItemModel(parent)
{    

    //this->setItemRoleNames(roleNames());
    this->setColumnCount(3);
    for (auto user: users) {
        QStandardItem *item = new QStandardItem();
        item->setData(user.getFirstname(), FirstnameRole);
        item->setData(user.getLastname(), LastnameRole);
        item->setData(user.getAge(), AgeRole);
        appendRow(item);
    }

    setSortRole(FirstnameRole);
}


QHash<int, QByteArray> UserModel::roleNames() const
{
    QHash<int, QByteArray> mapping = QStandardItemModel::roleNames();

    mapping[FirstnameRole] = "firstname";
    mapping[LastnameRole] = "lastname";
    mapping[AgeRole] = "age";

    return mapping;
}

My table view only show the last Role added with the function: item->setData(user.getFirstname(), FirstnameRole);

If its the age lastly added, its the age showed... Any clues ?


Solution

  • Let's say you really need a custom model and want to extend an existing one. Since your data is in tabular fashion, I'd suggest to use QAbstractTableModel as the base class.

    So let's have this class:

    class UserModel: public QAbstractTableModel
    {
        Q_OBJECT
        QList<User> _users;
    public:
        UserModel(QList<User> users, QObject *parent = Q_NULLPTR) : QAbstractTableModel(parent), _users(users){}
    

    As you can see, the class itself stores a list of users, given at construction time. The constructor itself does nothing but copy-initializing the list.

    Then you need to supply these implementation, at least:

    int rowCount(const QModelIndex &) const override
    {
        return _users.size();
    }
    int columnCount(const QModelIndex &) const override
    {
        return 3;
    }
    QVariant data(const QModelIndex &index, int role) const override
    {
        if(role == Qt::DisplayRole)
        {
            User user = _users.at(index.row());
            QVariant data[] = { user.getFirstname(), user.getLastname() , user.getAge() };
            return data[index.column()];
        }
        return {};
    }
    

    While columnCount is constant and always returns 3, rowCount will return the number of items in the list of users. In data implementation, the index passed is inspected and a value is returned, according to the index row and column, and the passed role. It's important to understand that when the view calls data passing a role equal to Qt::DisplayRole, the function should return the very data that will be shown in the cell at (index.row(), index.column()), in our case: one of the three User's data members.

    It's quite useful to reimplement the sort function as well, i.e.

    void sort(int column, Qt::SortOrder order) override
    {
        auto fnSort = [](const User & u1, const User & u2){ return u1.getFirstname() < u2.getFirstname(); };
        auto lnSort = [](const User & u1, const User & u2){ return u1.getLastname() < u2.getLastname(); };
        auto agSort = [](const User & u1, const User & u2){ return u1.getAge() < u2.getAge(); };
    
        std::function<bool (const User &, const User &)>  sortFn[] = {fnSort, lnSort, agSort};
        std::sort(_users.begin(), _users.end(), sortFn[column]);
    
        if(order == Qt::DescendingOrder)
        {
            std::reverse(_users.begin(), _users.end());
        }
    }
    

    This way you let the user sort by column, as expected:

    myTableView->setModel(new UserModel(list));
    myTableView->model()->sort(2, Qt::DescendingOrder); //sort by first age, in descending order
    

    If, for some reason, you want to use custom roles, please have your enum be like this:

    enum ItemRoles {
        FirstnameRole = Qt::UserRole,
        LastnameRole,
        AgeRole,
    };
    

    Starting from Qt::UserRole (which is there for this very purpose) ensures that your roles don't clash whit builtin roles.

    Please notice that the above code is meant to suggest a possible solution and it's not the solution itself (and lacks many important features like bounds checking and memory management).