1

I have a custom storage and I want to implement ListModel to display it with QComboBox. For simplicity let's assume that we have a list of int as a model's data, so here is my implementation of a model and test program:

#include <QApplication> 
#include <QDebug>
#include <QAbstractListModel>
#include <QComboBox>
#include <QHBoxLayout>
#include <QPushButton>

QList<int> list;

class MyModel : public QAbstractListModel
{
public:
    MyModel( QWidget* parent ):
        QAbstractListModel( parent )
    {}
    ~MyModel()
    {
        qDebug() << __FUNCTION__;
    }

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

        if ( ( role == Qt::DisplayRole ) && ( index.row() < list.size() ) )
            return QString::number( list.at( index.row() ) );

        return QVariant();
    }

    int rowCount(const QModelIndex &parent) const
    {
        Q_UNUSED( parent )
        return list.size();
    }
};

int main ( int argc, char* argv[] )
{
    QApplication app ( argc, argv );

    QWidget w;
    QHBoxLayout* l = new QHBoxLayout();

    QComboBox* c = new QComboBox( &w );
    c->setModel( new MyModel( c ) );
    l->addWidget( c );

    QPushButton* b = new QPushButton("+");
    QObject::connect( b, &QPushButton::clicked, [](){
        list.push_back( qrand() );
        qDebug() << list;
    } );
    l->addWidget( b );

    b = new QPushButton("-");
    QObject::connect( b, &QPushButton::clicked, [](){
        if ( !list.isEmpty() )
            list.pop_back();
        qDebug() << list;
    } );
    l->addWidget( b );
    w.setLayout( l );
    w.show();

    return app.exec ();
}

If I hit button Add only once and then check list, it looks okay, but when I hit it again, QComboBox displays only the first value and an empty line; continuing adding new elements has no effect at QComboBox. If I click on button Add many times, then I can see the correct list in ComboBox.

I can't understand what is going on and what I do wrong.

I am using Qt5.5.1 with VS2013 x32 on Windows 7.

3 Answers 3

5

Your model needs to emit proper signals when the data actually changes. This is generally done by calling protected functions beginInsertRows, endInsertRows, etc... As these are protected, they can be called only by member functions inside MyModel. Also, for good practice, the actual data (your list) should be modified only by MyModel.

First, make list a private member of MyModel. It should not be accessed from the outside.

private:
    QList<int> list;

Add two public functions inside MyModel for managing list:

public:
    void push(int value)
    {
        beginInsertRows(list.size(), list.size());
        list.push_back(value);
        endInsertRows();
    }

    void pop()
    {
        if(list.size()>0)
        {
            beginRemoveRows(list.size()-1, list.size()-1);
            list.pop_back();
            endRemoveRows();
        }
    }

Inside main, keep a pointer on your model

MyModel * m = new MyModel(c);

Then use these functions inside main() instead of appending to list directly.

QObject::connect( b, &QPushButton::clicked, [m](){
        m->push(qrand());
    } );

QObject::connect( b, &QPushButton::clicked, [m](){
        m->pop();
    } );

Et voila

Alternative, more Qt way

Declare push and pop as slots:

public slots:
    void push()
    {
        beginInsertRows(list.size(), list.size());
        list.push_back(qrand());
        endInsertRows();
    }

    void pop()
    {
        if(list.size()>0)
        {
            beginRemoveRows(list.size()-1, list.size()-1);
            list.pop_back();
            endRemoveRows();
        }
    }

And connect them directly to push buttons in main:

QObject::connect( b, &QPushButton::clicked, m, &MyModel::push);

//...

QObject::connect( b, &QPushButton::clicked, m, &MyModel::pop);
Sign up to request clarification or add additional context in comments.

2 Comments

Thanks for the great explanation! But the only problem that list given in my example is simplification of a real situation, and it could not be encapsulated in model, because it is already encapsulated by different class :-( Is there is any other way to nmotify model from the outside?
If you cannot call MyModel member functions right before and after data is modified, then it's not possible. You should change your program design so that the model class is responsible for handling the data, either by setting your other class as a member of MyModel, or by using multiple inheritance in your other class. The model architecture is no magic, it cannot detect content change in a simple memory container.
1

The problem with this code is that model doesn't know that data inside list is changed. And the QComboBox doesn't know about data inside model changed too. So each time you change the data inside list your model should emit signals layoutAboutToBeChanged() and then layoutChanged() to notify QComboBox about changes.

Comments

0

You have to implement additional functions, at least AbstractItemModel::insertRows. You have to notify a model about changes. For more information look into docs.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.