1 / 1 / 0
Регистрация: 21.08.2019
Сообщений: 19
1

Переопределение QSqlQueryModel и паралельное использование делегата в одном QTableWidget

22.07.2020, 17:01. Показов 2814. Ответов 11

Author24 — интернет-сервис помощи студентам
Добрый день!
Есть QTableView, в которую попадает модель checkboxVtableModel, наследованная от QSqlQueryModel, и есть делегат ComboBoxDelegate наследованный от QItemDelegate.

Задача переопределенной модели - отображать чекбоксы в нужной колонке и хранить индексы выделенных чекбоксов.

хаголовочный
C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#ifndef CHECKBOXVTABLEMODEL_H
#define CHECKBOXVTABLEMODEL_H
#include <QSqlQueryModel>
#include <QPersistentModelIndex>
#include <QSet>
 
class checkboxVtableModel : public QSqlQueryModel
{
    Q_OBJECT
public:
    explicit checkboxVtableModel(QObject *parent = nullptr);
    QVariant data(const QModelIndex &idx, int role) const;
    Qt::ItemFlags flags(const QModelIndex &index) const;
    bool setData(const QModelIndex &index, const QVariant &value,int role);
private:
    QSet<QPersistentModelIndex> checkedItems;
};
#endif // CHECKBOXVTABLEMODEL_H
исходный
C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#include "checkboxvtablemodel.h"
#include <QDebug>
checkboxVtableModel::checkboxVtableModel(QObject *parent) : QSqlQueryModel(parent)
{}
 
QVariant checkboxVtableModel::data(const QModelIndex &idx, int role) const
{
    if (!idx.isValid())
        return QVariant();
 
    if(role == Qt::CheckStateRole and idx.column()==1)
        return checkedItems.contains(idx) ?
                    Qt::Checked : Qt::Unchecked;
 
    return QSqlQueryModel::data(idx, role);
}
 
Qt::ItemFlags checkboxVtableModel::flags(const QModelIndex &index) const
{
    Qt::ItemFlags defaultFlags = QSqlQueryModel::flags(index);
    if (index.isValid() and  index.column()==1){
        return defaultFlags | Qt::ItemIsUserCheckable;
    }
    if (index.isValid() and  index.column()==0){
        return defaultFlags | Qt::ItemIsEditable;
    }
    return defaultFlags;
}
 
bool checkboxVtableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if(index.isValid() and role == Qt::CheckStateRole)
    {
        if(value == Qt::Checked)
            checkedItems.insert(index);
        else
            checkedItems.remove(index);
 
        emit dataChanged(index, index,{Qt::CheckStateRole});
        return true;
    }
 
    if(  index.isValid() and index.column()==0  and role == Qt::EditRole)
    {
        // скорее всего тут должно быть какое-то изменение данных в отображении QTableView
        emit dataChanged(index, index);
        return true;
    }
    return false;
}
Задача делегата - отображать выпадающий список при редактировании(двойном клике по ячейке) первой ячейки.

Заголовочный
C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#ifndef COMBOBOXDELEGATE_H
#define COMBOBOXDELEGATE_H
#include <QItemDelegate>
#include <QModelIndex>
#include <QObject>
#include <QSize>
#include <QComboBox>
 
class ComboBoxDelegate : public QItemDelegate
{
    Q_OBJECT
public:
    ComboBoxDelegate(QStringList & list, 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;
private:
    QStringList  data;
};
#endif // COMBOBOXDELEGATE_H
исходный
C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#include "comboboxdelegate.h"
 
ComboBoxDelegate::ComboBoxDelegate(QStringList & list, QObject *parent):
    QItemDelegate(parent), data(list){}
 
QWidget *ComboBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    if(index.column()==0){
        QComboBox * comboBox = new QComboBox(parent);
        comboBox->addItems(data);
        return comboBox;
    }
}
 
void ComboBoxDelegate::setEditorData(QWidget *editor, const QModelIndex &index) const
{
    if(index.column()==0){
        QString value = index.model()->data(index, Qt::EditRole).toString();
        QComboBox * comboBox = static_cast<QComboBox*>(editor);
        comboBox->setCurrentText(value);
    }
}
 
void ComboBoxDelegate::setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const
{
    QComboBox * comboBox = static_cast<QComboBox*>(editor);
    QString value = comboBox->currentText();
    model->setData(index, value, Qt::EditRole);
}
 
void ComboBoxDelegate::updateEditorGeometry(QWidget *editor, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    editor->setGeometry(option.rect);
}
работа с QtableView обычная
C++ (Qt)
1
2
3
    tableView = new QTableView; 
    tableViewProblem->setModel(model); // checkboxVtableModel
    tableViewProblem->setItemDelegate(delegate); // ComboBoxDelegate
Проблема в том, что при выборе значения из выпадающего списка значение в ячейке остается неизменным, тогда как чекбокс работает прекрасно.
По одиночке все работает как надо, но если установить мою модель и добавить делегат, то происходит то, о чем я говорил выше.

Видимо, проблема в переопределении методов data и setData класса qSqlQueryModel

Прошу помощи, плюхаюсь уже пару дней на одном месте.
0
Лучшие ответы (1)
Programming
Эксперт
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
22.07.2020, 17:01
Ответы с готовыми решениями:

Переопределение метода в сабклассе делегата
Сабж. Есть класс1, реализует некий метод некого протокола. Как в классе 2(наследник класса1)...

Использование делегата-события
Добрый день, я впервые столкнулся с необходимостью использования делегата, и не совсем понимаю как...

Использование делегата Action
Добрый день. Имеется такая задача Написать статический метод, выполняющий указанное действие...

Использование QSqlTableModel внутри делегата
Есть две таблицы для каждой из которых создан QSqlTableModel. Одна из таблиц отображается через...

11
459 / 357 / 69
Регистрация: 29.05.2018
Сообщений: 1,048
22.07.2020, 20:12 2
Цитата Сообщение от chebureckus Посмотреть сообщение
Проблема в том, что при выборе значения из выпадающего списка значение в ячейке остается неизменным
Цитата Сообщение от chebureckus Посмотреть сообщение
Видимо, проблема в переопределении методов data и setData класса qSqlQueryModel
Проблема, скорее всего, в методе createEditor делегата комбобокса. Мне похожую проблему пришлось решать для QSqlTableModel, но думаю, это не принципиально.
Дело в том, что модель не получает от комбобокса при его изменении новые данные и подставляет старые данные. Для этого надо сообщить модели, что данные изменились.
Делал так:
C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
    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::changedComboBox(int /*index*/)
    {
        emit commitData(qobject_cast<QComboBox*>(sender()));
    }
1
1 / 1 / 0
Регистрация: 21.08.2019
Сообщений: 19
23.07.2020, 09:08  [ТС] 3
Ender Che, сделал так же, как у вас, но, к сожалению, работать не хочет

C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
QWidget *ComboBoxDelegate::createEditor(QWidget *parent, const QStyleOptionViewItem &option, const QModelIndex &index) const
{
    if(index.column()==0){
        QComboBox * comboBox = new QComboBox(parent);
        comboBox->addItems(data);
        comboBox->setCurrentIndex(0);
        connect(comboBox, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &ComboBoxDelegate::changedComboBox);
        return comboBox;
    }
}
 
void ComboBoxDelegate::changedComboBox(int)
{
    qDebug() << "changedComboBox"<<sender();
    emit commitData(qobject_cast<QComboBox*>(sender()));
}
Слот вызывается как и метод setData в переопределенной модели, но в представлении после выбора элемента из комбобокса ничего меняться не хочет


Ender Che,

C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
bool checkboxVtableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if(index.isValid() and role == Qt::CheckStateRole) // с чекбоксами все ок
    {
        if(value == Qt::Checked)
            checkedItems.insert(index);
        else
            checkedItems.remove(index);
 
        emit dataChanged(index, index,{Qt::CheckStateRole});
        return true;
    }
 
    if(  index.isValid() and index.column()==0  and role == Qt::EditRole)
    {
        qDebug() << "checkboxVtableModel";
 
        // пробовал QAbstractTableModel::setData(index,value,role)             пробовал роль Qt::DisplayRole и Qt::EditRole
        // пробовал QAbstractTableModel::setItemData(index,mapValue);
        // пробовал QAbstractItemModel::setData(index,value,role) 
        // пробовал QAbstractItemModel::setData(index,mapValue)
        // пробовал setData(index,mapValue)
 
        // но все они возвращают false и ничего не меняют. 
        // я уже даже не представляю, как еще можно изменить отображаемые данные 
 
        emit dataChanged(index, index);
        return true;
    }
    return false;
}
0
2523 / 1243 / 459
Регистрация: 08.11.2016
Сообщений: 3,412
23.07.2020, 09:48 4
C++ (Qt)
1
2
3
4
5
6
    if(  index.isValid() and index.column()==0  and role == Qt::EditRole)
    {
        // скорее всего тут должно быть какое-то изменение данных в отображении QTableView
        emit dataChanged(index, index);
        return true;
    }
Тут должна быть запись данных в модель, вот только в толк не возьму куда их писать: по идее Вам нужно куда-то записать значение const QVariant &value которое делегат передает методу setData() модели, но у вас модель содержит только индексы выбранных элементов... так что ли должно быть?
C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
bool checkboxVtableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if(index.isValid() && (role == Qt::CheckStateRole || role == Qt::EditRole)) // с чекбоксами все ок
    {
        if(value == Qt::Checked)
            checkedItems.insert(index);
        else
            checkedItems.remove(index);
 
        emit dataChanged(index, index, role);
        return true;
    }
}
1
1 / 1 / 0
Регистрация: 21.08.2019
Сообщений: 19
23.07.2020, 10:07  [ТС] 5
Annemesski,
для чекбоксов есть приватное поле, которое хранит индексы выделенных ячеек
C++ (Qt)
1
 QSet<QPersistentModelIndex> checkedItems;
Колонка с чекбоксами помечается флагом Qt::ItemIsUserCheckable
C++ (Qt)
1
2
3
4
5
6
7
8
Qt::ItemFlags checkboxVtableModel::flags(const QModelIndex &index) const
{
    Qt::ItemFlags defaultFlags = QSqlQueryModel::flags(index);
    if (index.isValid() and  index.column()==1){
        return defaultFlags | Qt::ItemIsUserCheckable;
    }
    return defaultFlags;
}
при клике на чекбокс вызывается метод setData, который добавляет или удаляет индекс из QSet в зависимости от того, стоит галочка в чекбоксе или нет, после этого испускается сигнал dataChanged, который обновляет ячейку с нужным индексом и вызывает для нее метод data
C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
bool checkboxVtableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if(index.isValid() and role == Qt::CheckStateRole)
    {
        if(value == Qt::Checked)
            checkedItems.insert(index);
        else
            checkedItems.remove(index);
 
        emit dataChanged(index, index,{Qt::CheckStateRole});
        return true;
    }
    return false;
}
а метод data уже отображает чекбокс в нужном состоянии в зависимости от того, есть ли нужный индекс в QSet или нет

QVariant checkboxVtableModel::data(const QModelIndex &idx, int role) const
{
if (!idx.isValid())
return QVariant();

if(role == Qt::CheckStateRole and idx.column()==1)
return checkedItems.contains(idx) ?
Qt::Checked : Qt::Unchecked;

return QSqlQueryModel::data(idx, role);
}
0
2523 / 1243 / 459
Регистрация: 08.11.2016
Сообщений: 3,412
23.07.2020, 10:13 6
chebureckus, это все понятно, непонятно какого эффекта вы хотите добиться по role == Qt::EditRole? В какое поле модели нужно писать данные из QComboBox::currentText или QComboBox::currentIndex делегата
1
1 / 1 / 0
Регистрация: 21.08.2019
Сообщений: 19
23.07.2020, 10:31  [ТС] 7
Annemesski, прикрепил 2 скриншота. Данные нужно записывать в первую колонку.

Или вы имеете ввиду поле в классе?
Можно сделать так же по аналогии с чекбоксами. Добавить поле QMap<QPersistentModelIndex, QString> CheckBoxCurrentText, в котором хранить индексы и текущие значения всех комбобоксов.

В методе setData при изменении комбобокса менять значение в QMap.

И в методе data по аналогии и с чекбоксами выводить текущие значения всех комбобоксов.

Но мне кажется, что все должно быть куда проще, ведь сам делегат прекрасно работает и изменяет представление под текущее значение в комбобоксе при его изменении, если к QTableView привязать стандартную модель QSqlQueryModel, а не мою - переопределенную.
Миниатюры
Переопределение QSqlQueryModel и паралельное использование делегата в одном QTableWidget   Переопределение QSqlQueryModel и паралельное использование делегата в одном QTableWidget  
0
1 / 1 / 0
Регистрация: 21.08.2019
Сообщений: 19
23.07.2020, 10:54  [ТС] 8
Annemesski, ведь где-то же хранятся значения первой колонки Qt:isplayRole и их можно как-то изменять
0
2523 / 1243 / 459
Регистрация: 08.11.2016
Сообщений: 3,412
23.07.2020, 11:39 9
Цитата Сообщение от chebureckus Посмотреть сообщение
Или вы имеете ввиду поле в классе?
Можно сделать так же по аналогии с чекбоксами. Добавить поле QMap<QPersistentModelIndex, QString> CheckBoxCurrentText, в котором хранить индексы и текущие значения всех комбобоксов.
что-то типа того, суть в том, что метод модели data() служит как для передачи данных в представление, так и для передачи данных редактору делегата, делегат при изменении данных в редакторе должен передать эти данные в модель yourDelegat::setModelData() где вызвать yourModel::setData() чем спровоцировать представление перечитать данные модели и отобразить эти изменения. То есть в вашем случае модель на основе QSqlQueryModel получив данные из комбобокса, должна пихнуть эти данные в БД, как-то так:
C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
bool checkboxVtableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if(index.isValid() and role == Qt::CheckStateRole)
    {
        if(value == Qt::Checked)
            checkedItems.insert(index);
        else
            checkedItems.remove(index);
 
        emit dataChanged(index, index,{Qt::CheckStateRole});
        return true;
    }
 
    if(  index.isValid() and index.column()==0  and role == Qt::EditRole)
    {
        // скорее всего тут должно быть какое-то изменение данных в отображении QTableView
        QSqlQuery q;
        q.prepare("update tablename set uid = :newuid where uid = :olduid");
        q.bindValue(":newuid", value);
        q.bindValue(":olduid", data(index, Qt::EditRole));
        if (!q.exec())
        {
            qDebug() << q.lastError().text();
            return false;
        }
        emit dataChanged(index, index, role);
        return true;
    }
    return false;
}
1
1 / 1 / 0
Регистрация: 21.08.2019
Сообщений: 19
23.07.2020, 12:17  [ТС] 10
Annemesski,
Цитата Сообщение от Annemesski Посмотреть сообщение
QSqlQuery q;
        q.prepare("update tablename set uid = :newuid where uid = :olduid");
        q.bindValue(":newuid", value);
        q.bindValue(":olduid", data(index, Qt::EditRole));
Дело в том, что мне не нужно менять данные в бд. Процесс для работы с базой данных вынесен в отдельный поток и взаимодействие с ним не самое простое дело. Мне нужно поменять данные именно в локальной модели.
Чтобы пользователь мог выбрать нужные ему значения для каждой записи и только потом, когда процесс редактирования через комбобоксы и чекбоксы будет завершен, уже сохранить эти данные в бд в отдельную таблицу.
Само представление сейчас немного не правильное, комбобокс должен быть в другой колонке, там пользователь должен выбирать номер дефекта, а чекбоксы нужны, как раз для того, добавлять эту запись в таблицу или нет. Сама таблица, куда будут добавляться записи выглядит примерно так.
| uid |num_defect |
|-------|--------------|

Надеюсь, я хорошо обьяснил, и логика работы стала немного понятнее)
0
2523 / 1243 / 459
Регистрация: 08.11.2016
Сообщений: 3,412
23.07.2020, 12:58 11
Лучший ответ Сообщение было отмечено chebureckus как решение

Решение

chebureckus, тогда в модели вместо QSet<QPersistentModelIndex> checkedItems; используйте QMap<QPersistentModelIndex, QVariant> modelDataMap;
C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
QVariant checkboxVtableModel::data(const QModelIndex &idx, int role) const
{
    if (!idx.isValid())
        return QVariant();
 
    if(role == Qt::CheckStateRole and idx.column()==1)
        return modelDataMap.find(idx) != modelDataMap.end() ?
                    Qt::Checked : Qt::Unchecked;
 
    if(role == Qt::EditRole and idx.column()==0 and modelDataMap.find(idx) != modelDataMap.end())
        return  modelDataMap[index];                    
 
    return QSqlQueryModel::data(idx, role);
}
C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
bool checkboxVtableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    if(index.isValid() and role == Qt::CheckStateRole)
    {
        if(value == Qt::Checked)
            modelDataMap[index] = data(index, Qt::EditRole);
        else
            modelDataMap.erase(modelDataMap.find(index));
 
        emit dataChanged(index, index,{Qt::CheckStateRole});
        return true;
    }
 
    if(  index.isValid() and index.column()==0  and role == Qt::EditRole)
    {
        // скорее всего тут должно быть какое-то изменение данных в отображении QTableView
        modelDataMap[index] = value;
        emit dataChanged(index, index, role);
        return true;
    }
    return false;
}
Особенность такого подхода в том что, когда значение ячейки будет изменено через комбобокс, чекбокс автоматом встанет в Qt::Checked, а при снятии флажка с чекбокса значение ячейки вернется к значению в БД что может быть даже лучше для Вас, но если требуется независимость одного от другого придется делать два контейнера и серьезно продумывать логику их взаимодействия.
1
1 / 1 / 0
Регистрация: 21.08.2019
Сообщений: 19
23.07.2020, 20:29  [ТС] 12
Annemesski, они должны работать независимо, тоесть галка может стоять и там, где в ячейке стоит стандартное значение. В общем решил проблему так)

Есть QMap, который хранит индексы и и текущее значение из чекбокса измененных ячеек,
в метод SetData добавляется эта пара всякий раз, как какой-либо чекбокс изменится
C++ (Qt)
1
2
3
4
5
6
7
8
9
10
11
12
bool checkboxVtableModel::setData(const QModelIndex &index, const QVariant &value, int role)
{
    ....
 
    if(  index.isValid() and index.column()==0  and role == Qt::EditRole)
    {
        defectChangedItems[index] = value;
        emit dataChanged(index, index);
        return true;
    }
    return false;
}
Если в QMap есть индекс, то возвращается его значение, а если индекса нет, то возвращается обычное значение
C++ (Qt)
1
2
3
4
5
6
7
8
9
QVariant checkboxVtableModel::data(const QModelIndex &idx, int role) const
{
    ...
 
    if (idx.column()==0 and role == Qt::DisplayRole and  defectChangedItems.find(idx) != defectChangedItems.end())
        return  defectChangedItems[idx];
 
    return QSqlQueryModel::data(idx, role);
}
0
23.07.2020, 20:29
IT_Exp
Эксперт
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
23.07.2020, 20:29
Помогаю со студенческими работами здесь

Использование делегата, разобрать участок кода
Помогите пожалуйста разобраться как работает строка 26. Вот мне понятно то что объявляется...

использование информации из QTableWidget
Здравствуйте. Какой функцией или методом можно осуществить захват информации из QTableWidget и...

Разница лямбда выражения, делегата (\анонимного делегата), методом (\анонимных методов)
Всё просто: В чём отличия: лямбд, делегатов, анонимных делегатов, методов, анонимных методов? ...

Отличие делегата от делегата с лямбда-выражением
Народ чем отличается Invoke(new Action(() =&gt; button2.IsEnabled = true)); от Invoke(new...

переопределение операторов, использование qmap
Здравствуйте, пытаюсь разобраться со стандартными классами контейнеров. Пытался создать класс...

QTableWidget (1) >>> QTableWidget (2) исключить повторяющиеся строки
Добрый день. Подскажите как лучше реализовать... Хочу реализовать исключение повторяющихся...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
12
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin
Copyright ©2000 - 2024, CyberForum.ru