How to organize dropdown list in tableview ? I use databinding with sqlite. In one of the columns there are elements that need to be presented as a drop-down list.

    2 answers 2

    If the data in the drop-down list is contained in a separate database table, then it is convenient to use the QSqlRelationalTableModel and the delegate class QSqlRelationalDelegate .

    For demonstration, suppose there is a table of cities:

     CREATE TABLE IF NOT EXISTS `cities` ( `city_id` INTEGER UNSIGNED NOT NULL , `name` VARCHAR(50) NOT NULL , PRIMARY KEY(`city_id`) ) 

    ... and a table of employees who work in these cities:

     CREATE TABLE IF NOT EXISTS `employees` ( `employee_id` INTEGER UNSIGNED NOT NULL , `city_id` INTEGER NOT NULL , `name` VARCHAR(255) NOT NULL , PRIMARY KEY(`employee_id`) ) 

    Fill these tables with data:

     INSERT INTO `cities` VALUES(1, 'Москва') INSERT INTO `cities` VALUES(2, 'Санкт-Петербург') INSERT INTO `employees` VALUES(1, 1, 'Иван Иваныч') INSERT INTO `employees` VALUES(2, 2, 'Пётр Петрович') 

    Ivan Ivanovich works in Moscow, and Petr Petrovich in St. Petersburg.

    Now create a model and table view using Qt:

     QSqlRelationalTableModel *model = new QSqlRelationalTableModel(); model->setTable("employees"); model->setEditStrategy(QSqlTableModel::OnManualSubmit); model->setRelation(1, QSqlRelation("cities", "city_id", "name")); model->setHeaderData(0, Qt::Horizontal, QObject::tr("ID")); model->setHeaderData(1, Qt::Horizontal, QObject::tr("City")); model->setHeaderData(2, Qt::Horizontal, QObject::tr("Name")); QTableView *view = new QTableView(); view->setModel(model); 

    The key line of code when combining data of two tables is using the setRelation() method of QSqlRelationalTableModel . The first argument of the method is the column index in the source table (in our case, employees ), the second is the QSqlRelation join QSqlRelation , in the designer of which the name of the table being connected is specified, the name of its column corresponding to the source, plus the name of the column whose contents should replace the contents in the source cell tables if a match is found.

    If you run the program, then in the table of employees instead of identifiers of cities will be their names. However, when you try to edit a city cell, a drop-down list with available names will not appear. To solve this side of the issue, it is enough to connect the delegate object QSqlRelationalDelegate to the view:

     view->setItemDelegateForColumn(1, new QSqlRelationalDelegate(view)); 

    Of course, all this will only work if the original model is derived from QSqlRelationalTableModel or direct use of an object of this class. It is not in all cases possible. For example, if the model receives data from the network.

    For this case, suppose that the existing tables employees and cities are not contained in the database, but only as data, for example, in an arbitrary successor to QAbstractItemModel . As in the first case, you will need a delegate who will act as a link between the tables of employees and cities.

    Comboboxitemdelegate.h file:

     #ifndef COMBOBOXITEMDELEGATE_H #define COMBOBOXITEMDELEGATE_H #include <QtCore/QPointer> #include <QtWidgets/QStyledItemDelegate> class QAbstractItemModel; class ComboBoxItemDelegate : public QStyledItemDelegate { Q_OBJECT public: explicit ComboBoxItemDelegate(QObject *parent = Q_NULLPTR); virtual ~ComboBoxItemDelegate() {} QAbstractItemModel *model() const; void setModel(QAbstractItemModel *model); int modelKeyColumn() const; void setModelKeyColumn(int column); int modelViewColumn() const; void setModelViewColumn(int column); virtual QString displayText(const QVariant &value , const QLocale &locale) const; virtual QWidget *createEditor(QWidget *parent , const QStyleOptionViewItem &option , const QModelIndex &index) const; virtual void setModelData(QWidget *editor , QAbstractItemModel *model , const QModelIndex &index) const; private: int _model_key_column, _model_view_column; QPointer<QAbstractItemModel> _model; }; #endif 

    Comboboxitemdelegate.cpp file:

     #include <QtWidgets/QComboBox> #include "comboboxitemdelegate.h" ComboBoxItemDelegate::ComboBoxItemDelegate(QObject *parent) : QStyledItemDelegate(parent) , _model_key_column(0), _model_view_column(0) {} QAbstractItemModel *ComboBoxItemDelegate::model() const { return _model; } void ComboBoxItemDelegate::setModel(QAbstractItemModel *model) { _model = model; } int ComboBoxItemDelegate::modelKeyColumn() const { return _model_key_column; } void ComboBoxItemDelegate::setModelKeyColumn(int column) { _model_key_column = column; } int ComboBoxItemDelegate::modelViewColumn() const { return _model_view_column; } void ComboBoxItemDelegate::setModelViewColumn(int column) { _model_view_column = column; } QString ComboBoxItemDelegate::displayText(const QVariant &value , const QLocale &locale) const { Q_UNUSED(locale); if(_model.isNull()) return QString(); while(_model->canFetchMore(QModelIndex())) _model->fetchMore(QModelIndex()); QModelIndexList indexes = _model->match(_model->index(0,_model_key_column) , Qt::DisplayRole, value, 1, Qt::MatchExactly); if(indexes.isEmpty()) return QString(); QModelIndex index = _model->index(indexes.first().row(), _model_view_column); return (index.isValid()) ? _model->data(index).toString() : QString(); } QWidget *ComboBoxItemDelegate::createEditor(QWidget *parent , const QStyleOptionViewItem &option, const QModelIndex &index) const { if(_model.isNull()) return; while(_model->canFetchMore(QModelIndex())) _model->fetchMore(QModelIndex()); QComboBox *cbox = new QComboBox(parent); cbox->setGeometry(option.rect); cbox->setModel(_model); cbox->setModelColumn(_model_view_column); QModelIndexList indexes = _model->match(_model->index(0,_model_key_column) , Qt::DisplayRole, index.data(), 1, Qt::MatchExactly); if(!indexes.isEmpty()) cbox->setCurrentIndex(indexes.first().row()); return cbox; } void ComboBoxItemDelegate::setModelData(QWidget *editor , QAbstractItemModel *model, const QModelIndex &index) const { if(_model.isNull()) return; QComboBox *cbox = qobject_cast<QComboBox*>(editor); if(cbox == Q_NULLPTR) return; while(_model->canFetchMore(QModelIndex())) _model->fetchMore(QModelIndex()); QModelIndex model_index = _model->index(cbox->currentIndex(), _model_key_column); if(model_index.isValid()) model->setData(index, model_index.data()); } 

    The presented delegate does not claim to be used and is published to demonstrate the mechanism of the association of values ​​between models. Connecting and using a delegate:

     QAbstractItemModel *employees = ... ; QAbstractItemModel *cities = ... ; QTableView *view = new QTableView(); view->setModel(employees); ComboBoxItemDelegate *delegate = new ComboBoxItemDelegate(view); delegate->setModel(cities); delegate->setModelKeyColumn(0); delegate->setModelViewColumn(1); view->setItemDelegateForColumn(1, delegate); 

      Do you at least clarify which tableview we are talking about - QTableView from widgets or TableView from QtQuick.Controls. In general, in both cases, the delegate is written; for the first case, QTableView (more precisely, its ancestor) has a setItemDelegateForColumn method, which is fed a column number and a descendant of QAbstractItemDelegate. Such a descendant I once wrote for myself and how much I remember it even worked, but it was a long time ago and I don’t know if it works now.

      With the second case, everything is simpler - TableViewColumn has the appropriate delegate property where qml Item is inserted (for example, as here , although you could probably do without Item), but I myself did not use this in practice as far as I remember, and in general with combo boxes QtQuick.Controls I have strained relations - it is not editable, then editable, but the list is opened for space (corrected only in 5.4.1), then the empty string is not programmatically set as the value