Ruslan PolupanNov. 20, 2018, 7:42 a.m.

Qt - Selecting data from a QSqlQuery database in a QThread stream and creating a QAbstractTableModel model based on it

There was a need to obtain a large amount of data from the database for the subsequent work of the user with them (Reference book of goods in a particular store).

For the code do not kick much. Programming for me is a hobby for the mind.

The query itself takes a long time. For this, we do it in the stream, display the endless ProgressBar to the user with the sentence "Lean back and wait." :-)


The data received in the request is stored in a vector of objects of the class Articles.

Articles

Class for storing a single record of the resulting query. For storage of all records we use a vector.

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;           // The number of parameters. necessary to create a model later
    int artileID;           // Product code
    QString shortname;      // Name
    float amount;           // Amount
    float price;            // 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;
}

For logging and debugging I used the methods described in this article Logging Qt application events to a text file .

ListArticles

The class for sampling from the database


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:
    // Signal to send data to the main stream for further processing
    void signalSendArticlesList(QVector<Articles>);
    // Signal about the end of data sampling
    void finish();

public slots:
    // Slot for starting data sampling, called from the main stream
    void createListGoods();

private:
    int m_terminalID;               // Store number, parameter for request
    int m_shiftID;                  // Shift number, parameter for request
    QVector<Articles> goods;        // A vector in which we will store the results of the query and transfer them to the main stream.
    QSqlRecord connRec;             // Record selected from another model containing database connection parameters

};

#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()
{
    // Create an object to store one record.
    Articles ar;

    // we register type for transfer it through the mechanism a signal slot.
    typedef QVector<Articles> vek;
    qRegisterMetaType<vek>("vektor");

    // From Assistant
    // "A connection can only be used within the thread that created it. Moving connections between threads and creating requests to another thread is not supported."
    // Therefore, we are creating a new connection.

    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 << "I can not connect to the central database"
                                 << endl << dbth.lastError().text();
        return;
    }
    // Bind request to connection
    QSqlQuery q = QSqlQuery(dbth);

    // We form line with request.
    // I prefer to create queries of this type as strings and test them directly in the database manager.

    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);
    // Execute the request
    if(!q.exec(strSQL)) {
        qInfo(logInfo()) << "Errog goodlist" << q.lastError().text();
        emit finish();
    }
    // The cycle of receiving records, and adding them to the vector
    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);
    }
    //Passing the result to the main thread.
    emit signalSendArticlesList(goods);
    //Flow finished work
    emit finish();
}

Run the data stream of query

// Data selection occurs when you go to a specific QWizardPage page.
void ArticlePage::initializePage()
{
    // Создаем объект класса и передаем ему параметры
    ListArticles *lsArticles = new ListArticles(recrodConn, field("terminalID").toInt(),field("shiftID").toInt());
    // Create a stream in which our sample will be made.
    QThread *thread = new QThread();
    // Moving a class object to a stream
    lsArticles->moveToThread(thread);

    //// Signals and slots for interacting with the stream
    
    // When starting a thread, we perform some actions in the current thread.
    // In my case, I simply mark the beginning of the data sample.
    connect(thread,&QThread::started,this,&ArticlePage::slotStartArticlesList);
    // At the start of the stream, we start sampling data
    connect(thread,&QThread::started,lsArticles,&ListArticles::createListGoods);

    // Passing the resulting QVector object from the child to the main thread
    connect(lsArticles,&ListArticles::signalSendArticlesList,this,&ArticlePage::slotGetArticlesList,Qt::DirectConnection);
    // The end of the flow of data sampling
    connect(lsArticles,&ListArticles::finish,thread,&QThread::quit);
    // Remove the object in the stream
    connect(lsArticles,&ListArticles::finish,lsArticles,&ListArticles::deleteLater);
    // Perform actions on the main thread after the completion of the child
    connect(lsArticles,&ListArticles::finish,this,&ArticlePage::slotFinishArticlesList);
    // We say goodbye to the child thread
    connect(thread,&QThread::finished,thread,&QThread::deleteLater);

    // We start a flow
    thread->start();
}

Data exchange slots with stream

void ArticlePage::slotGetArticlesList(QVector<Articles> ls)
{
    //Get the vector with the results from the stream
    goods = ls;
}

void ArticlePage::slotStartArticlesList()
{
    qInfo(logInfo()) << "Began to receive a list of goods" << QTime::currentTime().toString("hh:mm:ss.zzz");

}

void ArticlePage::slotFinishArticlesList()
{
    // based on the received vector with data, create a data model of the VectorModel type and output it in QTableView
    
    qInfo(logInfo()) << "Finished receiving a list of goods" << 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();
    // Minimum row height in QTableView
    ui->tableView->verticalHeader()->setDefaultSectionSize(ui->tableView->verticalHeader()->minimumSectionSize());

}

VektorModel

The class derived from QAbstractTableModel creates a model for displaying request data.

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;
}
// data mapping model
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-й column ID
        case 1: return a.getShortName();                    // 2-й column Name
        case 2: return a.getAmount();                       // 3-й column Amount
        case 3: return QString::number(a.getPrice(),'f',2); // 4-й column Price
        default: break;
        }
      break;

    // align the 2nd and 3rd column to the right
    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
{
    //Creating model column headers
    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
{
    // number of lines
    return ar.size();
}

int VektorModel::columnCount(const QModelIndex &parent) const
{
    // Number of columns
    Articles a;
    return a.getColParam();
}

The result is the following:

We recommend hosting TIMEWEB
We recommend hosting TIMEWEB
Stable hosting, on which the social network EVILEG is located. For projects on Django we recommend VDS hosting.

В процессе отладки на реальных базах данных столкнулся с проблемой что перенос данных из объекта 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++;
    }



s

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

В потоке надо создавать свое соединение с БД с другим именем.

s

Если в:
QSQLDatabase db_thread = QSQLDatabase::addDatabase("MYSQL","db_new_name");

крашится после запуска сразу

Вы полностью создаете новое соединение?
И при создании объекта QSqlQuery или модели указываете алиас подключения?

s

Упс, видимо нет, буду проверять. У меня просто собраны функции работы с базой данных, и подключение новое, но в функция

#ifndef FUNCTIONS_H
#define FUNCTIONS_H

#include <QTextCodec>
#include <QDesktopServices>
#include <QUrl>
#include <QAxObject>
#include <QDebug>
#include <QFile>
#include <QMessageBox>
#include <QSqlDatabase>
#include <QSqlQuery>
#include <QSqlRecord>
#include <QSqlError>
#include <QVariant>

extern QStringList gsf_GetRecoredsetFields(const QString & m_sql);
extern QList<QStringList> gsf_GetRecoredsetData(const QString & m_sql);
extern QString     gsf_GetRecordsetValueString(const QString & m_sql);
extern QByteArray  gsf_GetRecordsetBinary(const QString & m_sql);
extern bool        gsf_ExecuteSQL(const QString & m_sql);
extern bool        gsf_RecordIsValid(const QString & m_sql);

#endif // FUNCTIONS_H
bool gsf_ExecuteSQL(const QString & m_sql)
{
    QSqlQuery query(m_sql);

    if(!query.exec()){
        QString m_message = QString("Запрос завершился с ошибкой: %1, обратитесь к Администратору.")
                .arg(query.lastError().text());

        QMessageBox::warning(nullptr, "Ошибка!", m_message);

        return false;
    }

    return true;
}

Как пример

s

Перенес в класс потока все функции для работы с БД, но все по старому когда закрываю поток основное соединение тоже закрывается

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

s

Я наверное слишком туп, можете пример привести как Вы это делаете?

Так эта статья как раз об этом.

s

Кстати сегодня почему-то все заработало :)

Луна стала в нужную фазу :-)

Comments

Only authorized users can post comments.
Please, Log in or Sign up
Timeweb

Let me recommend you the excellent hosting on which EVILEG is located.

For many years, Timeweb has been proving his stability.

For projects on Django I recommend VDS hosting

View Hosting
EK

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:90points,
  • Rating points8
EK

C ++ - Test 004. Pointers, Arrays and Loops

  • Result:50points,
  • Rating points-4
p

C++ - Test 001. The first program and data types

  • Result:40points,
  • Rating points-8
Last comments
g

Поздравляю всё Сообщество EVILEG с Новым Годом!!!

#include <iostream>using namespace std;int main() { cout << "Happy New Year and Thank you!"; return 0;}
MZ
MZ

Django - Tutorial 003. Model, Template, View on Django

from django.shortcuts import *from django.http import *from django.http import HttpResponseRedirectfrom django.contrib.auth.forms import *from django.urls import reverse_lazyfrom django.views …
MZ

Django - Tutorial 003. Model, Template, View on Django

Я дура. Разобралась. Но, остаюсь вашим читателем)
Now discuss on the forum

Проблемы с QTableView

Добрый день! Кто-нибудь сталкивался с тем, чтобы в QTableView (или QTableWidget) менять ширину/высоту и хэдров, и самих ячеек таблицы курсором мыши как это реализовано с помощью resizeSecti…

QSqlTableModel - Как добавить картинки в таблицу, чтобы они отражались в диалоговом окне, но не были частью модели

Если у вас модель, в переопределнном методе QVariant data надо привязываться к Qt::EditRole, это если открывающийся диалог редактирования является частью таблицы
t

Работа с потоками в разных версиях Qt

Добрый день, впервые начал проект в котором активно используются потоки и наткнулся на один не очень приятный момент, например: Существует главный поток (GUI) и созданный поток. Связь…
e
  • eviza
  • Jan. 13, 2021, 6:08 a.m.

QSerialPort. Как правильнее проверить сигнал CTS?

Здравствуйте! Есть задача отслеживать изменение сигнала CTS, можно ли это сделать по таймеру методом QSerialPort::pinoutSignals()?
About
Services
© EVILEG 2015-2020
Recommend hosting TIMEWEB