© EVILEG 2015-2018
Рекомендует хостинг
TIMEWEB
23 июля 2018 г. 9:04

QComboBox делегат для QTableView

Qt

Добрый день!
Подскажите пожалуйста как можно реализовать делегат в виде QComboBox для ячейки в QTableView.
Данные в сам QComboBox будут браться с отдельной таблицы в базе данных и id выбранной записи необходимо записать в основную таблицу.
Искал в сети пример, но везде реализуют только делегат с уже внесенными данными в конструкторе самого делегата.

Пробовал так:

#ifndef COMBODELEGATE_H
#define COMBODELEGATE_H

#include <QItemDelegate>
#include <QComboBox>

class QModelIndex;
class QWidget;
class QVariant;

class ComboBoxDelegate : public QItemDelegate
{
Q_OBJECT
public:
ComboBoxDelegate(QObject *parent = 0);

QWidget *createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const;
void setEditorData(QWidget *editor, const QModelIndex &index) const;
void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const;
void updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const;
};

#endif // COMBODELEGATE_H
Реализация:
#include "combodelegate.h"

#include <QWidget>
#include <QModelIndex>
#include <QAbstractItemModel>

ComboBoxDelegate::ComboBoxDelegate(QObject *parent)
:QItemDelegate(parent)
{

}

QWidget *ComboBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/* option */, const QModelIndex &/* index */) const
{
QComboBox* editor = new QComboBox(parent);
return editor;
}

void ComboBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
QComboBox *comboBox = dynamic_cast<QComboBox*>(editor);
int value = index.data(Qt::EditRole).toInt();
comboBox->setCurrentIndex(value);
}

Делегат отображается в ячейке, так, но пустой:
ui->mainTableView->setItemDelegateForColumn(2, new ComboBoxDelegate(this));

Как в него запихнуть модель с данными и затем получать id выбранной записи не пойму.

У QComboBox есть метод setModel()

В методе setEditorData вам нужно каким-то образом создать модель с необходимыми данными, и поместить его в этот комбобокс, как я полагаю.
void ComboBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    QComboBox *comboBox = dynamic_cast<QComboBox*>(editor);
    int value = index.data(Qt::EditRole).toInt();

    // Создать здесь модель
    // Думаю, что можно воспользоваться QStandardItemModel, или какая вам будет удобнее, если данные берутся из БД
    auto* yourModelChoices = new SomeTypeModel(comboBox); // Или ещё какой-то иной способ получения модели
    comboBox->setModel(yourModelChoices)
    comboBox->setCurrentIndex(value);
}

То есть то, как достать эту модель для комбобокса - уже зависит от более конретной логики вашего приложения... Возможно можете написать какую-нибудь SqlQueryModel или ещё что-то в этом роде.


  • #
  • отредактировано23 июля 2018 г. 10:22
  • 23 июля 2018 г. 10:19
Есть модель написанная под QComboBox:

BaseComboModel::BaseComboModel( const QString &visualColumn, const QString &queryTail, QObject *parent,
const QString &baseTable, const QString &baseColumn ) :
QSqlQueryModel( parent ),
mainTable(baseTable), mainColumn(baseColumn)
{
selectionColumn = visualColumn;
selectionTable = queryTail.mid(0, queryTail.indexOf(" "));
QSqlQuery query;
query.prepare( QString( "SELECT %1.id, %2 FROM %3" ).arg( queryTail.split( ' ' ).first() ).arg( visualColumn ).arg( queryTail ) );
query.exec();
QSqlQueryModel::setQuery( query );
}


QVariant BaseComboModel::data(const QModelIndex &item, int role) const
{
QVariant result;

if( item.row() == 0 )
{
switch( role )
{
case Qt::UserRole:
result = 0;
break;
case Qt::DisplayRole:
result = "(please select)";
break;
default:
break;
}
}
else
{
switch( role )
{
case Qt::UserRole:
result = dataFromParent( item, Id );
break;
case Qt::DisplayRole:
result = dataFromParent( item, Data );
break;
default:
break;
}
}

return result;
}
В делегате делаю так:
void ComboBoxDelegate::setEditorData(QWidget *editor,
const QModelIndex &index,
const QString column,
const QString table,
const QString baseTable,
const QString baseColumn) const
{
QComboBox *comboBox = dynamic_cast<QComboBox*>(editor);
//int value = index.model()->data(index, Qt::EditRole).toUInt();
int value = index.data(Qt::EditRole).toInt();

BaseComboModel *model = new BaseComboModel(column, table, nullptr, baseTable, baseColumn);
comboBox->setModel(model);
comboBox->setCurrentIndex(value);
}
Использование:
ComboBoxDelegate *equipdelegate = new ComboBoxDelegate();
equipdelegate->setEditorData(new QComboBox(), QModelIndex(), Equipment_Name, Equipment_Table, Main_Table, "equipment");
ui->mainTableView->setItemDelegateForColumn(2, equipdelegate);
С QComboBox эта модель работает отлично, а здесь в делегате по прежнему пусто. Может я не правильно понял или что-то не так делаю?
  • #
  • 23 июля 2018 г. 11:24

И можно еще один маленький вопрос: Как изменить значение в ячейке QTableView?

Думается мне, что вся проблема в том, что в

equipdelegate->setEditorData(new QComboBox(), QModelIndex(), Equipment_Name, Equipment_Table, Main_Table, "equipment");

Передаётся в качестве аргумента QModelIndex() - который по существу невалидный. То есть непонятно, что там будет и будет ли работать.
Но скорее всего есть ещё что-то.
Также вот это место нужно переписать так

BaseComboModel::BaseComboModel( const QString &visualColumn, const QString &queryTail, QObject *parent,
        const QString &baseTable, const QString &baseColumn ) :
        QSqlQueryModel( parent ),
        mainTable(baseTable), mainColumn(baseColumn)
{
    selectionColumn = visualColumn;
    selectionTable = queryTail.mid(0, queryTail.indexOf(" "));
    QSqlQueryModel::setQuery( QString( "SELECT %1.id, %2 FROM %3" ).arg( queryTail.split( ' ' ).first() ).arg( visualColumn ).arg( queryTail ));
}

QSqlQuery выполняется в методе setQuery
Попробуйте для начала сделать какую-нибудь QStandardItemModel и установить её в делегате в методе setEditorData, чтобы посмотреть, работает ли там в принципе что-нибудь.


Вам нужно реализовать метод setData в вашей QSqlQueryModel. Эта модель, из-за того, что она использует сырые запросы, является Read-Only, поскольку невозможно определить логику модели заранее... Там может быть спойка нескольких таблиц с самой хитрой логикой.

Поэтому нужно переопределить метод setData и самостоятельно написать сырые SQL запросы для изменения данных.
Огромное спасибо за помощь!
Решил уйти от QSqlQueryModel и реализовать все на QSqlQuery с использованием свойства Qt::UserRole из QCombobox.
Если кому-то пригодится выкладываю рабочий вариант и пример использования (заголовочный не выкладываю там и так все ясно).
Реализация делегата:
#include "combodelegate.h"

#include <QComboBox>
#include <QSqlQuery>

ComboBoxDelegate::ComboBoxDelegate(QObject *parent, const QString &visualColumn, const QString &queryTail)
    :QStyledItemDelegate(parent)
{
    this->visualColumn = visualColumn;
    this->queryTail = queryTail;
}

QWidget *ComboBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &/* option */, const QModelIndex &/* index */) const
{
    auto *editor = new QComboBox(parent);
    QSqlQuery query;
    query.prepare( QString( "SELECT %1.id, %2 FROM %3" ).arg( queryTail.split( ' ' ).first() ).arg( visualColumn ).arg( queryTail ) );
    query.exec();

    while (query.next()) {
        // query.value(1).toString() - text
        // query.value(0) - userData (id)
        editor->addItem(query.value(1).toString(), query.value(0));
    }
    return editor;
}

void ComboBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    auto *comboBox = dynamic_cast<QComboBox*>(editor);
    QString currentText = index.data(Qt::EditRole).toString();
    int cbIndex = comboBox->findText(currentText);
    if (cbIndex >= 0)
        comboBox->setCurrentIndex(cbIndex);
}

void ComboBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    if (auto *comboBox = dynamic_cast<QComboBox*>(editor)) {
        QVariant modelData = comboBox->itemData(comboBox->currentIndex(), Qt::UserRole);    // id из Qt::UserRole
        model->setData(index, modelData.toString(), Qt::EditRole);
    }
    else
        QStyledItemDelegate::setModelData(editor, model, index);
}

void ComboBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/* index */) const
{
    editor->setGeometry(option.rect);
}

Использование:
mainModel->setRelation(2, QSqlRelation("table_name", "id", "column_name"));

auto *eqCb = new ComboBoxDelegate(ui->mainTableView, "column_name", "table_name");
ui->mainTableView->setItemDelegateForColumn(2, eqCb);

Если у Вас есть какие-то поправки или улучшения, буду им рад (никакой код не совершенный).

  • #
  • 24 июля 2018 г. 12:01

Только один вопрос пока не понятно как решить : измение значения в ячейке таблицы.

Пользователь вводит данные в ячейку 1 и ячейку 2, а в третью нужно записать их сумму.
Как изменить значение знаю,  а как отследить изменения в ячейках нет. Хочется это сделать без написания своего класса наследника.


Вообще, для отслеживания изменений используется сигнал dataChanged(const QModelIndex &, const QModelIndex &, const QVector<int> &), который передаёт индексы ячеек, где было изменение. Можете подключиться к этому сигналу и отслеживат, что и где изменилось. По изменению в первом и втором столбце делать изменения в третьем. При этом изменения втретьем столбце игнорировать

Понял. Спасибо! Чуть не перемудрил :)

  • #
  • 24 июля 2018 г. 13:20

Не выходит с dataChanged в QTableView он protected.

Упс.. я перепутал.. сигнал у модели. Если у вас стандартная какая-нибудь модель, то должен вызываться, если наследованная, то возможно вам самостоятельно нужно будет прописать место, где вызывать этот сигнал.

Да я уже понял. Спасибо!

connect(ui->mainTableView->model(), &QAbstractItemModel::dataChanged, this, &TableForm::calculateTime);
  • #
  • 24 июля 2018 г. 13:32
Только не понимаю как из этого получить ячейку:

calculateTime(const QModelIndex &topLeft, const QModelIndex &bottomRight, const QVector<int> &roles)
  • #
  • 24 июля 2018 г. 13:53
Все разобрался в
topLeft, bottomRigt

есть column и row.

Ответы

Только авторизованные пользователи могут отвечать на форуме.
Пожалуйста, Авторизуйтесь или Зарегистрируйтесь
15 августа 2018 г. 19:02
Lord Inquisitoris

C++ - Тест 003. Условия и циклы

  • Результат 57баллов,
  • Очки рейтинга-2
15 августа 2018 г. 18:58
Lord Inquisitoris

C++ - Тест 005. Структуры и Классы

  • Результат 83баллов,
  • Очки рейтинга4
15 августа 2018 г. 9:29
Леха Завистович

C++ - Тест 001. Первая программа и типы данных

  • Результат 86баллов,
  • Очки рейтинга6
Последние комментарии
10 августа 2018 г. 13:40
Alex

Работа с триггерными функциями в PostgreSQL

Приветствую! Если вы создаете новую таблицу, почему бы просто не сделать вьюху ? Просто от одного названия "триггер" как-то не хочется его использовать, а уж кода сколько писа...
10 августа 2018 г. 11:46
Евгений Легоцкой

Bash скрипт для создания и скачивания дампа базы данных и медиа файлов с удаленного сервера

Вон оно что. Не сталкивался с таким, надо будет глянуть исходники дефолтного менеджера объектов. Возможно там кеширование просто. Пробовали добавить запись через adminer, перезапусти...
10 августа 2018 г. 11:34
Alex

Bash скрипт для создания и скачивания дампа базы данных и медиа файлов с удаленного сервера

допустим у нас есть любая таблица, созданная джангой. через админку добавляем пару записей. все ок. далее, лично в моем случае , я открываю adminer, и в эту таблицу добавляю еще одну зап...
Сейчас обсуждают на форуме
15 августа 2018 г. 14:06
Олег Корнев

Как подключить QtCharts в QML?

После некоторых манипуляций (переустановил креатор) смог запустить экземплы с использованием QtCharts, но все они работают с подключениями в файлах .pro .cpp, у меня таких файлов нет. Как...
14 августа 2018 г. 7:02
Ruslan-maniak

Переключение страниц и перевод фокуса на потомка новой страницы

Большое спасибо. Подтолкнули меня на мысль вынести обработку клавиш из PathView на всю страницу. И тогда - да, ваша подсказка работает. добавил в StackView onCurrentItemChanged: currentItem.fo...
14 августа 2018 г. 6:39
Евгений Легоцкой

Как сделать аудиовизуализацию для плеера на qt?

Добрый день. Просмотрите пример в Qt Creator, который на QML, там реализовано визуализация, возможно вам понравится использовать, QML, да и кастомные интерфейсы на нём всё-таки лучше...
11 августа 2018 г. 10:12
Евгений Легоцкой

Qt C++ vs QML

Добрый день. Если Андроид предполагается, то конечно нужно использовать QML. Я занимался разработкой арканоида на QML и ещё одной игры. Пытался реализовывать логику на QML, но это ...
11 августа 2018 г. 9:24
Евгений Легоцкой

Помогите со слоями

Проверочное сообщение

Рекомендуемые страницы