2

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()
{

}
eyllanesc
  • 235,170
  • 19
  • 170
  • 241
JokerMartini
  • 5,674
  • 9
  • 83
  • 193

3 Answers3

2

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.

rafix07
  • 20,001
  • 3
  • 20
  • 33
  • Your answer is 100% correct about how to solve what the OP asks. I will share another point of view, which does not mean to question if your answer is correct, but if there is a need to subclass QStandardItem at all. – scopchanov Sep 16 '18 at 18:30
  • 1
    @scopchanov Great solution rafi. However I'm curious to see scopchanov answer. Ideally I do not want to make my class bundle have to inherit from qstandarditem model if I dont have to. However j do like the suggestion as an option. I never would have thought of that. – JokerMartini Sep 16 '18 at 18:40
  • 1
    *scopchanov* Yes, I agree with you, *QStandardItem* as base class is not required to solve OP problem. @JokerMartini , *eyllanesc* Put the solution where inheritance was not used. I don't know why he deleted his answer. – rafix07 Sep 16 '18 at 18:45
  • Yeah I never saw his answer. Thanks for the information though. – JokerMartini Sep 16 '18 at 18:51
2

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.

eyllanesc
  • 235,170
  • 19
  • 170
  • 241
2

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();
}
Community
  • 1
  • 1
scopchanov
  • 7,966
  • 10
  • 40
  • 68
  • The reason i was using a struct was because i was eventually going to make it so users could double click and item and edit the various properties like nickname and team name. – JokerMartini Sep 16 '18 at 19:06
  • 1
    @JokerMartini, you can do that with this approach as well. Just use delegates. Post another question about how to display and edit the data of QStandardItem and I will try to give you a solution. Or someone could propose even better one. As you see, the people are willing to help. – scopchanov Sep 16 '18 at 19:09
  • 1
    ok I will maybe do that. I'm going to do some research first and post if im looking for help. thank you – JokerMartini Sep 16 '18 at 19:12