Политика конфиденциальностиКонтактыО сайтеОтзывыGitHubDonate
© EVILEG 2015-2018
Рекомендует хостинг
TIMEWEB

Qt - Выбор данных из базы данных QSqlQuery в потоке QThread и создание на их основе модели QAbstractTableModel

QThread, Qt, QAbstractTableModel, QSqlQuery

Возникла необходимость получить большой объем данных из базы данных для последующей работы пользователя с ними (Справочник товаров в определенном магазине).

За код сильно не пинайте. Программирование для меня это хобби в качестве зарядки для ума.

Запрос сам по себе выполняет достаточно долго. По этому  делаем его в потоке, пользователю отображаем бесконечный ProgressBar с предложением "Откинутся на спинку стула и подождать." :-)


Данные получаемые в запросе храним в векторе объектов класса Articles.

Articles

Класс для хранения одной записи результирующего запроса. Для хранения всех записей используем вектор.

articles.h

#ifndef ARTICLES_H
#define ARTICLES_H

#include <QString>

class Articles
{
public:
    Articles();
    void setColParam(int _colParam);
    void setID(int _articleID);
    void setShortName(QString _shortName);
    void setAmount(float _amount);
    void setPrice(float _price);
    int getColParam();
    int getID();
    QString getShortName();
    float getAmount();
    float getPrice();
private:
    int colParam;           // Количетсво параметров. необходимое для последующего создания модели
    int artileID;           // Код товара
    QString shortname;      // Наименование
    float amount;           // Количество
    float price;            // Цена
};

#endif // ARTICLES_H

articles.cpp

#include "articles.h"

Articles::Articles()
{
    setColParam(4);
}

void Articles::setColParam(int _colParam)
{
    colParam = _colParam;
}

int Articles::getColParam()
{
    return colParam;
}

void Articles::setID(int _articleID)
{
    artileID = _articleID;
}

void Articles::setShortName(QString _shortName)
{
    shortname = _shortName;
}

void Articles::setAmount(float _amount)
{
    amount = _amount;
}

void Articles::setPrice(float _price)
{
    price = _price;
}

int Articles::getID()
{
    return artileID;
}

QString Articles::getShortName()
{
    return shortname;
}

float Articles::getAmount()
{
    return amount;
}
float Articles::getPrice()
{
    return price;
}

Для логирования и отладки использовал методы описанные в этой статье Логирование событий Qt приложения в текстовый файл .

ListArticles

Класс для осуществления выборки из базы данных


listarticles.h

#ifndef LISTARTICLES_H
#define LISTARTICLES_H

#include "articles.h"

#include <QObject>
#include <QSqlQuery>
#include <QSqlError>
#include <QVector>
#include <QSqlRecord>


class ListArticles : public QObject
{
    Q_OBJECT
public:
    explicit ListArticles(QSqlRecord rec, int terminal_D, int shift, QObject *parent = nullptr);

signals:
    // Сигнал дя отправки данных в основной поток для дальнейшей их обработки
    void signalSendArticlesList(QVector<Articles>);
    // Сигнал об окончании выборки данных
    void finish();

public slots:
    // Слот для начала выбоки данных, вызываемый из основного потока
    void createListGoods();

private:
    int m_terminalID;               // Номер магазина, параметр для запроса
    int m_shiftID;                  // Номер смены, параметр для запроса
    QVector<Articles> goods;        // Вектор в котором будем хранить результаты запроса и передадим в основной поток
    QSqlRecord connRec;             // Запись выбранная из другой модели содержащщая параметры подключения к базе данных

};

#endif // LISTARTICLES_H

listarticles.cpp

#include "listarticles.h"
#include "loggingcategories.h"

ListArticles::ListArticles(QSqlRecord rec, int terminal_D, int shift, QObject *parent) : QObject(parent)
{
    // Получаем входные данные
    m_terminalID = terminal_D;
    m_shiftID = shift;
    connRec =rec;
}

void ListArticles::createListGoods()
{
    // Создаем объект для хранения одной записи
    Articles ar;

    // регистрируем тип для передачи его через механизм сигнал слот.
    typedef QVector<Articles> vek;
    qRegisterMetaType<vek>("vektor");

    // Из Асистента
    // "Соединение может использоваться только внутри создавшего его потока. Перемещение соединений между потоками и создание запросов в другой поток не поддерживается.
    // Поэтому создаем новое соединение

    QSqlDatabase dbth = QSqlDatabase::addDatabase("QIBASE","thcentr");

    dbth.setHostName(connRec.value("conn_host").toString());
    dbth.setDatabaseName(connRec.value("conn_db").toString());
    dbth.setUserName(connRec.value("conn_user").toString());
    dbth.setPassword(connRec.value("conn_pass").toString());


    if(!dbth.open()) {
        qCritical(logCritical()) << Q_FUNC_INFO << "Не могу подключится к центральной базе"
                                 << endl << dbth.lastError().text();
        return;
    }
    // Привязыем запрос к соединению
    QSqlQuery q = QSqlQuery(dbth);

    // Формируем строку с запросом.
    // Такого типа запросы предпочитаю создавать ввиде строки и тестировать их в непосредственно в менеджере базы данных

    QString strSQL = QString("SELECT A.GARTICLE_ID, GA.SHORTNAME, SL.AMOUNT, "
                                 "(SELECT FIRST 1 NEWPRICE FROM HISTORY_PRICES HP "
                                  "WHERE HP.TERMINAL_ID = SL.TERMINAL_ID "
                                  "AND HP.GARTICLE_ID = A.GARTICLE_ID "
                                  "AND HP.DATEOP < SH.DATCLOSE "
                                  "ORDER BY HP.DATEOP DESC) AS PRICE "
                               "FROM GET_ASALDOS (%1, %2, NULL, 0) AS SL "
                               "INNER JOIN ARTICLES A ON A.TERMINAL_ID = SL.TERMINAL_ID AND A.ARTICLE_ID = SL.ARTICLE_ID "
                               "LEFT JOIN SHIFTS SH ON SH.TERMINAL_ID = SL.TERMINAL_ID AND SH.SHIFT_ID = SL.SHIFT_ID "
                               "LEFT JOIN GARTICLES GA ON GA.GARTICLE_ID = A.GARTICLE_ID "
                               "WHERE SL.AMOUNT > 0 "
                               "ORDER BY A.GARTICLE_ID" )
                .arg(m_terminalID)
                .arg(m_shiftID);
    // Выполняем запрос
    if(!q.exec(strSQL)) {
        qInfo(logInfo()) << "Errog goodlist" << q.lastError().text();
        emit finish();
    }
    // Цикл получения записей, и добавления их в вектор
    while (q.next()){
        ar.setID(q.value("GARTICLE_ID").toInt());
        ar.setShortName(q.value("SHORTNAME").toString().trimmed());
        ar.setAmount(q.value("AMOUNT").toFloat());
        ar.setPrice(q.value("PRICE").toFloat());
        goods.append(ar);
    }
    //Передаем результат в основной поток
    emit signalSendArticlesList(goods);
    //Поток закончил работу
    emit finish();
}

Запуск потока выборки данных

// Выбор данных происходит при переходе на определенную странцу QWizardPage
void ArticlePage::initializePage()
{
    // Создаем объект класса и передаем ему параметры
    ListArticles *lsArticles = new ListArticles(recrodConn, field("terminalID").toInt(),field("shiftID").toInt());
    // Создаем поток в которм будут производиться наша выборка
    QThread *thread = new QThread();
    // Перемещаем объект класса в поток
    lsArticles->moveToThread(thread);

    //// Сигналы и слоты для взаимидействия с потоком
    
    // при старте потока выполняем некоторые действия в текущем потоке. 
    // В моем случае на просто засекаю начало выбоки данных
    connect(thread,&QThread::started,this,&ArticlePage::slotStartArticlesList);
    // При старте потока начинаем выборку данных
    connect(thread,&QThread::started,lsArticles,&ListArticles::createListGoods);

    // Передача результирующего объекта QVertor из дочернего потока в основной
    connect(lsArticles,&ListArticles::signalSendArticlesList,this,&ArticlePage::slotGetArticlesList,Qt::DirectConnection);
    // Окончание работы потока по завершению выбрки данных
    connect(lsArticles,&ListArticles::finish,thread,&QThread::quit);
    // Удаляем объект в потоке
    connect(lsArticles,&ListArticles::finish,lsArticles,&ListArticles::deleteLater);
    // Вы полняем действия по в основном потоке после завершения дочернего
    connect(lsArticles,&ListArticles::finish,this,&ArticlePage::slotFinishArticlesList);
    // Прощаемся с дочерним потоком
    connect(thread,&QThread::finished,thread,&QThread::deleteLater);

    // Запускаем поток
    thread->start();
}

Слоты обмена данными с потоком

void ArticlePage::slotGetArticlesList(QVector<Articles> ls)
{
    //Получаем вектор с результами из потока
    goods = ls;
}

void ArticlePage::slotStartArticlesList()
{
    qInfo(logInfo()) << "Начали получать список товаров" << QTime::currentTime().toString("hh:mm:ss.zzz");

}

void ArticlePage::slotFinishArticlesList()
{
    // на основании полученного вектора с данными создаем модель данных типа VectorModel и выводим ее в QTableView
    
    qInfo(logInfo()) << "Закончили получать список товаров" << QTime::currentTime().toString("hh:mm:ss.zzz");
    ui->frameProgress->hide();
    ui->groupBoxAdd->show();

    modelArticles = new VektorModel(goods);

    ui->tableView->setModel(modelArticles);
    ui->tableView->verticalHeader()->hide();
    ui->tableView->setAlternatingRowColors(true);
    ui->tableView->resizeColumnsToContents();
    // Минимальная высота строк в QTableView
    ui->tableView->verticalHeader()->setDefaultSectionSize(ui->tableView->verticalHeader()->minimumSectionSize());

}

VektorModel

Класс наследник от QAbstractTableModel создающий модель для отображения данных запроса.

vektormodel.h

#ifndef VEKTORMODEL_H
#define VEKTORMODEL_H

#include "articles.h"
#include <QObject>
#include <QAbstractTableModel>
#include <QVariant>



class VektorModel : public QAbstractTableModel
{
    Q_OBJECT
    QVector<Articles>  ar;

public:
    VektorModel(const QVector<Articles> vek);
    QVariant data(const QModelIndex &index, int role) const;
    QVariant headerData( int section, Qt::Orientation orientation, int role ) const;
    int rowCount(const QModelIndex &parent) const;
    int columnCount(const QModelIndex &parent) const;
};

#endif // VEKTORMODEL_H

vectormodel.cpp

#include "vektormodel.h"
#include "articles.h"

VektorModel::VektorModel(const QVector<Articles> vek)
{
    ar = vek;
}
// отображение данных модели
QVariant VektorModel::data(const QModelIndex &index, int role) const
{
    if ( !index.isValid() ) { return QVariant(); }
        Articles a = ar[index.row()];
    switch (role) {
    case Qt::DisplayRole:
        switch (index.column()) {
        case 0: return a.getID();                           // 1-й столбец ID
        case 1: return a.getShortName();                    // 2-й столбец Наименование
        case 2: return a.getAmount();                       // 3-й столбец Количетсво
        case 3: return QString::number(a.getPrice(),'f',2); // 4-й столбец Цена
        default: break;
        }
      break;

    // выравниваем 2-й и 3-й столбез по правому краю
    case Qt::TextAlignmentRole:
        if(index.column() == 2 || index.column() == 3 )
            return Qt::AlignRight;
        break;
    default:
        break;
    }

    return QVariant();

}

QVariant VektorModel::headerData(int section, Qt::Orientation orientation, int role) const
{
    //Cоздаем заголовки столбцов модели
    if( role != Qt::DisplayRole ) {
        return QVariant();
    }

    if( orientation == Qt::Vertical ) {
        return section;
    }

    switch( section ) {
    case 0:
        return tr( "Гл.Код" );
    case 1:
        return tr( "Наименование" );
    case 2:
        return tr( "Доступно" );
    case 3:
        return tr( "Цена" );
    }

    return QVariant();
}

int VektorModel::rowCount(const QModelIndex &parent) const
{
    // Количетсво строк
    return ar.size();
}

int VektorModel::columnCount(const QModelIndex &parent) const
{
    // Количество столбцов
    Articles a;
    return a.getColParam();
}

В результате получаем следующее:

В процессе отладки на реальных базах данных столкнулся с проблемой что перенос данных из объекта QSqlQuery в вектор выполняется достаточно медленно.

    // Цикл получения записей, и добавления их в вектор
    while (q.next()){
        ar.setID(q.value("GARTICLE_ID").toInt());
        ar.setShortName(q.value("SHORTNAME").toString().trimmed());
        ar.setAmount(q.value("AMOUNT").toFloat());
        ar.setPrice(q.value("PRICE").toFloat());
        goods.append(ar);
    }

Проблема решилась замена названий в value на номера.

    while (q.next()){
        ar.setID(q.value(0).toInt());
        ar.setShortName(q.value(1).toString().trimmed());
        ar.setAmount(q.value(2).toFloat());
        ar.setPrice(q.value(3).toFloat());
        goods << ar;
        i++;
    }



Комментарии

Только авторизованные пользователи могут публиковать комментарии.
Пожалуйста, авторизуйтесь или зарегистрируйтесь
v
17 января 2019 г. 11:51
vitalir12

C++ - Тест 004. Указатели, Массивы и Циклы

  • Результат:20баллов,
  • Очки рейтинга-10
v
17 января 2019 г. 11:49
vitalir12

C++ - Тест 002. Константы

  • Результат:50баллов,
  • Очки рейтинга-4
v
17 января 2019 г. 11:13
vitalir12

C++ - Тест 003. Условия и циклы

  • Результат:28баллов,
  • Очки рейтинга-10
Последние комментарии
I
16 января 2019 г. 8:06
IscanderChe

Заработало. Забыл model->select(); вписать.
I
16 января 2019 г. 8:02
IscanderChe

Всё равно пусто, хотя строка с данными в базу добавляется.
16 января 2019 г. 7:51
Евгений Легоцкой

потому, что нужно сохранять информацию для всех остальных ролей и столбцов через вызов переопределённого метода. Да к тому же вы ещё и зациклили вызов метода data. QVariant MySqlTableModel:...
I
16 января 2019 г. 7:43
IscanderChe

Сделал вот так. В tableView ничего нет, кроме заголовка. QVariant MySqlTableModel::data(const QModelIndex &index, int role) const{ if (role == Qt::DisplayRole) { QTime ...
Сейчас обсуждают на форуме
18 января 2019 г. 11:26
nayk1982

Для Desktop делал так: void pause(int ms){ QTimer timer; timer.setInterval( qBound(1, ms, 3600000) ); timer.setSingleShot(true); QEventLoop loop; QObject::connect(&...
17 января 2019 г. 12:01
Алексей Внуков

у меня просто есть отдельное поле с чекбоксамими какие колонки нужно отображать CheckBox { id: checkBox text: qsTr("some text") checked: true onC...
15 января 2019 г. 16:53
Михаиллл

Спасибо, заработало.Но выдало обычный текст без форатирования HTML.Придется искать дальше
15 января 2019 г. 12:52
BlinCT

Я же вам выше написал CLion умеет работать с ремоут машинами. И Qt так же собирает.
Присоединяйтесь к нам в социальных сетях

Для зарегистрированных пользователей на сайте присутствует минимальное количество рекламы