Blocking serial port. QSerialPort + QThread.

Qt, QSerialPort, QThread

I had the task of writing software for controlling the radiator of the X-ray. Namely: realize the protocol of data transfer between the PC and the radiator of the X-ray and create the user-defined functions "Set parameters", "Enable X-ray", "Switch off X-ray".

The X-ray radiator is controlled by data transfer via the serial port, which is also called a COM port, but it is applicable only for Windows.

Qt has the QSerialPort class, which provides functions for accessing the serial port.
I want to show you my implementation of this task.

Project structure


Emitter.pro - project profile

Headers:

  • emitter.h - header file of the emitter class.
  • mainwindow.h - header file of the main program window.

Sources:

  • emitter.cpp - implementation file of the emitter class.
  • main.cpp
  • mainwindow.cpp - implementation file of the main program window.

Forms:

  • mainwindow.ui - file form of the main program window

Emitter class

In the project's profile, we add serialport to use the QtSerialPort module.

QT       += core gui serialport

emitter.hpp

#ifndef EMITTER_H
#define EMITTER_H

#include <QSerialPort>
#include <QTimer>

class Emitter : public QObject
{
    Q_OBJECT
public:
    explicit Emitter(QObject *parent = 0);
    // Connection Test Function.
    bool isConnected() const;

public slots:
    // Slot for setting parameters.
    void setFeatures(int voltage, int current, int workTime, int coolTime);
    // X-ray inclusion slot.
    void turnOnXRay();
    // X-ray shutdown slot.
    void turnOffXRay();

private:
    enum class Command: quint8; // Announcement of the transfer
    // Connection function.
    void connectToEmitter();
    // The most important function that implements the exchange protocol with the emitter.
    QByteArray writeAndRead(Command com, quint8 y1 = 0, quint8 y2 = 0, quint8 *m = nullptr);
    // Emitter command: Emitter status.
    bool commandS();
    // Emitter command: Set parameters.
    void commandP(quint16 volt, quint16 curr, quint16 workTime, quint16 coolTime);
    // Emitter command: Switch on the emitter.
    void commandN();
    // Emitter command: Switch off the emitter.
    void commandF();

    const quint8 STARTBYTE = 0x40;  // Beginning of the parcel.
    const quint8 DEV = 0;  // Emitter ID.

    // Available commands sent to the radiator. It is sent by the third byte in the parcel.
    enum class Command : quint8
    {
        S = 0x53, // Command request status.
        P = 0x50, // Command for setting parameters.
        N = 0x4e, // Command to turn on the X-ray.
        F = 0x46  // X-ray shutdown command.
    };

    // Emitter messages.
    enum class MessageCommandS : quint8
    {
        OK = 0x00, // All Oк.
        XRAY_ON = 0x01, // Emission included.
        XRAY_STARTING = 0x02, // Emitter output to the mode.
        XRAY_TRAIN = 0x03, // There is training.
        COOLING = 0x04 // Cooling.
    };

    // Emitter errors.
    enum class ErrorCommandS : quint8
    {
        NO_ERROR = 0x00, // Without errors
        XRAY_CONTROL_ERROR = 0x01, // X-ray tube control error.
        MODE_ERROR = 0x02, // Error setting the specified mode.
        VOLTAGE_ERROR = 0x03, // Exceeding the voltage threshold.
        CURRENT_ERROR = 0x04, // Exceed the current threshold.
        PROTECTIVE_BOX_ERROR = 0x05, // The protective box is open.
        LOW_SUPPLY_VOLTAGE = 0x06, // Low supply voltage.
        DISCONNECTION = 0x07, // No confirmation of connection (more than 1 s)
        OVERHEAT = 0x08 // Overheat.
    };

    QSerialPort *m_pSerialPort;
    bool m_isConnected;

    QTimer *m_pTimerCheckConnection;
};

#endif // EMITTER_H

"Communication" with the radiator occurs according to a certain protocol.

Brief description of the radiator protocol:

  1. The host is the computer. He is always the first to send a command. When receiving each parcel, the microprocessor of the device must send back the corresponding response command.
  2. Each packet isas follows:
    @ dev com y1 y2 m1 ... mn CRC
    @ - start byte, the sign of the beginning of the transmission (always equal to 0x40).
    dev - Device ID. ID controller and computer must be different.
    com is a command.
    y1 - The least significant byte of the word that determines the number of data bytes to send.
    y2 - high byte of a word. which determines the number of data bytes sent.
    m1 - first byte of data.
    mn - the last byte of the data.
    CRC -byte of the checksum. The checksum is considered as the sum of all bytes, beginning with the first byte of the instruction and ending with the last byte of the data.

Work with the serial port will be in synchronous mode, i.e. using the write and waitForBytesWritten functions for writing and waitForReadyRead and readAll for reading.

emitter.cpp

#include "emitter.h"

#include <QDebug>
#include <QThread>

Emitter::Emitter(QObject *parent) : QObject(parent)
{
    // Инициализация последовательного порта.
    m_pSerialPort = new QSerialPort(this);
    m_pSerialPort->setPortName("COM3");
    // Скорость передачи данных. 19200 бит/с.
    m_pSerialPort->setBaudRate(QSerialPort::Baud19200);
    m_pSerialPort->setDataBits(QSerialPort::Data8);
    m_pSerialPort->setParity(QSerialPort::NoParity);
    m_pSerialPort->setStopBits(QSerialPort::OneStop);
    m_pSerialPort->setFlowControl(QSerialPort::NoFlowControl);

    /* In this radiator with the included X-ray, it is necessary, at least once a second,
     send a command to the status request to confirm that the connection is not disconnected.
     Otherwise, the x-ray will be turned off. To do this, create a timer with an interval of 1 second. */
    m_pTimerCheckConnection = new QTimer(this);
    m_pTimerCheckConnection->setInterval(1000);

    /* After a time of 1 s, the status request command is called.
     This is where the lambda function is used, not to create a slot.
     It would be possible to make commandS private slot, but in this case at connect
     through the old form of recording (on SIGNAL, SLOT)
     This slot would be accessible to external objects. */
    connect(m_pTimerCheckConnection, &QTimer::timeout, [this](){
        commandS();
    });

    // We connect the serial port.
    connectToEmitter();
}

bool Emitter::isConnected() const
{
    return m_isConnected;
}

void Emitter::setFeatures(int voltage, int current, int workTime, int coolTime)
{
    if (isConnected())
    {
        commandP(voltage, current, workTime, coolTime);
    }
}

void Emitter::turnOnXRay()
{
    if (isConnected())
    {
        commandN();
    }
}

void Emitter::turnOffXRay()
{
    if (isConnected())
    {
        commandF();
    }
}

void Emitter::connectToEmitter()
{
    if (m_pSerialPort->open(QSerialPort::ReadWrite))
    {
        // We are convinced that the radiator of the X-ray is connected to the serial port.
        m_isConnected = commandS();
        if (m_isConnected)
        {
            qDebug() << "The radiograph of the X-ray is connected.";
        }
        else
        {
            qDebug() << "A different device is connected to the serial port of the radiator of the X-ray!";
        }
    }
    else
    {
        qDebug() << "The serial port is not connected. ";
         m_isConnected = false;
    }
}

QByteArray Emitter::writeAndRead(Command com, quint8 y1, quint8 y2, quint8 *m)
{
    quint16 y = y2 * 256 + y1;

    // Data sent to the serial port.
    QByteArray sentData;
    sentData.resize(6 + y);
    sentData[0] = STARTBYTE;
    sentData[1] = DEV;

    // Checksum = the sum of all bytes, starting with the byte of the command.
    quint8 crc = 0;
    crc += sentData[2] = static_cast<quint8>(com);
    crc += sentData[3] = y1;
    crc += sentData[4] = y2;

    // If there are data bytes, then they are also included in the checksum.
    if (y && m)
    {
        for (int i = 0; i < y; ++i)
        {
            crc += sentData[5 + i] = m[i];
        }
    }

    // The last byte is the checksum.
    sentData[5 + y] = crc;

    // Write to the serial port and wait for up to 100 ms until the recording is done.
    m_pSerialPort->write(sentData);
    m_pSerialPort->waitForBytesWritten(100);

    // We go to sleep, waiting for the microcontroller of the radiator to process the data and answer.
    this->thread()->msleep(50);
    // We read the data from the emitter.
    m_pSerialPort->waitForReadyRead(50);
    return m_pSerialPort->readAll();
}

bool Emitter::commandS() // Command request status.
{
    // We send the emitter.
    QByteArray receivedData = writeAndRead(Command::S);

    /* Must come exactly 8 bytes.
     The answer should be: @ dev com 2 0 s e CRC.
     Otherwise the answer did not come from the radiator. */
    if (receivedData.size() == 8)
    {
        quint8 crc = 0;
        for (int i = 1; i < receivedData.size() - 1; ++i)
        {
            crc += receivedData[i];
        }
        if (crc != static_cast<quint8>(receivedData[receivedData.size() - 1]))
            qDebug() << "CRC does not match!" << crc << static_cast<quint8>(receivedData[receivedData.size() - 1]);

        // If the condition is correct, then our radiographer of the X-ray replied.
        if (receivedData[0] == STARTBYTE &&
            static_cast<Command>(receivedData.at(2)) == Command::S &&
            receivedData[3] == 2 &&
            receivedData[4] == 0)
        {
            MessageCommandS message = static_cast<MessageCommandS>(receivedData.at(5));
            switch (message)
            {
            case MessageCommandS::OK:
                qDebug() << "Everything is fine.";
                break;
            case MessageCommandS::XRAY_ON:
                qDebug() << "Radiation is on.";
                break;
            case MessageCommandS::XRAY_STARTING:
                qDebug() << "Output of radiation to the mode.";
                break;
            case MessageCommandS::XRAY_TRAIN:
                qDebug() << "There is training.";
                break;
            case MessageCommandS::COOLING:
                qDebug() << "Cooling.";
                break;
            }
            ErrorCommandS error = static_cast<ErrorCommandS>(receivedData.at(6));
            switch (error)
            {
            case ErrorCommandS::NO_ERROR:
                qDebug() << "No mistakes";
                break;
            case ErrorCommandS::XRAY_CONTROL_ERROR:
                qDebug() << "X-ray tube control error.";
                break;
            case ErrorCommandS::MODE_ERROR:
                qDebug() << "It is not possible to set the preset mode on the handset.";
                break;
            case ErrorCommandS::VOLTAGE_ERROR:
                qDebug() << "The voltage threshold is exceeded.";
                break;
            case ErrorCommandS::CURRENT_ERROR:
                qDebug() << "The current threshold is exceeded.";
                break;
            case ErrorCommandS::PROTECTIVE_BOX_ERROR:
                qDebug() << "The protective box is open.";
                break;
            case ErrorCommandS::LOW_SUPPLY_VOLTAGE:
                qDebug() << "Low supply voltage.";
                break;
            case ErrorCommandS::DISCONNECTION:
                qDebug() << "Lack of communication with the host (more than 1s).";
                break;
            case ErrorCommandS::OVERHEAT:
                qDebug() << "Overheat.";
                break;
            }
            // Return true, because the answer came from the radiator. 
            return true;
        }
    }
    else
    {
        qDebug() << "Error. The answer does not meet expectations.";
    }
    return false;
}

// Command to set parameters.
void Emitter::commandP(quint16 volt, quint16 curr, quint16 workTime, quint16 coolTime)
{
    quint8 m[8];

    // 8 bytes of data, 4 words. The first low byte of the word, the second highest byte of the word.
    m[0] = volt - (volt/256) * 256;
    m[1] = volt/256;
    m[2] = curr - (curr/256) * 256;
    m[3] = curr/256;
    m[4] = workTime - (workTime/256) * 256;
    m[5] = workTime/256;
    m[6] = coolTime - (coolTime/256) * 256;
    m[7] = coolTime/256;
    QByteArray receivedData = writeAndRead(Command::P, 8, 0, m);

    /* Must come exactly 14 bytes.
     The answer should be: @ dev com 8 0 volt curr workTime coolTime CRC.
     volt, curr, workTime, coolTime - words in 2 bytes.
     Otherwise the answer did not come from the radiator. */
    if (receivedData.size() == 14)
    {
        quint8 crc = 0;
        for (int i = 1; i < receivedData.size() - 1; ++i)
        {
            crc += receivedData[i];
        }
        if (crc != static_cast<quint8>(receivedData[receivedData.size() - 1]))
            qDebug() << "CRC does not match!" << crc << static_cast<quint8>(receivedData[receivedData.size() - 1]);

        // The response from the radiator is the current parameter values.
        if (receivedData[0] == STARTBYTE &&
            static_cast<Command>(receivedData.at(2)) == Command::P &&
            receivedData[3] == 8 &&
            receivedData[4] == 0)
        {
            quint16 receivedVolt = receivedData[5] + receivedData[6] * 256;
            quint16 receivedCurr = receivedData[7] + receivedData[8] * 256;
            quint16 receivedWorkTime = receivedData[9] + receivedData[10] * 256;
            quint16 receivedCoolTime = receivedData[11] + receivedData[12] * 256;
            qDebug() << "Данные : " << receivedVolt << receivedCurr << receivedWorkTime << receivedCoolTime;
        }
    }
    else
    {
        qDebug() << "Error. The answer does not meet expectations.";
    }
}

// X-ray shutdown command.
void Emitter::commandN()
{
    QByteArray receivedData = writeAndRead(Command::N);

    /* It should come exactly 6 bytes.
     The answer should be: @ dev com 0 0 CRC.
     Otherwise the answer did not come from the radiator. */
    if (receivedData.size() == 6)
    {
        quint8 crc = 0;
        for (int i = 1; i < receivedData.size() - 1; ++i)
        {
            crc += receivedData[i];
        }
        if (crc != static_cast<quint8>(receivedData[receivedData.size() - 1]))
            qDebug() << "CRC does not match!" << crc << static_cast<quint8>(receivedData[receivedData.size() - 1]);

        // Turn on the connection expiration timer if the X-ray is on.
        if (receivedData[0] == STARTBYTE &&
            static_cast<Command>(receivedData.at(2)) == Command::N &&
            receivedData[3] == 0 &&
            receivedData[4] == 0)
        {
            qDebug() << "X-rays included.";
            m_pTimerCheckConnection->start();
        }
    }
    else
    {
        qDebug() << "Error. The answer does not meet expectations.";
    }
}

void Emitter::commandF() // Turn off the X-ray.
{
    QByteArray receivedData = writeAndRead(Command::F);

    /* It should come exactly 6 bytes.
     The answer should be: @ dev com 0 0 CRC.
     Otherwise the answer did not come from the radiator. */
    if (receivedData.size() == 6)
    {
        quint8 crc = 0;
        for (int i = 1; i < receivedData.size() - 1; ++i)
        {
            crc += receivedData[i];
        }
        if (crc != static_cast<quint8>(receivedData[receivedData.size() - 1]))
            qDebug() << "CRC does not match!" << crc << static_cast<quint8>(receivedData[receivedData.size() - 1]);

        // Turn off the connection confirmation timer.
        if (receivedData[0] == STARTBYTE &&
            static_cast<Command>(receivedData.at(2)) == Command::F &&
            receivedData[3] == 0 &&
            receivedData[4] == 0)
        {
            qDebug() << "The X-ray is turned off.";
            m_pTimerCheckConnection->stop();
        }
    }
    else
    {
        qDebug() << "Error. The answer does not meet expectations.";
    }
}

mainwindow.h

Here one signal is declared for passing parameters, and the object-emitter-object-stream pair is created.

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QThread>

#include "emitter.h"

namespace Ui {
class MainWindow;
}

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    explicit MainWindow(QWidget *parent = 0);
    ~MainWindow();

signals:
    // This signal will be sent by pressing the "Set" button to transfer the parameters to the emitter.
    void setFeaturesRequested(int voltage, int current, int workTime, int coolTime);

private:
    Ui::MainWindow *ui;

    // Emitter and a separate stream in which it will work.
    QThread *m_pThread;
    Emitter *m_pEmitter;
};

#endif // MAINWINDOW_H

mainwindow.ui

Primitive design. But the best for demonstrating the work of the radiator is not required.
Widgets spinBox, spinBox_2, spinBox_3, spinBox_4 are renamed to spinBoxVoltage, spinBoxCurrent, spinBoxWorkTime, spinBoxCoolTime, respectively. The buttons pushButton, pushButton_2, pushButton_3 are renamed to pushButtonSet, pushButtonTurnOn, pushButtonTurnOff, respectively. This is done for the convenience of accessing these objects in the mainwindow.cpp implementation file.


mainwindow.cpp

Here the radiator object is moved to a separate stream. The operation of the user interface is also described.

#include "mainwindow.h"
#include "ui_mainwindow.h"

#include <QMessageBox>

MainWindow::MainWindow(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    m_pThread = new QThread(this);
    // You do not need to specify a parent. The parent will be the stream, when we move our object emitter into it.
    m_pEmitter = new Emitter;
    
    /* We move the radiator object into a separate stream so that the synchronous pending operations are not blocked
     the main GUI stream. Create a connection: Delete the radiator object when the flow ends.
     Run the thread.*/
    m_pEmitter->moveToThread(m_pThread);
    connect(m_pThread, SIGNAL(finished()), m_pEmitter, SLOT(deleteLater()));
    m_pThread->start();
    
    // Verify the connection.
    if (m_pEmitter->isConnected())
    {
        ui->pushButtonTurnOff->setEnabled(false);
    }
    else
    {
        ui->pushButtonSet->setEnabled(false);
        ui->pushButtonTurnOn->setEnabled(false);
        ui->pushButtonTurnOff->setEnabled(false);
        QMessageBox::critical(this, "Connection error", "Connect the X-ray radiator to the COM3 serial port"
                              " and restart the program.", QMessageBox::Ok);
    }

    // When you click on the "Enable" button, turn on the X-ray and switch the state of the buttons.
    connect(ui->pushButtonTurnOn, &QPushButton::clicked, m_pEmitter, &Emitter::turnOnXRay);
    connect(ui->pushButtonTurnOn, &QPushButton::clicked, [this](){
        ui->pushButtonTurnOn->setEnabled(false);
        ui->pushButtonTurnOff->setEnabled(true);
    });
    
    // When you click on the "Disable" button, turn off the X-ray and switch the state of the buttons.
    connect(ui->pushButtonTurnOff, &QPushButton::clicked, m_pEmitter, &Emitter::turnOffXRay);
    connect(ui->pushButtonTurnOff, &QPushButton::clicked, [this](){
        ui->pushButtonTurnOn->setEnabled(true);
        ui->pushButtonTurnOff->setEnabled(false);
    });

    // When you click on the "Set" button, all the relevant parameters are displayed in the spinBox.
    connect(ui->pushButtonSet, &QPushButton::clicked, [this](){
        emit setFeaturesRequested(ui->spinBoxVoltage->value(),
                                  ui->spinBoxCurrent->value(),
                                  ui->spinBoxWorkTime->value(),
                                  ui->spinBoxCoolTime->value());
    });
    connect(this, &MainWindow::setFeaturesRequested, m_pEmitter, &Emitter::setFeatures);
}

MainWindow::~MainWindow()
{
    // Before deleting the main window, we wait until the thread finishes.
    m_pThread->quit();
    m_pThread->wait(1000);

    delete ui;
}

Conclusion

As a result: A radiator class has been created, where encapsulation is observed. There are 4 public functions, 3 of which are slots. There are internal functions of the radiator, the operation of which is implemented in accordance with the described synchronous protocol. The use of the emitter object in pairing with the stream object is shown, which makes it possible not to block the main stream even if the read-write operations require delays.

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

    Nice article, in my opinion it's much clearer than the official example at http://doc.qt.io/qt-5/qtserialport-blockingmaster-example.html.
    I have the following question: what would be the most elegant way to programmatically set the port name, given that the application may offer a QComboBox that lists every serial port reachable by the host device? How would you do it?

    AK

    Thanks for reply. This is not hard to do. This described in "Terminal" example by Qt http://doc.qt.io/qt-5/qtserialport-terminal-example.html ,  in SettingsDialog http://doc.qt.io/qt-5/qtserialport-terminal-settingsdialog-cpp.html

    void SettingsDialog::fillPortsInfo()
    {
        m_ui->serialPortInfoListBox->clear();
        QString description;
        QString manufacturer;
        QString serialNumber;
        const auto infos = QSerialPortInfo::availablePorts();
        for (const QSerialPortInfo &info : infos) {
            QStringList list;
            description = info.description();
            manufacturer = info.manufacturer();
            serialNumber = info.serialNumber();
            list << info.portName()
                 << (!description.isEmpty() ? description : blankString)
                 << (!manufacturer.isEmpty() ? manufacturer : blankString)
                 << (!serialNumber.isEmpty() ? serialNumber : blankString)
                 << info.systemLocation()
                 << (info.vendorIdentifier() ? QString::number(info.vendorIdentifier(), 16) : blankString)
                 << (info.productIdentifier() ? QString::number(info.productIdentifier(), 16) : blankString);
    
            m_ui->serialPortInfoListBox->addItem(list.first(), list);
        }
    
        m_ui->serialPortInfoListBox->addItem(tr("Custom"));
    }
    a

    Thanks for the answer. But I meant to ask a different thing. I already knew about QSerialPortInfo, but I wanted to know what would be the best way of setting the current portname in the Emitter class. I'd like the update to be thread-safe, since Emiter extends QThread. What I'm asking is if something like the following is the right thing to do:

    // emitter.h
    void Emitter::setPortName(const QString& portName) {
        m_pSerialPort->setPortName(portName);
    }
    
    // inside some view file with a pointer to an instance of Emitter
    Emitter* emitter = ...
    QComboBox* portNamesComboBox = ... // assume it contains a list of portNames extracted from QSerialPortInfo
    QPushButton* changePortBtn = new QPushButton(tr("Change"));
    
    connect(changePortBtn , QPushButton::clicked, this, [this,portNamesComboBox]() {
        auto newPortName  = portNamesComboBox.currentText();
        emitter->setPortName(newPortName);
    });
    AK

    Class Emitter does not extend QThread. They work in pair. They do not inherit each other.

    In the article i do so:

    connect(ui->pushButtonTurnOn, &QPushButton::clicked, m_pEmitter, &Emitter::turnOnXRay);
    connect(ui->pushButtonTurnOn, &QPushButton::clicked, [this](){
            ui->pushButtonTurnOn->setEnabled(false);
            ui->pushButtonTurnOff->setEnabled(true);
    });
    This works.

    Comments

    Only authorized users can post comments.
    Please, Log in or Sign up
    How to become an author?

    Contribute to the evolution of the EVILEG community.

    Learn how to become a site author.

    Learn it
    Donate

    Good day, Dear Users!!!

    I am Evgenii Legotckoi, developer of EVILEG. And it is my hobby project, which helps to learn programming another programmers and developers

    If the site helped you, and you want also support the development of the site, than you can donate by following ways

    PayPalYandex.Money
    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 Timeweb
    MN
    May 25, 2020, 11:33 a.m.
    Mitja Nagibin

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

    • Result:50points,
    • Rating points-4
    f
    May 25, 2020, 5:05 a.m.
    falcon

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

    • Result:66points,
    • Rating points-1
    jm
    May 25, 2020, 3:30 a.m.
    just maks

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

    • Result:80points,
    • Rating points4
    Last comments
    May 26, 2020, 6:51 a.m.
    Evgenij Legotskoj

    Qt/C++ - Lesson 004. QSqlTableModel – How to present the table from database?

    У вас база данных не открылась Исправьте путь к базе данных на свой корректный в следующих методах void DataBase::connectToDataBase() bool DataBase::openDataBase()
    T1
    T1
    May 26, 2020, 6:22 a.m.
    Tima 1

    Qt/C++ - Lesson 004. QSqlTableModel – How to present the table from database?

    полностью повторил структору проекта. В форму дабавил tableView. Но при запуске получаю форму только с пустым tableView. Можете подсказать в чем пробелма?
    May 26, 2020, 6:02 a.m.
    Evgenij Legotskoj

    Qt/C++ - Lesson 004. QSqlTableModel – How to present the table from database?

    Потому что это файл который нужно создать, а не библиотека. В статье есть содержание этого файла. Добавляйте в проект. Копируйте содержимое из статьи.
    T1
    May 26, 2020, 6 a.m.
    Tima 1

    Qt/C++ - Lesson 004. QSqlTableModel – How to present the table from database?

    не удается подключиить библеотеку include "database.h" выдает ошибку. Можете помочь?
    Now discuss on the forum
    May 26, 2020, 5:16 a.m.
    BlinCT

    Отсутствие драйвера SQLite в пакете Qt 4 на Linux

    Вот честно непонимаю почему до сих пор используют qt4, там же столько всего отсутствует, много фишек и возможностей нету там. То есть используя такое старье приходится много писать самому а не и…
    DK
    May 26, 2020, 2:24 a.m.
    Dzhon Kofi

    Disable autoscroll

    такие естественные решения все перепробовал. Получилось вчера так: const int maximumScroll = ui->_samples->verticalScrollBar()->maximum();const int sliderPos = ui->_samp…
    May 26, 2020, 12:43 a.m.
    Ruslan Polupan

    Посоветуйте новичку (базы данных и Qt, что учить)

    Без БД сейчас практически никуда. Поэтому SQL надо знать. SQLite самы простой вариант, но имхо лучще начать с бд клиент-сервер. Настроить сервер. Подключаться клиентом. Просто это помогает понят…
    EJ
    May 25, 2020, 2:42 p.m.
    Esteban José María

    Компиляция пустого проекта Qt Android

    qt 5.12.8 BUILD SUCCESSFUL in 42s 28 actionable tasks: 28 executed Android package built successfully in 68.251 ms. Ну, буду разбираться по-тихоньку. :)
    s
    May 25, 2020, 1:24 p.m.
    sander-007

    Использование файлов в памяти (memory file mapping)

    Добрый вечер, проблемы работы с файлом Exel нет вообще. Весь смысл в том чтобы не создавать на диске физический файл (требования безопасности), дабы потом не чистить. А так вопрос только в этом …
    About
    Services
    © EVILEG 2015-2020
    Recommend hosting TIMEWEB