QComboBox Delegate и отображение данных в QTableView

135
22 сентября 2019, 17:20

Есть несколько вопросов по коду, и возможно вы предложите более грамотный вариант, т.к мой выглядит как костыль. Буду рад любой помощи.

Вопросы:

  1. Как правильно установить значение по умолчанию, что бы оно отображалось, возможно минуя метод paint()? Без переопределения метода paint() и без установления значения по умолчанию text = m_itemValue.at(0); ничего не отображается, пока не кликнешь по виджету 2 раза, тогда вызовется метод createEditor() и заполнится содержимым.
  2. Как сделать что бы значение по умолчанию заданное пользователем можно было установить из вне (по значению value(Qt::UserRole)). Пример: через список инициализации проинициализировать значение и установить в таком то переопределенном методе.
  3. Изначально отображается значение 1, посредством переопределенного метода paint(), а мне необходимо сделать One , при клике появляется выпадающий список в виде текста One Two Three, после окончания редактирования отображается снова 1, 2, 3, как сделать так что бы по окончанию редактирования тоже отображались One Two Three , а не 1, 2, 3, но при этом метод changeValue(QStandardItem *item) получал именно числовое значение 1, 2, 3. Возможно ли такое сделать?

Код прилагается:

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);
}
Answer 1

Показываю пример с комментариями и с надеждой, что он ответит на все ваши вопросы, но если что, я готов (в меру сил и знаний) что-то уточнить и прояснить.

И так, начнем. Тельце конструктора 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();
    };
}

Пояснения.

  1. Для того, чтобы преобразовать в QVariant наш список пар необходимо где-то (рекомендую в каком-нибудь общем заголовочном файле) объявить следующее:

    typedef QList< QPair > MyList;
    Q_DECLARE_METATYPE(MyList);

  2. Объект класса Делегата в шаблоне 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);
};
READ ALSO
Блокировка запуска программы [закрыт]

Блокировка запуска программы [закрыт]

Пользователь сам определяет какую программу хочет защитить, при запуске программы под защитой появляется окно блокировки

171
nvoglv64.dll exception во время выполнения glDrawArrays

nvoglv64.dll exception во время выполнения glDrawArrays

При выполнении функции glDrawArrays() в Rubeckcpp в 9 из 10 случаев возникает исключение Exception thrown at 0x00000000535D7C20 (nvoglv64

185
Где находится windows.h и как подключить на Ubuntu?

Где находится windows.h и как подключить на Ubuntu?

Вообщем такой вопрос, "как поставить библиотеку, чтобы там были заголовки windowsh, conio

174
Округление дробных чисел. С++

Округление дробных чисел. С++

Можно ли сделать так,чтобы округлялось в большую или меньшую сторону в зависимости от полученного значения , только тогда, когда у меня тип...

168