Рассмотрим подробно таблицу задач.
По требованиям, указанным вначале, таблица должна была выглядеть так.
На момент разработки стало понятно, что логика работы отдельно стоящего выпадающего списка становится сложной, и было принято решение реализовать его через делегата. Поэтому таблица теперь выглядит так.
Вначале реализуем делегата, потом займёмся заливкой цветом и запретом редактирования ячеек.
Для этого надо унаследовать QStyledItemDelegate и перезаписать несколько его методов.
// comboboxdelegate.h #ifndef COMBOBOXDELEGATE_H #define COMBOBOXDELEGATE_H // Подключаем main.h, откуда нам понадобится и база данных, и список состояний задач #include "main.h" #include <QStyledItemDelegate> class ComboBoxDelegate : public QStyledItemDelegate { Q_OBJECT public: ComboBoxDelegate(); QWidget* createEditor(QWidget* parent, const QStyleOptionViewItem& option, const QModelIndex& index) const override; void setEditorData(QWidget* editor, const QModelIndex& index) const override; void setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const override; void updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& index) const override; void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override; private slots: void changedComboBox(int index); }; #endif // COMBOBOXDELEGATE_H
Реализуем указанные методы.
// comboboxdelegate.cpp #include "comboboxdelegate.h" #include "customsortfilterproxymodel.h" #include <QComboBox> #include <QApplication> #include <QMessageBox> ComboBoxDelegate::ComboBoxDelegate() { } // Создаём комбобокс QWidget* ComboBoxDelegate::createEditor(QWidget* parent, const QStyleOptionViewItem& /*option*/, const QModelIndex& /*index*/) const { QComboBox* comboBox = new QComboBox(parent); // Загружаем список состояний задач foreach(QString status, statusList) comboBox->addItem(status); comboBox->setCurrentIndex(0); // Создаём соединение между сигналом изменения индекса комбобокса и его обработчиком connect(comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ComboBoxDelegate::changedComboBox); return comboBox; } // Устанавливаем данные для комбобокса void ComboBoxDelegate::setEditorData(QWidget* editor, const QModelIndex& index) const { // Если поле 5 (колонка "Состояние") if(index.column() == 5) { // читаем данные из модели QString value = index.model()->data(index, Qt::EditRole).toString(); QComboBox* comboBox = static_cast<QComboBox*>(editor); for(int i = 0; i < statusList.size(); ++i) { if(value == statusList.at(i)) // и устанавливаем соответствующий индекс comboBox->setCurrentIndex(i); } } } // Записываем данные в модель (через базу данных) void ComboBoxDelegate::setModelData(QWidget* editor, QAbstractItemModel* model, const QModelIndex& index) const { // Если поле 5 (колонка "Состояние") if(index.column() == 5) { QComboBox* edit = static_cast<QComboBox*>(editor); QString status = edit->currentText(); // Получаем исходную модель CustomSortFilterProxyModel* filterModel = static_cast<CustomSortFilterProxyModel*>(model); int row = index.row(); // Получаем из модели поле с номером задачи QModelIndex idIndex = filterModel->index(row, 0); // Получаем номер задачи int id = filterModel->data(idIndex, Qt::DisplayRole).toInt(); // Если состояние задачи переведено в "закрыта" if(status == "закрыта") // закрыть задачу database->closeTask(id, "manual"); else // иначе обновить состояние задачи database->updateStatusTask(id, status); } } // Переустанавливаем геометрию комбобокса void ComboBoxDelegate::updateEditorGeometry(QWidget* editor, const QStyleOptionViewItem& option, const QModelIndex& /*index*/) const { editor->setGeometry(option.rect); } // Перерисовываем комбобокс void ComboBoxDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const { if(index.column() == 5) { QStyleOptionComboBox comboBoxStyleOption; comboBoxStyleOption.state = option.state; comboBoxStyleOption.rect = option.rect; comboBoxStyleOption.currentText = index.data(Qt::EditRole).toString(); QApplication::style()->drawComplexControl(QStyle::CC_ComboBox, &comboBoxStyleOption, painter, 0); QApplication::style()->drawControl(QStyle::CE_ComboBoxLabel, &comboBoxStyleOption, painter, 0); return; } QStyledItemDelegate::paint(painter,option, index); } // Отправка сигнала модели, что данные должны быть записаны после изменения // состояния комбобокса void ComboBoxDelegate::changedComboBox(int /*index*/) { emit commitData(qobject_cast<QComboBox*>(sender())); }
Реализовалось не совсем гладко. Осталась беда с доступностью выпадающего списка: он начинает нормально работать только после двойного щелчка на нём. И всё бы ничего, но на двойной щелчок у меня подвешено редактирование задачи, диалоговое окно которого и появляется. После закрытия окна можно спокойно менять состояние задачи.
Теперь займёмся цветом и ячейками. Здесь необходимо унаследовать соответствующую модель данных.
// customsortfilterproxymodel.h #ifndef CUSTOMSORTFILTERPROXYMODEL_H #define CUSTOMSORTFILTERPROXYMODEL_H // Подключаем main.h, из которого нам понадобятся список состояний и соответствующих им цветов #include "main.h" #include <QSortFilterProxyModel> class CustomSortFilterProxyModel : public QSortFilterProxyModel { public: CustomSortFilterProxyModel(); Qt::ItemFlags flags(const QModelIndex& index) const override; QVariant data(const QModelIndex& idx, int role) const override; }; #endif // CUSTOMSORTFILTERPROXYMODEL_H
// customsortfilterproxymodel.cpp #include "customsortfilterproxymodel.h" #include <QBrush> #include <QColor> CustomSortFilterProxyModel::CustomSortFilterProxyModel() { } // Устанавливаем для всех ячеек, кроме столбца "Состояние", свойства // доступности ячеек и возможности их выбора, редактирование ячеек невозможно Qt::ItemFlags CustomSortFilterProxyModel::flags(const QModelIndex& index) const { if(index.isValid()) { if(index.column() != 5) { Qt::ItemFlags flags = QSortFilterProxyModel::flags(index); flags = Qt::ItemIsEnabled | Qt::ItemIsSelectable; return flags; } } return QSortFilterProxyModel::flags(index); } // Устанавливаем для всех ячеек в строке цвет в зависимости от состояния задачи QVariant CustomSortFilterProxyModel::data(const QModelIndex& idx, int role) const { if(idx.isValid()) { if(role == Qt::BackgroundRole) { for(int i = 0; i < statusList.size(); ++i) { if(QSortFilterProxyModel::data(this->index(idx.row(), 5)).toString() == statusList.at(i)) return QBrush(QColor(colorList.at(i))); } } else if(role == Qt::DisplayRole) return QSortFilterProxyModel::data(idx); } return QSortFilterProxyModel::data(idx, role); }
Теперь вид таблицы соответствует обновлённым требованиям.
По-моему, это всё-таки уже часть 5.
Вот здесь у вас такой код
В данном случае вызывать метод базового класс не имеет смысла, поскольку вы всё равно полностью перезаписываете флаги, а не добавляете новые к тем, которые вернул метод базового класса. То есть можно просто так написать.
Вы придерживаетесь какого-то код стайла с ограничение количества колонок на строку?
У нас в проекте принято именовать через enum колонки в модели данных, чтобы не было вот таких магических чисел
Такое именование может выглядеть следующим образом.
Тогда такой код будет выглядеть так, это лучше, чем магическое число.
Плюс я бы объединил проверки
Вообще я думаю, что весь этот метод можно переписать тогда так
Если не будет компилироваться, то тогда так
Вот этот метод стоит переписать так
Да, часть 5, поправил.
Согласен.
Да, стараюсь вписаться в 80 символов. Если разделить окно просмотра кода на два экрана по вертикали, это актуально.
Согласен, что нужны enumы. Только я думаю, что будет лучше вынести их глобально куда-нибудь, т.к. у меня эти же магические числа используются и в теле основного класса.
От такого однострочника у меня голова лопается.)) Лучше второй вариант.
Согласен.
Спасибо за подробный разбор кода!
Понимаю, к сожалению иногда названия методов или классов становятся достаточно большими, чтобы проект комфортно поддерживался, то есть сокращения неприемлемы, что актуально в крупном проекте. В тоже время код-стайл может не приветствовать подобный перенос того, что справа от оператора = на следующую строку.
Это пока у вас одна модель данных на весь проект, а когда в проекте около 1000 моделей, то тогда лучше, чтобы каждая модель данных имела свой набор наименований колонок, которые бы подчинялись определённым правилам. Тем более, что наименования колонок имеют прямое отношение к модели данных, поэтому скорее другой программист ожидал бы видеть такой enum в области модели, чем где-то глобально.
Вы можете просто подключать заголовочный файл модели и использовать enum так
Таким образом у вас будет информация, к какой области кода непосредственно относится колонка.
Да, так даже лучше. Спасибо.