2016-01-25 8 views
1

У меня есть собственное хранилище, и я хочу реализовать ListModel, чтобы отобразить его с помощью QComboBox. Для простоты предположим, что у нас есть список int в качестве данных по типовому, так вот моя реализация модели и программы испытаний:QComboBox с пользовательской моделью

#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(); 
} 

Если я ударил кнопку Добавить только один раз, а затем проверить список, его выглядит нормально, но когда я снова ударил его, QComboBox отображает только первое значение и пустую строку; продолжение добавления новых элементов не влияет на QComboBox. Если я нажимаю на кнопку , добавьте много раз, затем я вижу правильный список в ComboBox.

Я не могу понять, что происходит и что я делаю неправильно.

Я использую Qt5.5.1 с VS2013 x32 по адресу Windows 7.

ответ

3

Ваша модель должна излучать правильные сигналы, когда данные действительно изменяются. Это обычно делается путем вызова защищенных функций beginInsertRows, endInsertRows и т. Д. Поскольку они защищены, их можно вызывать только функциями-членами внутри MyModel. Кроме того, для хорошей практики фактические данные (ваш list) должны быть изменены только MyModel.

Прежде всего, сделайте list частным членом MyModel. Он не должен быть доступен извне.

private: 
    QList<int> list; 

Добавьте две общественные функции внутри MyModel управления 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(); 
     } 
    } 

Внутри главного, хранить указатель на модели

MyModel * m = new MyModel(c); 

Затем использовать эти функции внутри main() вместо добавления к list напрямую.

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

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

Et вуаля

альтернативы, более Qt пути

Объявить push и pop в 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(); 
     } 
    } 

и подключить их непосредственно нажимать на кнопки в основном:

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

//... 

QObject::connect(b, &QPushButton::clicked, m, &MyModel::pop); 
+0

Спасибо за отличное объяснение! Но единственная проблема, которая приведена в моем примере, - это упрощение реальной ситуации, и она не может быть инкапсулирована в модель, потому что она уже инкапсулирована другим классом :-(Есть ли какой-либо другой способ для изменения модели извне? – Jeka

+0

Если вы не можете вызывать функции-члены MyModel прямо до и после изменения данных, тогда это невозможно. Вы должны изменить свой дизайн программы, чтобы класс модели отвечал за обработку данных, либо установив свой другой класс в качестве члена MyModel , или с помощью множественного наследования в вашем другом классе. Архитектура модели не волшебна, она не может обнаружить изменение контента в простом контейнере памяти. – galinette

0

Вы должны реализовать дополнительные функции, по крайней мере AbstractItemModel :: insertRows. Вы должны уведомить модель об изменениях. Для получения дополнительной информации смотрите docs.

1

Проблема с этим кодом заключается в том, что модель не знает, что данные внутри списка изменены. И QComboBox не знает об изменениях данных внутри модели. Поэтому каждый раз, когда вы меняете данные внутри списка, ваша модель должна излучать сигналы layoutAboutToBeChanged(), а затем layoutChanged(), чтобы уведомить QComboBox об изменениях.