Schauen wir uns die Aufgabentabelle genauer an.
Nach den eingangs genannten Anforderungen hätte die Tabelle so aussehen sollen.
Zum Zeitpunkt der Entwicklung wurde klar, dass die Logik der eigenständigen Dropdownliste immer komplizierter wurde, und es wurde beschlossen, sie durch einen Delegaten zu implementieren. Also sieht die Tabelle jetzt so aus.
Zuerst implementieren wir den Delegaten, dann kümmern wir uns um das Füllen mit Farbe und das Verbieten der Zellbearbeitung.
Dazu müssen Sie QStyledItemDelegate erben und mehrere seiner Methoden überschreiben.
// 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
Wir implementieren diese Methoden.
// 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())); }
Es lief nicht ganz rund. Es gibt ein Problem mit der Zugänglichkeit der Dropdown-Liste: Sie funktioniert erst nach einem Doppelklick darauf normal. Und alles wäre in Ordnung, aber bei einem Doppelklick habe ich die Bearbeitung der Aufgabe ausgesetzt, deren Dialogfeld angezeigt wird. Nach dem Schließen des Fensters können Sie den Status der Aufgabe sicher ändern.
Kommen wir nun zu Farbe und Zellen. Hier ist es notwendig, das entsprechende Datenmodell zu erben.
// 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); }
Das Aussehen der Tabelle entspricht nun den aktualisierten Anforderungen.
По-моему, это всё-таки уже часть 5.
Вот здесь у вас такой код
В данном случае вызывать метод базового класс не имеет смысла, поскольку вы всё равно полностью перезаписываете флаги, а не добавляете новые к тем, которые вернул метод базового класса. То есть можно просто так написать.
Вы придерживаетесь какого-то код стайла с ограничение количества колонок на строку?
У нас в проекте принято именовать через enum колонки в модели данных, чтобы не было вот таких магических чисел
Такое именование может выглядеть следующим образом.
Тогда такой код будет выглядеть так, это лучше, чем магическое число.
Плюс я бы объединил проверки
Вообще я думаю, что весь этот метод можно переписать тогда так
Если не будет компилироваться, то тогда так
Вот этот метод стоит переписать так
Да, часть 5, поправил.
Согласен.
Да, стараюсь вписаться в 80 символов. Если разделить окно просмотра кода на два экрана по вертикали, это актуально.
Согласен, что нужны enumы. Только я думаю, что будет лучше вынести их глобально куда-нибудь, т.к. у меня эти же магические числа используются и в теле основного класса.
От такого однострочника у меня голова лопается.)) Лучше второй вариант.
Согласен.
Спасибо за подробный разбор кода!
Понимаю, к сожалению иногда названия методов или классов становятся достаточно большими, чтобы проект комфортно поддерживался, то есть сокращения неприемлемы, что актуально в крупном проекте. В тоже время код-стайл может не приветствовать подобный перенос того, что справа от оператора = на следующую строку.
Это пока у вас одна модель данных на весь проект, а когда в проекте около 1000 моделей, то тогда лучше, чтобы каждая модель данных имела свой набор наименований колонок, которые бы подчинялись определённым правилам. Тем более, что наименования колонок имеют прямое отношение к модели данных, поэтому скорее другой программист ожидал бы видеть такой enum в области модели, чем где-то глобально.
Вы можете просто подключать заголовочный файл модели и использовать enum так
Таким образом у вас будет информация, к какой области кода непосредственно относится колонка.
Да, так даже лучше. Спасибо.