4

I created model for QComboBox:

#ifndef QCOMBOBOXMODEL_H
#define QCOMBOBOXMODEL_H

#include <QModelIndex>


class QComboBoxModel : public QAbstractListModel
{
public:
    QComboBoxModel(QObject *parent=nullptr);
    int rowCount(const QModelIndex &) const;
    QVariant data(const QModelIndex &index, int role) const;
    void populate(const QList<QPair<int,QString>> &values);

private:
    QList<QPair<int,QString>> values;
};

#endif // QCOMBOBOXMODEL_H

code

#include "qcomboboxmodel.h"

#include <QModelIndex>

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

int QComboBoxModel::rowCount(const QModelIndex &) const
{
    return values.count();
}


QVariant QComboBoxModel::data( const QModelIndex &index, int role ) const
{        

    QVariant value;

        switch ( role )
        {
            case Qt::DisplayRole: //string
            {
                value = this->values.value(index.row()).second;
            }
            break;

            case Qt::UserRole: //data
            {
            value = this->values.value(index.row()).first;
            }
            break;

            default:
                break;
        }

    return value;
}

void QComboBoxModel::populate(const QList<QPair<int,QString>> &values)
{
    this->values = values;
}

Now i use it

    values.append(QPair<int,QString>(-1,"Select item"));
    values.append(QPair<int,QString>(10,"item1(0)"));
    values.append(QPair<int,QString>(11,"item1(1)"));
    values.append(QPair<int,QString>(21,"item1(2)"));
    values.append(QPair<int,QString>(32,"item1(3)"));
    values.append(QPair<int,QString>(44,"item1(4)"));

    newidx = 50;


    model = new QComboBoxModel();
    model->populate(values);
    this->ui->comboBox->setModel(model);

and on button click i add new item to combobox

newidx++;
QString strIdx = QString().number(newidx);
values.append(QPair<int,QString>(newidx,"New item("+strIdx+")"));

model = new QComboBoxModel();
model->populate(values);
this->ui->comboBox->setModel(model);

Its all seems works just fine, but problem here that i need to recreate model every time i add new item to combobox data

model = new QComboBoxModel();
model->populate(values);
this->ui->comboBox->setModel(model);

Is that a proper way to do so? Or there are another way to force model update combobox when data updated?

scopchanov
  • 7,966
  • 10
  • 40
  • 68
Vasilij Altunin
  • 754
  • 1
  • 5
  • 18

3 Answers3

3

According to "Model Subclassing Reference" in the documentation you have to do many more things to make an editable model. Is there a reason why you don't use a ready made model like QStandardItemModel?

    comboModel = new QStandardItemModel(0, 2, this);
    ui->comboBox1->setModel(comboModel);
    comboModel->insertRow(0);
    comboModel->setData(comboModel->index(0, 0), -1);
    comboModel->setData(comboModel->index(0, 1), "Select item");
        //and so on
    //and the data is available as
    int number = comboModel->data(comboModel->index(0, 0)).toInt();
    QString itemtext = comboModel->data(comboModel->index(0, 1)).toString();
Steve J
  • 59
  • 4
1

I find solution!

First i add new method to model

void QComboBoxModel::append(int index, QString value)
{

    int newRow = this->values.count();

    this->beginInsertRows(QModelIndex(), newRow, newRow);

        values.append(QPair<int,QString>(index,value));

    endInsertRows();
}

Now on button click method changed to this

void MainWindow::on_pushButton_clicked()
{
    qDebug() << "Clicked!";

    newidx++;
    QString strIdx = QString().number(newidx);

    model->append(newidx,"new item " + strIdx );
}

Point is to use beginInsertRows and endInsertRows to notify model that data actually changed!

Now all worked as expected!

Now you also can modify append method to batch add rows to it, but i think if you add many rows it better just recreate model and reassign it to combobox.

Update 1:

Also keep in mind, that you update values QList inside model, so if you add

qDebug() << values;

in on_pushButton_clicked() method, you always see

(QPair(-1,"Select item"), QPair(10,"item1(0)"), QPair(11,"item1(1)"), QPair(21,"item1(2)"), QPair(32,"item1(3)"), QPair(44,"item1(4)"))

Update 2:

Also i update populate method

void QComboBoxModel::populate(const QList<QPair<int,QString>> &newValues)
{
    int oldIdx = this->values.count();
    int newIdx = newValues.count();
    this->beginInsertRows(QModelIndex(), oldIdx, newIdx);
        this->values = newValues;
    endInsertRows();
}

Now you can just work with values list

void MainWindow::on_pushButton_clicked()
{
    qDebug() << "Clicked!";

    newidx++;
    QString strIdx = QString().number(newidx);

    values.append(QPair<int,QString>(newidx,"new item " + strIdx));
    model->populate(values);

    qDebug() << values;
}

Update 3:

Now, i find one big problem - i did not use pointer inside model, so when i pass QList to model it just create copy instead use already created, so i rewrite model and other code:

Model

#ifndef QCOMBOBOXMODEL_H
#define QCOMBOBOXMODEL_H

#include <QModelIndex>


class QComboBoxModel : public QAbstractListModel
{
public:
    QComboBoxModel(QObject *parent=nullptr);
    int rowCount(const QModelIndex &) const;
    QVariant data(const QModelIndex &index, int role) const;
    void populate(QList<QPair<int,QString>> *newValues);
    void append(int index, QString value);

private:
    QList<QPair<int,QString>> *values;
};

#endif // QCOMBOBOXMODEL_H

#include "qcomboboxmodel.h"

#include <QModelIndex>
#include <QDebug>

QComboBoxModel::QComboBoxModel(QObject *parent)
    :QAbstractListModel(parent)
{
    values = new QList<QPair<int,QString>>();
}

int QComboBoxModel::rowCount(const QModelIndex &) const
{
    return values->count();
}


QVariant QComboBoxModel::data( const QModelIndex &index, int role ) const
{        

    QVariant value;

        switch ( role )
        {
            case Qt::DisplayRole: //string
            {
                value = this->values->value(index.row()).second;
            }
            break;

            case Qt::UserRole: //data
            {
            value = this->values->value(index.row()).first;
            }
            break;

            default:
                break;
        }

    return value;
}

void QComboBoxModel::populate(QList<QPair<int,QString>> *newValues)
{
    int oldIdx = this->values->count();
    int newIdx = newValues->count();
    this->beginInsertRows(QModelIndex(), oldIdx, newIdx);
        this->values = newValues;
    endInsertRows();
}

void QComboBoxModel::append(int index, QString value)
{

    int newRow = this->values->count();

    this->beginInsertRows(QModelIndex(), newRow, newRow);

        values->append(QPair<int,QString>(index,value));

    endInsertRows();
}

Main form

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include "qcomboboxmodel.h"

#include <QMainWindow>

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

private slots:
    void on_comboBox_currentIndexChanged(int index);

    void on_comboBox_currentIndexChanged(const QString &arg1);

    void on_pushButton_clicked();

private:
    Ui::MainWindow *ui;
    int newidx;
    QList<QPair<int,QString>> *values;
    QComboBoxModel *model;
};
#endif // MAINWINDOW_H

#include "mainwindow.h"
#include "qcomboboxmodel.h"
#include "ui_mainwindow.h"

#include <QDebug>

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    values = new QList<QPair<int,QString>>();

    values->append(QPair<int,QString>(-1,"Select item"));
    values->append(QPair<int,QString>(10,"item1(0)"));
    values->append(QPair<int,QString>(11,"item1(1)"));
    values->append(QPair<int,QString>(21,"item1(2)"));
    values->append(QPair<int,QString>(32,"item1(3)"));
    values->append(QPair<int,QString>(44,"item1(4)"));

    newidx = 50;


    model = new QComboBoxModel();
    model->populate(values);
    this->ui->comboBox->setModel(model);
}

MainWindow::~MainWindow()
{
    delete ui;
}


void MainWindow::on_comboBox_currentIndexChanged(int index)
{
    qDebug() << ui->comboBox->itemData(index).value<int>();
}

void MainWindow::on_comboBox_currentIndexChanged(const QString &arg1)
{
    qDebug() << arg1;
}

void MainWindow::on_pushButton_clicked()
{
    qDebug() << "Clicked!";

    newidx++;
    QString strIdx = QString().number(newidx);

    values->append(QPair<int,QString>(newidx,"new item " + strIdx));
    model->populate(values);

    qDebug() << values->toStdList();
}

Now all looks just fine and works as intended!

Vasilij Altunin
  • 754
  • 1
  • 5
  • 18
-1

Why don't you request the current model via "QAbstractItemModel * model() const" from the QComboBox; alter it and assign it again (via "void QComboBox::setModel(QAbstractItemModel *model)")?

Marcel Petrick
  • 454
  • 3
  • 15
  • nope, it does not work QComboBoxModel *m = (QComboBoxModel *)this->ui->comboBox->model(); this->ui->comboBox->setModel(m); – Vasilij Altunin Oct 27 '20 at 10:26
  • What is the specific error/problem? Why do you cast and don't keep the base-class? (By the way: C-Style casts are not CPP-like: see https://stackoverflow.com/questions/43994584/what-is-qobject-cast ) 'auto' is your friend. – Marcel Petrick Oct 27 '20 at 14:30
  • I've just given it a try. The QComboBox comes from an ui-file (first line..). Then changed the value of the first item. ` auto* cb = ui->myCB; cb->insertItem(0, "foo"); cb->insertItem(1, "bar"); auto* model = cb->model(); model->setData(model->index(0,0), "not foo"); cb->setModel(model);` – Marcel Petrick Oct 27 '20 at 14:52
  • There no errors point is best practice, i need to update "values" and then just update combobox with model update. I try different approach but it did not work. You suggest use of setData but i dont want use that complex approach, i need just to add new element to array and then update combobox. – Vasilij Altunin Oct 28 '20 at 00:32
  • Ok, do like this QComboBoxModel *m = dynamic_cast(ui->comboBox->model()); m->populate(values); this->ui->comboBox->setModel(m); it seems work fine, but there a bug, when i press button it add items to cb, but when i open combobox to check items and after that try to add more, it did not add new items :( – Vasilij Altunin Oct 28 '20 at 00:35