JokerMartini
JokerMartini

Reputation: 6147

Append Class to QStandardItemModel

How can I append my items of class BundleItem to the QListView's QStandardItem model? When they are appended, I only want to use the BundleItem's Name property to be displayed in the listview. I would like a pointer to the actual item to live in the UserRole of the model so when a user double clicks and item in the list, for now it would just print to the debugger console or something similar.

#include "mainwindow.h"
#include <QVBoxLayout>
#include <QListView>
#include <QSortFilterProxyModel>
#include <QStandardItemModel>
#include <QAbstractItemModel>

struct BundleItem {
  QString name;
  QString nickname;
  QString team;

  // Constructor
  BundleItem(QString name,
             QString nickname,
             QString team):
      name(name), nickname(nickname), team(team)
  {}
};

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    auto *proxyModel = new QSortFilterProxyModel;
    proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);

    auto *widget = new QWidget(this);
    auto *lay = new QVBoxLayout(widget);
    auto *listview = new QListView();

    auto *model = new QStandardItemModel();
    proxyModel->setSourceModel(model);
    listview->setModel(proxyModel);

    // add Item to list
    BundleItem("Kevin", "Kev", "Coyotes");
    BundleItem("Michael", "Mike", "Walkers");

    lay->addWidget(listview);
    setCentralWidget(widget);
}

MainWindow::~MainWindow()
{

}

Upvotes: 2

Views: 816

Answers (3)

scopchanov
scopchanov

Reputation: 8399

Solution

The answers from @rafix07 and @eyllanesc are both 100% correct and solve the problem you have asked about.

However, I will allow myself the liberty to suggest a different direction in your design, because:

In the case you have presented, you do not actually need to create your own structure, i.e. BundleItem, and subclass QStandardItem for that matter.

QStandardItem itself provides enough functionality to suit your needs. Just use QStandardItem::data and QStandardItem::setData.

Note: For your convenience you could create an enum with the meaning of each data, e.g. to use something like ItemTeam instead of Qt::UserRole + 1.

Example

Here is an example I have prepared for you to demonstrate the approach I have suggested:

MainWindow.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>

class QStandardItem;

class MainWindow : public QMainWindow
{
    enum DataType : int {
        ItemName = Qt::DisplayRole,
        ItemNickName = Qt::UserRole,
        ItemTeam
    };

    Q_OBJECT
public:
    explicit MainWindow(QWidget *parent = nullptr);

    QStandardItem *createItem(QString name, QString nickname, QString team);

private slots:
    void onDoubleCLicked(const QModelIndex &index);
};

#endif // MAINWINDOW_H

MainWindow.cpp

#include "MainWindow.h"
#include <QStandardItem>
#include <QSortFilterProxyModel>
#include <QBoxLayout>
#include <QListView>
#include <QDebug>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent)
{
    auto *proxyModel = new QSortFilterProxyModel;
    proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);

    auto *widget = new QWidget(this);
    auto *lay = new QVBoxLayout(widget);
    auto *listview = new QListView();

    auto *model = new QStandardItemModel();
    proxyModel->setSourceModel(model);
    listview->setModel(proxyModel);

    // add Item to list
    model->appendRow(createItem("Kevin", "Kev", "Coyotes"));
    model->appendRow(createItem("Michael", "Mike", "Walkers"));

    lay->addWidget(listview);
    setCentralWidget(widget);

    connect(listview, &QListView::doubleClicked, this, &MainWindow::onDoubleCLicked);
}

QStandardItem *MainWindow::createItem(QString name, QString nickname, QString team)
{
    auto *item = new QStandardItem(name);

    item->setData(nickname, ItemNickName);
    item->setData(team, ItemTeam);

    return item;
}

void MainWindow::onDoubleCLicked(const QModelIndex &index)
{
    if (index.isValid())
        qDebug() << index.data(ItemName).toString() << index.data(ItemNickName).toString() << index.data(ItemTeam).toString();
}

Upvotes: 2

eyllanesc
eyllanesc

Reputation: 243897

It is not necessary to use a pointer, you can save only one copy but it must be possible to convert it to QVariant. So that your structure can be converted to QVariant you must use the Q_DECLARE_METATYPE macro and have a default constructor. I have also added the possibility of using QDebug directly with its structure.

*.h

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
class QListView;
class QStandardItemModel;
class QSortFilterProxyModel;

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
private slots:
    void onDoubleClicked(const QModelIndex & index);
private:
    QListView *listview;
    QStandardItemModel *model;
    QSortFilterProxyModel *proxyModel;
    QWidget *widget;
};

#endif // MAINWINDOW_H

*.cpp

#include "mainwindow.h"

#include <QListView>
#include <QSortFilterProxyModel>
#include <QStandardItemModel>
#include <QVBoxLayout>

#include <QDebug>

struct BundleItem {
    QString name;
    QString nickname;
    QString team;

    // Constructor
    BundleItem() = default;
    BundleItem(const QString & name,
               const QString & nickname,
               const QString & team):
        name(name), nickname(nickname), team(team)
    {}
};
Q_DECLARE_METATYPE(BundleItem)

QDebug operator<<(QDebug debug, const BundleItem &b)
{
    QDebugStateSaver saver(debug);
    debug.nospace() << '(' << b.name << ", " << b.nickname << ", "<< b.team <<')';
    return debug;
}

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
{
    proxyModel = new QSortFilterProxyModel(this);
    proxyModel->setSortCaseSensitivity(Qt::CaseInsensitive);

    widget = new QWidget(this);
    auto lay = new QVBoxLayout(widget);
    listview = new QListView();

    model = new QStandardItemModel();
    proxyModel->setSourceModel(model);
    listview->setModel(proxyModel);

    // add Item to list
    BundleItem item1("Kevin", "Kev", "Coyotes");
    BundleItem item2("Michael", "Mike", "Walkers");

    for(const BundleItem & e : {item1, item2}){
        QStandardItem *it = new QStandardItem(e.name);
        it->setData(QVariant::fromValue(e));
        model->appendRow(it);
    }
    connect(listview, &QListView::doubleClicked, this, &MainWindow::onDoubleClicked);
    lay->addWidget(listview);
    setCentralWidget(widget);
}

MainWindow::~MainWindow()
{

}

void MainWindow::onDoubleClicked(const QModelIndex &index)
{
    QVariant v = proxyModel->data(index, Qt::UserRole+1);
    BundleItem b = v.value<BundleItem>();
    qDebug()<< b;
}

The advantage of using a copy is that you will not have to handle memory and therefore fewer problems. When using a proxy it is not necessary to access the source model, so to access that data you can do it from the proxy model. On the other hand if you are going to pass QString as an argument of a function or method and it will not modify it better, pass it as const QString &, the reason I leave it as a task for you.

Upvotes: 2

rafix07
rafix07

Reputation: 20918

The easy version to do it is inheritance from QStandardItem class.

struct BundleItem : public QStandardItem {
  QString name;
  QString nickname;
  QString team;

  BundleItem(QString name,
             QString nickname,
             QString team):
      QStandardItem(name), // call constructor of base class - name will be displayed in listview
      name(name), nickname(nickname), team(team)
  {}
};

Remember to call QStandardItem constructor in ctor of BundleItem and passing name as its parameter.

Lines to add your items to ListView are:

model->appendRow (new BundleItem("Kevin", "Kev", "Coyotes"));
model->appendRow (new BundleItem("Michael", "Mike", "Walkers"));

and it is all, you have listview filled by two items : Kevin and Michael.

If you want to handle double-click action, you can use in your slot

handleDoubleClick (const QModelIndex&); // declaration of your slot

method QStandardItemModel::indexFromItem(const QModelIndex&) which takes QModelIndex (pass index from slot) as parameter and returns pointer to QStandardItem. You need only to cast this pointer to BundleItem then you have access to other members of your class.

Upvotes: 2

Related Questions