Blockieren der seriellen Schnittstelle. QSerialPort + QThread.

Ich bekam die Aufgabe, eine Software zur Steuerung des Röntgenstrahlers zu schreiben. Nämlich: Implementieren Sie ein Datenübertragungsprotokoll zwischen einem PC und einem Röntgenstrahler und erstellen Sie benutzerdefinierte Funktionen „Parameter einstellen“, „Röntgen aktivieren“, „Röntgen ausschalten“.

Der Röntgenstrahler wird durch Datenübertragung über einen seriellen Anschluss gesteuert, der auch als COM-Anschluss bezeichnet wird, dies gilt jedoch nur für Windows-Betriebssysteme.

Qt hat die QSerialPort-Klasse, die Funktionen für den Zugriff auf die serielle Schnittstelle bereitstellt.

Ich möchte Ihnen meine Umsetzung dieser Aufgabe zeigen.


Projektstruktur

Emitter.pro - Projektprofil

Überschriften:

  • emitter.h - Emitter-Klassen-Header-Datei.
  • mainwindow.h - Header-Datei des Hauptprogrammfensters.

Quellen:

  • emitter.cpp - Implementierungsdatei der Emitter-Klasse.
  • main.cpp
  • mainwindow.cpp - Implementierungsdatei des Hauptprogrammfensters.

Formen:

  • mainwindow.ui - Formulardatei des Hauptfensters des Programms

Emitterklasse

Wir fügen serialport zum Projektprofil hinzu, um das QtSerialPort-Modul verwenden zu können.

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);
    // Функция проверки соединения.
    bool isConnected() const;

public slots:
    // Слот установки параметров.
    void setFeatures(int voltage, int current, int workTime, int coolTime);
    // Слот включения рентгена.
    void turnOnXRay();
    // Слот выключения рентгена.
    void turnOffXRay();

private:
    enum class Command: quint8; // Объявление перечисления
    // Функция подключения.
    void connectToEmitter();
    // Важнейшая функция, реализующая протокол обмена с излучателем.
    QByteArray writeAndRead(Command com, quint8 y1 = 0, quint8 y2 = 0, quint8 *m = nullptr);
    // Команда излучателя: Статус излучателя.
    bool commandS();
    // Команда излучателя: Установка параметров.
    void commandP(quint16 volt, quint16 curr, quint16 workTime, quint16 coolTime);
    // Команда излучателя: Включить излучатель.
    void commandN();
    // Команда излучателя: Выключить излучатель.
    void commandF();

    const quint8 STARTBYTE = 0x40;  // Начало посылки.
    const quint8 DEV = 0;  // ID - излучателя.

    // Доступные нам команды, отправляемые излучателю. Отправляется третьим байтом в посылке.
    enum class Command : quint8
    {
        S = 0x53, // Команда запрос статуса.
        P = 0x50, // Команда установки параметров.
        N = 0x4e, // Команда включения рентгена.
        F = 0x46  // Команда выключения рентгена.
    };

    // Сообщения излучателя.
    enum class MessageCommandS : quint8
    {
        OK = 0x00, // Все ок.
        XRAY_ON = 0x01, // Излучение включено.
        XRAY_STARTING = 0x02, // Выход излучателя на режим.
        XRAY_TRAIN = 0x03, // Идет тренировка.
        COOLING = 0x04 // Охлаждение.
    };

    // Ошибки излучателя.
    enum class ErrorCommandS : quint8
    {
        NO_ERROR = 0x00, // Нет ошибок.
        XRAY_CONTROL_ERROR = 0x01, // Ошибка управления рентгеновской трубкой.
        MODE_ERROR = 0x02, // Ошибка установки заданного режима.
        VOLTAGE_ERROR = 0x03, // Превышение порога напряжения.
        CURRENT_ERROR = 0x04, // Превышение порога по току.
        PROTECTIVE_BOX_ERROR = 0x05, // Защитный бокс открыт.
        LOW_SUPPLY_VOLTAGE = 0x06, // Низкое питающее напряжение.
        DISCONNECTION = 0x07, // Отсутствие подтверждения соединения (более 1 с)ю
        OVERHEAT = 0x08 // Перегрев.
    };

    QSerialPort *m_pSerialPort;
    bool m_isConnected;

    QTimer *m_pTimerCheckConnection;
};

#endif // EMITTER_H

Die „Kommunikation“ mit dem Sender erfolgt nach einem bestimmten Protokoll.

Kurzbeschreibung des Senderprotokolls:

  1. Der Host ist der Computer. Er sendet immer zuerst den Befehl. Beim Empfang jeder Nachricht muss der Mikroprozessor der Maschine den entsprechenden Antwortbefehl zurücksenden.
  2. Jedes Paket ist wie folgt:
    @ dev com y1 y2 m1 ... mn CRC
    @ - Startbyte, ein Zeichen für den Beginn der Übertragung (immer gleich 0x40).
    dev - Geräte-ID. ID - Controller und Computer müssen unterschiedlich sein.
    de - Team.
    y1 ist das niederwertige Byte des Wortes, das die Anzahl der zu sendenden Datenbytes bestimmt.
    y2 - hohes Byte des Wortes. die die Anzahl der zu sendenden Datenbytes bestimmt.
    m1 ist das erste Datenbyte.
    mn ist das letzte Datenbyte.
    CRC - Prüfsummenbyte. Die Prüfsumme wird als Summe aller Bytes berechnet, beginnend mit dem ersten Befehlsbyte und endend mit dem letzten Datenbyte.

Die Arbeit mit der seriellen Schnittstelle erfolgt im synchronen Modus, d. H. Verwenden der Funktionen write und waitForBytesWritten zum Schreiben und waitForReadyRead und readAll zum Lesen.

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

    /* В данном излучателе при включенном рентгене нужно обязательно, хотя бы раз в секунду,
    посылать команду на запрос статуса, чтобы подтвердить, что соединение не разорвано.
    Иначе рентген будет выключен. Для этого создаем таймер с интервалом в 1 секунду. */
    m_pTimerCheckConnection = new QTimer(this);
    m_pTimerCheckConnection->setInterval(1000);

    /* По истечении времени 1 с вызывается команда запроса статуса.
    Здесь используется именно лямбда-функция, чтобы не создавать слот.
    Можно было бы сделать commandS private slot, но в этом случае при connect
    через старую форму записи (на макросах SIGNAL, SLOT)
    этот слот был бы доступен внешним объектам. */
    connect(m_pTimerCheckConnection, &QTimer::timeout, [this](){
        commandS();
    });

    // Подключаем последовательный порт.
    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))
    {
        // Убеждаемся, что в последовательный порт подключен именно излучатель рентгена.
        m_isConnected = commandS();
        if (m_isConnected)
        {
            qDebug() << "Излучатель рентгена подключен.";
        }
        else
        {
            qDebug() << "В последовательный порт излучателя рентгена подключено другое устройство!";
        }
    }
    else
    {
        qDebug() << "Последовательный порт не подключен.";
        m_isConnected = false;
    }
}

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

    // Данные, посылаемые в последовательный порт.
    QByteArray sentData;
    sentData.resize(6 + y);
    sentData[0] = STARTBYTE;
    sentData[1] = DEV;

    // Контрольная сумма = сумма всех байтов, начиная с байта команды.
    quint8 crc = 0;
    crc += sentData[2] = static_cast<quint8>(com);
    crc += sentData[3] = y1;
    crc += sentData[4] = y2;

    // Если есть байты данных, то их тоже включаем в контрольную сумму.
    if (y && m)
    {
        for (int i = 0; i < y; ++i)
        {
            crc += sentData[5 + i] = m[i];
        }
    }

    // Последний байт и есть контрольная сумма.
    sentData[5 + y] = crc;

    // Записываем в последовательный порт и ждем до 100 мс, пока запись не будет произведена.
    m_pSerialPort->write(sentData);
    m_pSerialPort->waitForBytesWritten(100);

    // Засыпаем, ожидая, пока микроконтроллер излучателя обработает данные и ответит.
    this->thread()->msleep(50);
    // Читаем данные от излучателя.
    m_pSerialPort->waitForReadyRead(50);
    return m_pSerialPort->readAll();
}

bool Emitter::commandS() // Команда запрос статуса.
{
    // Отправляем излучателю.
    QByteArray receivedData = writeAndRead(Command::S);

    /* Должно придти именно 8 байт.
    Ответ должен быть таким: @ dev com 2 0 s e CRC.
    Иначе ответ пришел не от излучателя. */
    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 не совпадает!" << crc << static_cast<quint8>(receivedData[receivedData.size() - 1]);

        // Если условие верно, то нам ответил наш излучатель рентгена.
        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() << "Все в порядке.";
                break;
            case MessageCommandS::XRAY_ON:
                qDebug() << "Излучение включено.";
                break;
            case MessageCommandS::XRAY_STARTING:
                qDebug() << "Выход излучения на режим.";
                break;
            case MessageCommandS::XRAY_TRAIN:
                qDebug() << "Идет тренировка.";
                break;
            case MessageCommandS::COOLING:
                qDebug() << "Охлаждение.";
                break;
            }
            ErrorCommandS error = static_cast<ErrorCommandS>(receivedData.at(6));
            switch (error)
            {
            case ErrorCommandS::NO_ERROR:
                qDebug() << "Нет ошибок";
                break;
            case ErrorCommandS::XRAY_CONTROL_ERROR:
                qDebug() << "Ошибка управления рентгеновской трубкой.";
                break;
            case ErrorCommandS::MODE_ERROR:
                qDebug() << "Невозможно установить заданный режим на трубке.";
                break;
            case ErrorCommandS::VOLTAGE_ERROR:
                qDebug() << "Превышен порог напряжения.";
                break;
            case ErrorCommandS::CURRENT_ERROR:
                qDebug() << "Превышен порог по току.";
                break;
            case ErrorCommandS::PROTECTIVE_BOX_ERROR:
                qDebug() << "Защитный бокс открыт.";
                break;
            case ErrorCommandS::LOW_SUPPLY_VOLTAGE:
                qDebug() << "Низкое питающее напряжение.";
                break;
            case ErrorCommandS::DISCONNECTION:
                qDebug() << "Отсутствие связи с хостом (более 1с).";
                break;
            case ErrorCommandS::OVERHEAT:
                qDebug() << "Перегрев.";
                break;
            }
            // Возвращаем true, т.к. ответ пришел от излучателя. 
            return true;
        }
    }
    else
    {
        qDebug() << "Ошибка. Ответ не соответствует ожиданиям.";
    }
    return false;
}

// Команда установить параметры.
void Emitter::commandP(quint16 volt, quint16 curr, quint16 workTime, quint16 coolTime)
{
    quint8 m[8];

    // 8 байт данных, 4 слова. Первый младший байт слова, второй старший байт слова.
    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);

    /* Должно придти именно 14 байт.
    Ответ должен быть таким: @ dev com 8 0 volt curr workTime coolTime CRC.
    volt, curr, workTime, coolTime - слова в 2 байта.
    Иначе ответ пришел не от излучателя. */
    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 не совпадает!" << crc << static_cast<quint8>(receivedData[receivedData.size() - 1]);

        // Ответом от излучателя являются текущие значения параметров.
        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() << "Ошибка. Ответ не соответствует ожиданиям.";
    }
}

// Команда выключения рентгена.
void Emitter::commandN()
{
    QByteArray receivedData = writeAndRead(Command::N);

    /* Должно придти именно 6 байт.
    Ответ должен быть таким: @ dev com 0 0 CRC.
    Иначе ответ пришел не от излучателя. */
    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 не совпадает!" << crc << static_cast<quint8>(receivedData[receivedData.size() - 1]);

        // Включаем таймер подверждения соединения, если рентген включен.
        if (receivedData[0] == STARTBYTE &&
            static_cast<Command>(receivedData.at(2)) == Command::N &&
            receivedData[3] == 0 &&
            receivedData[4] == 0)
        {
            qDebug() << "Рентген включен.";
            m_pTimerCheckConnection->start();
        }
    }
    else
    {
        qDebug() << "Ошибка. Ответ не соответствует ожиданиям.";
    }
}

void Emitter::commandF() //Выключить рентген.
{
    QByteArray receivedData = writeAndRead(Command::F);

    /* Должно придти именно 6 байт.
    Ответ должен быть таким: @ dev com 0 0 CRC.
    Иначе ответ пришел не от излучателя. */
    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 не совпадает!" << crc << static_cast<quint8>(receivedData[receivedData.size() - 1]);

        // Выключаем таймер подтверждения соединения.
        if (receivedData[0] == STARTBYTE &&
            static_cast<Command>(receivedData.at(2)) == Command::F &&
            receivedData[3] == 0 &&
            receivedData[4] == 0)
        {
            qDebug() << "Рентген выключен.";
            m_pTimerCheckConnection->stop();
        }
    }
    else
    {
        qDebug() << "Ошибка. Ответ не соответствует ожиданиям.";
    }
}

Hauptfenster.h

Hier wird ein Signal zum Übergeben von Parametern deklariert, und ein Objekt-Emitter-Objekt-Stream-Paar wird erstellt.

#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:
    // Этот сигнал будет высылаться при нажатии на кнопку "Задать", чтобы передать параметры в излучатель.
    void setFeaturesRequested(int voltage, int current, int workTime, int coolTime);

private:
    Ui::MainWindow *ui;

    // Излучатель и отдельный поток, в котором он будет работать.
    QThread *m_pThread;
    Emitter *m_pEmitter;
};

#endif // MAINWINDOW_H

mainwindow.ui

Primitives Design. Aber das Beste, um den Betrieb des Emitters zu demonstrieren, ist nicht erforderlich.
Die Widgets spinBox, spinBox_2, spinBox_3, spinBox_4 wurden in spinBoxVoltage, spinBoxCurrent, spinBoxWorkTime bzw. spinBoxCoolTime umbenannt. Die Schaltflächen pushButton, pushButton_2, pushButton_3 wurden in pushButtonSet, pushButtonTurnOn bzw. pushButtonTurnOff umbenannt. Dies wurde gemacht, um bequem auf diese Objekte in der Implementierungsdatei mainwindow.cpp zugreifen zu können.

Hauptfenster.cpp

Hier wird das Emitter-Objekt in einen separaten Thread verschoben. Auch die Bedienung der Benutzeroberfläche wird beschrieben.

#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);
    // Указывать родителя нет необходимости. Родителем станет поток, когда переместим в него наш объект излучателя.
    m_pEmitter = new Emitter;

    /* Перемещаем объект излучателя в отдельный поток, чтобы синхронные ожидающие операции не блокировали
    основной GUI-поток. Создаем соединение: Удаляем объект излучателя при окончании работы потока.
    Запускаем поток.*/
    m_pEmitter->moveToThread(m_pThread);
    connect(m_pThread, SIGNAL(finished()), m_pEmitter, SLOT(deleteLater()));
    m_pThread->start();

    // Проверка соединения.
    if (m_pEmitter->isConnected())
    {
        ui->pushButtonTurnOff->setEnabled(false);
    }
    else
    {
        ui->pushButtonSet->setEnabled(false);
        ui->pushButtonTurnOn->setEnabled(false);
        ui->pushButtonTurnOff->setEnabled(false);
        QMessageBox::critical(this, "Ошибка подключения", "Подключите излучатель рентгена в последовательный порт COM3"
                              " и перезапустите программу.", QMessageBox::Ok);
    }

    // При нажатии на кнопку "Включить" включать рентген и переключать состояние кнопок.
    connect(ui->pushButtonTurnOn, &QPushButton::clicked, m_pEmitter, &Emitter::turnOnXRay);
    connect(ui->pushButtonTurnOn, &QPushButton::clicked, [this](){
        ui->pushButtonTurnOn->setEnabled(false);
        ui->pushButtonTurnOff->setEnabled(true);
    });

    // При нажатии на кнопку "Выключить" выключать рентген и переключать состояние кнопок.
    connect(ui->pushButtonTurnOff, &QPushButton::clicked, m_pEmitter, &Emitter::turnOffXRay);
    connect(ui->pushButtonTurnOff, &QPushButton::clicked, [this](){
        ui->pushButtonTurnOn->setEnabled(true);
        ui->pushButtonTurnOff->setEnabled(false);
    });

    // При нажатии на кнопку "Задать", передаются все соответствующие параметры выставленные в 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()
{
    // Перед удалением основного окна, дожидаемся завершения работы потока.
    m_pThread->quit();
    m_pThread->wait(1000);

    delete ui;
}

Insgesamt

Als Ergebnis: Es wurde eine Emitterklasse geschaffen, bei der die Kapselung beachtet wird. Es gibt 4 öffentliche Funktionen, von denen 3 Slots sind. Es gibt interne Funktionen des Emitters, deren Betrieb gemäß dem beschriebenen synchronen Protokoll implementiert ist. Es zeigt die Verwendung eines Emitter-Objekts gepaart mit einem Thread-Objekt, das es ermöglicht, den Haupt-Thread nicht zu blockieren, selbst wenn Lese-Schreib-Operationen Verzögerungen erfordern.

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

Magst du es? In sozialen Netzwerken teilen!

a
  • 3. Mai 2018 06:49

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?

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

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

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.

Kommentare

Nur autorisierte Benutzer können Kommentare posten.
Bitte Anmelden oder Registrieren
Letzte Kommentare
A
ALO1ZE19. Oktober 2024 08:19
Fb3-Dateileser auf Qt Creator Подскажите как это запустить? Я не шарю в программировании и кодинге. Скачал и установаил Qt, но куча ошибок выдается и не запустить. А очень надо fb3 переконвертировать в html
ИМ
Игорь Максимов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> в заголовочном файле не работает валидатор.
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