問題
これまで、C++でデータ処理部分を書いて、GUIは、Javaに任せてきた。両者の間は、簡便にUDP データグラム通信でやっていたが、何かと面倒だった。そこで、両方C++で処理できるように、QtのGUIを使う事にした。発想は、Javaと似ているので、楽だと思っていたらスレッドの処理で躓いた。
スレッドの中から、GUIのテキスト表示アイテムをよびだして逐次書き込むところで、数日を要して、四苦八苦した。C++ネイティブのようにも、Javaのようにもいかない。最終的に、解決したようでもあるので、ここに記録しておこうと思った。
メイン
メイン関数は特に問題はない。Qtの標準仕様だ。
#include "widget.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Widget w;
w.show();
return a.exec();
}
ここでは、TopレベルのGUIをQWidgetを継承したWidgetにしている。
インタフェースクラス
GUIのクラスは次のようになる。
#ifndef WIDGET_H
#define WIDGET_H
#include "work.h"
#include <QWidget>
#include <QTextBrowser>
class Widget : public QWidget
{
Q_OBJECT
public:
Widget(QWidget *parent = nullptr);
~Widget();
private slots:
void close_widget();
void start_thread();
void stop_thread();
private:
QTextBrowser* console;
Work* work;
};
#endif // WIDGET_H
一つのポイントは、Widgetクラスに、直接、スレッド処理をさせないということだ。色々試したが、ことごとく失敗した。うまくやればいけるのかもしれないが、見つからなかった。だから、普通のウィンドウ処理になっている。
#include "widget.h"
#include "work.h"
#include <QVBoxLayout>
#include <QPushButton>
#include <QtDebug>
Widget::Widget(QWidget *parent)
: QWidget(parent)
{
resize(500,400);
QVBoxLayout* layout = new QVBoxLayout();
QPushButton* startBtn = new QPushButton("Start Thread",this);
layout->addWidget(startBtn);
QPushButton* stopBtn = new QPushButton("Stop Thread",this);
layout->addWidget(stopBtn);
console = new QTextBrowser();
layout->addWidget(console);
QPushButton* closeBtn = new QPushButton("Close",this);
closeBtn->setFixedWidth(100);
layout->addWidget(closeBtn);
setLayout(layout);
connect(closeBtn, &QPushButton::clicked, this, &Widget::close_widget);
connect(startBtn, &QPushButton::clicked, this, &Widget::start_thread);
connect(stopBtn, &QPushButton::clicked, this, &Widget::stop_thread);
}
void Widget::start_thread()
{
qDebug() << Q_FUNC_INFO;
work = new Work();
work->StartThread(console);
}
void Widget::stop_thread()
{
qDebug() << Q_FUNC_INFO;
work->stopThread();
}
void Widget::close_widget()
{
close();
}
Widget::~Widget()
{
delete console;
delete work;
}
スレッドの開始ボタンと終了ボタンいつづいて、テキストブラウザーのGUIアイテムがインスタンスかされ、レイアウトに並べている。そして、ボタン類をシグナル・スロットで繋げている。一つだけ注意することは、スレッドからの出力を逐次表示するQTextBrowserのインスタンスのポインタを、クラス変数にしていることだ。スレッドの実態を担うクラス Work は別に定義している。
ウィンドウは次のように表示される。
スレッド実行クラス
スレッドの実行クラスは次のようになる。
#ifndef WORK_H
#define WORK_H
#include <QObject>
#include <QTextBrowser>
class Work: public QObject
{
Q_OBJECT;
public:
Work();
~Work();
void Loop (QTextBrowser *browser);
void StartThread (QTextBrowser *browser);
void stopThread();
signals:
void value_changed(QString location);
private:
QThread* thread1;
};
#endif // WORK_H
#include "work.h"
#include <QThread>
#include <unistd.h>
#include <QtDebug>
Work::Work()
{
}
void Work::Loop (QTextBrowser *browser)
{
long count = 0;
while (true) {
QString data = browser->toPlainText();
emit value_changed(data+" "+QString::number(count));
if (QThread::currentThread()->isInterruptionRequested())
{
return ;
}
count++;
usleep(200000);
}
qDebug() << "Thread Stopped.. ";
}
void Work::StartThread (QTextBrowser *browser)
{
connect(this, &Work::value_changed, browser, &QTextBrowser::setText);
thread1 = QThread::create(std::bind(&Work::Loop, this, browser));
thread1->start();
}
void Work::stopThread(){
thread1->requestInterruption();
}
Work::~Work()
{
}
見てもわかるように、ポイントは、signal/slotでデータをスレッドとやり取りしていることである。四苦八苦していたのは、QTextBrowserをどこでインスタンス化するのか、メインスレッドかサブスレッドか、その呼び出しをどうするかだった。結果的に、ネットのサイトの質疑応答を参考にして上記のようにすれば良いことがわかった。
参考にしたサイトは、
https://stackoverflow.com/questions/62909888/qt-how-to-utilize-qwidget-with-qthread
です。
出力状況は次のような感じです。
誤り、改善の可能性などありましたら、ご教示いただければ幸いです。