Ruslan Polupan
Ruslan Polupan20. November 2018 07:42

Qt - Auswählen von Daten aus einer QSqlQuery-Datenbank in einem QThread-Stream und Erstellen eines darauf basierenden QAbstractTableModel-Modells

Es war notwendig, eine große Datenmenge aus der Datenbank für die spätere Arbeit des Benutzers mit ihnen zu erhalten (Warenverzeichnis in einem bestimmten Geschäft).

Für den Code treten nicht viel. Programmieren ist für mich ein Hobby als Denksport.

Die Abfrage selbst dauert sehr lange. Deshalb machen wir es in einem Flow, wir zeigen dem Benutzer einen endlosen Fortschrittsbalken mit dem Satz „Lehnen Sie sich in ihrem Stuhl zurück und warten Sie.“ :-)


Die in der Anfrage empfangenen Daten werden in einem Vektor von Objekten der Artikelklasse gespeichert.

Artikel

Klasse zum Speichern eines Datensatzes der resultierenden Abfrage. Wir verwenden einen Vektor, um alle Datensätze zu speichern.

Artikel.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

Artikel.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;
}

Zum Protokollieren und Debuggen habe ich die in diesem Artikel Protokollieren von Qt-Anwendungsereignissen in einer Textdatei beschriebenen Methoden verwendet.

Artikelliste

Klasse zum Abrufen aus der Datenbank

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();
}

Datenabruf-Thread starten

// Выбор данных происходит при переходе на определенную странцу 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();
}

Slots für den Datenaustausch mit dem Stream

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());

}

Vektormodell

Eine von QAbstractTableModel abgeleitete Klasse, die ein Modell zum Anzeigen von Abfragedaten erstellt.

Vektormodell.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

Vektormodell.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();
}

Als Ergebnis erhalten wir Folgendes:

Рекомендуємо хостинг TIMEWEB
Рекомендуємо хостинг TIMEWEB
Stabiles Hosting des sozialen Netzwerks EVILEG. Wir empfehlen VDS-Hosting für Django-Projekte.

Magst du es? In sozialen Netzwerken teilen!

Ruslan Polupan
  • 27. November 2018 06:36

В процессе отладки на реальных базах данных столкнулся с проблемой что перенос данных из объекта 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
  • 29. November 2020 04:27
  • (bearbeitet)

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

Ruslan Polupan
  • 30. November 2020 02:32

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

s
  • 30. November 2020 11:40

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

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

Ruslan Polupan
  • 1. Dezember 2020 02:43

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

s
  • 1. Dezember 2020 13:21

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

#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
  • 2. Dezember 2020 16:10

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

Ruslan Polupan
  • 3. Dezember 2020 01:03

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

s
  • 3. Dezember 2020 14:13

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

Ruslan Polupan
  • 4. Dezember 2020 09:27

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

s
  • 4. Dezember 2020 10:11

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

Ruslan Polupan
  • 7. Dezember 2020 01:43

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

Kommentare

Nur autorisierte Benutzer können Kommentare posten.
Bitte Anmelden oder Registrieren
Letzte Kommentare
ИМ
Игорь Максимов5. Oktober 2024 07:51
Django – Lektion 064. So schreiben Sie eine Python-Markdown-Erweiterung Приветствую Евгений! У меня вопрос. Можно ли вставлять свои классы в разметку редактора markdown? Допустим имея стандартную разметку: <ul> <li></li> <li></l…
d
dblas55. Juli 2024 11:02
QML - Lektion 016. SQLite-Datenbank und das Arbeiten damit in QML Qt Здравствуйте, возникает такая проблема (я новичок): ApplicationWindow неизвестный элемент. (М300) для TextField и Button аналогично. Могу предположить, что из-за более новой верси…
k
kmssr8. Februar 2024 18:43
Qt Linux - Lektion 001. Autorun Qt-Anwendung unter Linux как сделать автозапуск для флэтпака, который не даёт создавать файлы в ~/.config - вот это вопрос ))
Qt WinAPI - Lektion 007. Arbeiten mit ICMP-Ping in Qt Без строки #include <QRegularExpressionValidator> в заголовочном файле не работает валидатор.
EVA
EVA25. Dezember 2023 10:30
Boost - statisches Verknüpfen im CMake-Projekt unter Windows Ошибка LNK1104 часто возникает, когда компоновщик не может найти или открыть файл библиотеки. В вашем случае, это файл libboost_locale-vc142-mt-gd-x64-1_74.lib из библиотеки Boost для C+…
Jetzt im Forum diskutieren
J
JacobFib17. Oktober 2024 03:27
добавить qlineseries в функции Пользователь может получить любые разъяснения по интересующим вопросам, касающимся обработки его персональных данных, обратившись к Оператору с помощью электронной почты https://topdecorpro.ru…
JW
Jhon Wick1. Oktober 2024 15:52
Indian Food Restaurant In Columbus OH| Layla’s Kitchen Indian Restaurant If you're looking for a truly authentic https://www.laylaskitchenrestaurantohio.com/ , Layla’s Kitchen Indian Restaurant is your go-to destination. Located at 6152 Cleveland Ave, Colu…
КГ
Кирилл Гусарев27. September 2024 09:09
Не запускается программа на Qt: точка входа в процедуру не найдена в библиотеке DLL Написал программу на C++ Qt в Qt Creator, сбилдил Release с помощью MinGW 64-bit, бинарнику напихал dll-ки с помощью windeployqt.exe. При попытке запуска моей сбилженной программы выдаёт три оши…
F
Fynjy22. Juli 2024 04:15
при создании qml проекта Kits есть но недоступны для выбора Поставил Qt Creator 11.0.2. Qt 6.4.3 При создании проекта Qml не могу выбрать Kits, они все недоступны, хотя настроены и при создании обычного Qt Widget приложения их можно выбрать. В чем может …

Folgen Sie uns in sozialen Netzwerken