Есть несколько вопросов по коду, и возможно вы предложите более грамотный вариант, т.к мой выглядит как костыль. Буду рад любой помощи.
Вопросы:
Код прилагается:
mainwindow.cpp
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QTableView>
#include <QStandardItemModel>
#include <QStringList>
#include "delegate.h"
#include <QDebug>
enum Test {One = 1, Two = 2, Three = 3};
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
QTableView *tableView = new QTableView;
QStandardItemModel * model = new QStandardItemModel(1,1);
tableView->setModel(model);
QStringList itemName;
itemName << "One" << "Two" << "Three";
QStringList itemValue;
itemValue << "1" << "2" << "3";
Delegate * delegate = new Delegate(itemName, itemValue, this);
tableView->setItemDelegateForColumn(0, delegate);
ui->verticalLayout->addWidget(tableView);
connect(model, SIGNAL(itemChanged(QStandardItem*)), this, SLOT(changeValue(QStandardItem*)));
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::changeValue(QStandardItem *item)
{
qDebug() << "changeValue:" << item->text();
Test test = static_cast<Test>(item->text().toUInt());
qDebug() << "TEST: " << test;
}
delegate.h
#ifndef DELEGATE_H
#define DELEGATE_H
#include <QObject>
#include <QItemDelegate>
#include <QStringList>
class Delegate : public QItemDelegate
{
Q_OBJECT
public:
explicit Delegate(const QStringList &itemName, const QStringList &itemValue, QObject *parent = NULL);
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;
void paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const;
private:
QStringList m_itemName;
QStringList m_itemValue;
mutable bool m_isNewValue;
};
#endif // DELEGATE_H
delegate.cpp
#include "delegate.h"
#include <QDebug>
#include <QComboBox>
#include <QApplication>
#include <QPalette>
Delegate::Delegate(const QStringList &itemName, const QStringList &itemValue, QObject *parent)
: QItemDelegate(parent), m_itemName(itemName), m_itemValue(itemValue), m_isNewValue(false)
{
qDebug() << "CREATE";
}
QWidget *Delegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
qDebug() << "createEditor";
QComboBox *comboBox = new QComboBox(parent);
for (int i = 0; i < m_itemValue.size(); ++i)
{
// Добавить элемент (text, value(Qt::UserRole))
comboBox->addItem(m_itemName.at(i), m_itemValue.at(i));
}
return comboBox;
}
void Delegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
qDebug() << "setEditorData";
QComboBox *edit = qobject_cast<QComboBox *>(editor);
QString currentText = index.data(Qt::EditRole).toString();
int cbIndex = edit->findText(currentText);
if (cbIndex >= 0)
edit->setCurrentIndex(cbIndex);
}
void Delegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
qDebug() << "setModelData";
QComboBox *edit = qobject_cast<QComboBox *>(editor);
QVariant modelData = edit->itemData(edit->currentIndex(), Qt::UserRole); // value из Qt::UserRole
model->setData(index, modelData.toString(), Qt::EditRole);
}
void Delegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
editor->setGeometry(option.rect);
}
void Delegate::paint(QPainter *painter, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QStyleOptionViewItemV4 opt = option;
//qDebug() << "INDEX: " << index.data(Qt::EditRole).toString();
// Изначально значение не отображается т.к не сработал еще метод createEditor
QString text = index.data(Qt::EditRole).toString();
if (text.isEmpty())
{
// Костыль, выборка значения по умолчанию
text = m_itemValue.at(0);
}
static QString oldValue = text;
if (oldValue != text)
{
m_isNewValue = true;
}
else
{
m_isNewValue = false;
}
opt.text = text;
if (m_isNewValue)
{
QPalette p;
p.setColor(QPalette::Text, Qt::green);
opt.palette = p;
}
QApplication::style()->drawControl(QStyle::CE_ItemViewItem, &opt, painter);
}
Показываю пример с комментариями и с надеждой, что он ответит на все ваши вопросы, но если что, я готов (в меру сил и знаний) что-то уточнить и прояснить.
И так, начнем. Тельце конструктора MainWindow:
{
QTableView* tableView = new QTableView;
// Создаем модель размером 1х1 и внедряем ее в нашу табличку
QStandartModel* model = new QStandartModel(1, 1);
tableView->setModel(model);
// Добавляем хранитель значения в нашу модель, обернутого типом QStandartItem
QStandartItem* item = new QStandartItem();
// А вот и наше значение (список элементов для Комбо) инициализированное парами {<value>, <id>}
QList< QPair<int, QString> > data = { {"Value1", 0}, {"Value2", 1}, {"Value2", 2}}; // О какой же кайф этот список инициализации, сколько ж лет я ждал... Кхм, едем дальше
// Внимание! Следим за руками
// Для отображения устанавливаем значение по умолчанию. Первый элемент из нашего списка
item->setData(data[0].first, Qt::DisplayRole); // DisplayRole - встроенная роль, которая говорит отображению, что связанные с ней данные надо отобразить
// Сохраним текущий id из списка под ролью Qt::UserRole +1
// Для чего это нужно будет описано ниже
item->setData(data[0].second);
// И наконец наш список значений для Комбо. (см. п.1 в Пояснениях)
item->setData( QVariant::fromValue(data), Qt::UserRole +2);
// Инициализировали нашу обертку нашими значениями. Теперь отправляем это в model
model->setItem(0, 0, item);
// Куда пропали аргументы класса делегата вы уже поняли. Если нужны пояснения то п.2
Delegate* delegate = new Delegate(this);
tableView.setItemDelegateForColumn(0, delegate);
//...
// С вашего разрешения я воспользуюсь лямда-функцией, чтобы все было в одном месте
// Вы можете оставить ваш коннект (включая функцию-слот) таким как он был
connect(model, &QStandartItemModel::itemChanged, this, [&](QStandartItem* itm)
{
qDebug() << "Current Item Is Changed. "
<< " ID = " << item.data(Qt::UserRole +1).toInt()
<< " VALUE = " << item.data(Qt::DisplayRole).toString();
};
}
Пояснения.
Для того, чтобы преобразовать в QVariant наш список пар необходимо где-то (рекомендую в каком-нибудь общем заголовочном файле) объявить следующее:
typedef QList< QPair > MyList;
Q_DECLARE_METATYPE(MyList);
Объект класса Делегата в шаблоне Model-View-Controller необходим только для формирования способа отображения и редактирования данных, которые должны располагаться в объекте класса модели. В вашем случае данные хранятся в QStandartItemModel, получаются при помощи QModelIndex (это все Model), а оторажаются при помощи QTableView и QComboBox в режиме редатирования данных ячейки (а это View, Controller это все что между ними ) ). Это очень хороший шаблон обработки/хранения/редактирования данных, я рекомендую изучить его работу, тем более, что это совсем не сложно (хотя бы на текущем примере). Шаблон этот используется во множестве фреймворков (особенно в web), так что пригодится.
Мы сформировали данные, сохранили их в моделе, теперь необходимо заняться их отображением. Переходим к классу Delegate. Заголовочный файл полностью:
#include <QItemDelegate>
class Delegate: public QItemDelegate
{
Q_OBJECT
public:
explicit Delegate(QObject* parent = Q_NULLPTR);
QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, 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;
}
Больше ничего не надо. Теперь реализация:
#inlcude "delegate.h"
#include <QComboBox>
// Не забудьте подключить заголовник с MyList, чтобы не дублировать здесь объявление
// typedef QList<QPair<int, QString> > MyList; хотя ... )
Delegate::Delegate(QObject *parent)
: QItemDelegate(parent)
{
}
QWidget *Delegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
QComboBox* comboBox = new QComboBox(parent);
// Под ролью UserRole +2 мы храним список пар значений
QVariant vList = index.data(Qt::UserRole +2);
// Преобразуем Вариант в наш список
MyList list = vList.value<MyList>();
// Инициализируем наш Комбо
for(auto& item: list)
{
comboBox->addItem(item.first /*Значение*/, item.second /*ID*/);
}
return comboBox;
}
void Delegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
QComboBox *edit = qobject_cast<QComboBox *>(editor);
// Временно блокируем сигналы изменения модели, потому что он эмитируется (emit)
// каждый раз, когда в модель вносят какие-либо изменения
model->blockSignals(true);
// Сохраняем выбранное значение из Комбо
model->setData(index, edit->currentText(), Qt::DisplayRole);
// А теперь нам снова надо внести изменения в модель,
// но при этом мы хотим, чтобы соответствующий сигнал (itemChanged) сработал,
// поэтому вновь включаем сигналы модели
model->blockSignals(false);
// Можешь закоментить оба вызова blockSignals и посмотреть что получится
// Сохраняем в модель выбранный ID
// В модель внесли изменения и она эмитирует itemChanged
// В роли Qt::UserRole +1 у нас ID пары
model->setData(index, edit->itemData(edit->currentIndex()), Qt::UserRole +1);
}
void Delegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &/*index*/) const
{
editor->setGeometry(option.rect);
}
Все. Текста много, но на самом деле все просто.
Дополнение
Для того, чтобы изменить цвет текста ячейки (или подложки, или шрифта) в зависимости от каких-либо условий, нам надо использовать QStandartItem, который мы получаем в качестве аргумента сигнала itemChanged(QStandartItem* item).
А именно, как-то так:
connect(model, &QStandartItemModel::itemChanged, this, [&](QStandartItem* itm)
{
// ID выбранного элемента
int id = itm->data(Qt::UserRole +1).toInt();
// Изменение цвета шрифта выбранного элемента
// Здесь я сравниваю полученный id например с 1
itm->setForeground(QBrush( id == 1? Qt::darkGreen: Qt::black ));
};
В примере я сравниваю полученное значение ID с "жесткой" единицей, но конечно можно где-то ввести переменную (поле класса), задавать ее и сравнивать уже с ней. А чтобы не плодить и не смешивать логику сущностей (так как это у нас все-таки главное окно приложения), я рекомендую наследоваться от класса QStandartItem и сохранять значение по-умолчанию внутри него.
Например так:
class MyStandartItem : public QStandartItem
{
public:
// Установка значения ID по умолчанию
void setDefaultID(int id) {
_defID =
}
// Проверяем текущий ID со значением по умолчанию
void checkID(int id)
{
setForeground(QBrush( _defID == id? Qt::darkGreen: Qt::black ));
}
private:
int _defID;
}
Это тем более полезно, если ваша таблица будет состоять из множества ячеек. Тогда наш itemChanged превращается в:
connect(model, &QStandartItemModel::itemChanged, this, [&](QStandartItem* itm)
{
// ID выбранного элемента
int id = itm->data(Qt::UserRole +1).toInt();
static_cast<MyStandartItem*>(itm)->checkID(id);
};
Сборка персонального компьютера от Artline: умный выбор для современных пользователей