i\'m trying to set up a GUI with Qt for an existing application which is meant to be run in the windows commandline. It\'s not just running the app with the
system()<
I found a solution for my needs and can do what i want to do.. Actually i'm a bit disappointed. I thought it would be something more complex.
First i have to say it's an QtQuick Application .. Maybe i should have said that earlier.
I simply outsourced the process functions to another class. This class is passed to QML via the qmlRegisterType<>()
function. I connected some signals from the process ( QProcess
) to slots in my own class and wrote my own functions to handle reading/writing data from and to the console application. With the QML-onClicked
events i can pass my parameters and strings to the console app. And with some application logic i can handle the in/out requests and timings.
WrapperClass.h
class WrapperClass: public QObject
{
Q_OBJECT
public:
explicit WrapperClass(QObject *parent = nullptr);
QProcess *process;
QString str_proc_output;
Q_INVOKABLE void startProcess();
Q_INVOKABLE void stopProcess();
Q_INVOKABLE QString getOutput();
Q_INVOKABLE void writeByte(QString str);
Q_INVOKABLE QString getAllOutput();
private:
signals:
public slots:
void mReadyRead();
void mReadyReadStandardOutput();
void mFinished(int code);
void mBytesWritten(qint64 written);
};
WrapperClass.cpp
WrapperClass::WrapperClass(QObject *parent) : QObject(parent)
{
process = new QProcess();
process->setProgram("untitled.exe");
process->setProcessChannelMode(QProcess::MergedChannels);
str_proc_output = "";
connect(process, SIGNAL(readyRead()), this, SLOT(mReadyRead()));
connect(process, SIGNAL(readyReadStandardOutput()), this, SLOT(mReadyReadStandardOutput()));
connect(process, SIGNAL(finished(int)), this, SLOT(mFinished(int)));
connect(process, SIGNAL(bytesWritten(qint64)), this, SLOT(mBytesWritten(qint64)));
}
void WrapperClass::startProcess() {
if(process->state() == QProcess::Running) {
stopProcess();
} else {
process->open(QProcess::ReadWrite);
}
}
void WrapperClass::stopProcess() {
process->close();
}
QString WrapperClass::getOutput() {
return str_proc_output;
}
QString WrapperClass::getAllOutput() {
QString str = process->readAll();
std::cout << str.toStdString() << std::endl;
return str;
}
void WrapperClass::writeByte(QString str) {
char cArr[str.length()] = {};
memcpy(cArr, str.toStdString().c_str(), str.length());
QByteArray arr = QByteArray(cArr, -1);
process->write(arr);
}
void WrapperClass::mReadyRead() {
QString s = QString(process->readAll());
std::cout << "ReadyRead: " << s.toStdString() << std::endl;
str_proc_output = s;
}
void WrapperClass::mReadyReadStandardOutput() {
QString s = QString(process->readAllStandardOutput());
std::cout << "ReadyReadStandardOutput: " << s.toStdString() << std::endl;
}
void WrapperClass::mFinished(int code) {
std::cout << "Process finished! (" << code << ')' << std::endl;
}
void WrapperClass::mBytesWritten(qint64 written) {
std::cout << "Bytes written: " << written << std::endl;
}
Main.cpp
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
qmlRegisterType<WrapperClass>("com.example.WrapperClass", 0, 1, "WrapperClass");
QQmlApplicationEngine engine;
engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
if (engine.rootObjects().isEmpty())
return -1;
return app.exec();
}
With registering the Cpp-Class to QML i'm able to trigger the read/write functions via Click-Events from QML-MouseArea
or Button
.
Out of curiosity, I played around with QProcess.
I'm really impressed how easy and straight forward everything works (remembering with horror how difficult it was when we did it in the past without Qt).
Thus, I can provide my little rather MCVE for demonstration.
First I made a simple console application testQProcessIOChild.cc
:
#include <chrono>
#include <iostream>
#include <sstream>
#include <string>
#include <thread>
using namespace std;
// inspired by:
// https://try.kotlinlang.org/#/Examples/Longer%20examples/99%20Bottles%20of%20Beer/99%20Bottles%20of%20Beer.kt
string bottlesOfBeer(int n)
{
switch (n) {
case 0: return "no more bottles of beer";
case 1: return "1 bottle of beer";
default: {
ostringstream outFmt;
outFmt << n << " bottles of beer";
return outFmt.str();
}
}
}
int main()
{
enum { delay = 1000 };
for (int n;;) {
cout << "Initial number of bottles (-1 ... finish): " << flush;
if (!(cin >> n)) {
cerr << "Input error!" << endl;
continue;
}
if (n < -1) {
cerr << "Illegal input!" << endl;
continue;
}
if (n < 0) break;
if (n > 100) {
cerr << "The ministry of health warns:" << endl
<< " Abuse of alcoholics may damage your health." << endl;
n = 99;
}
cout << "Go to the store and buy some more, "
<< bottlesOfBeer(n) << " on the wall." << endl;
while (n) {
this_thread::sleep_for(chrono::milliseconds(delay));
cout << bottlesOfBeer(n) << " on the wall, "
<< bottlesOfBeer(n) << '.' << endl
<< "Take one down, pass it around, ";
--n;
cout << bottlesOfBeer(n) << " on the wall." << endl;
}
this_thread::sleep_for(chrono::milliseconds(delay));
cout << "No more bottles of beer on the wall, no more bottles of beer."
<< endl;
}
return 0;
}
I did this to have something which provides:
Second I made the Qt GUI application testQProcessIO.cc
as wrapper:
// Qt header:
#include <QtWidgets>
const char *childProgram = "./testQProcessIOChild";
int main(int argc, char **argv)
{
qDebug() << QT_VERSION_STR;
// main application
QApplication app(argc, argv);
QProcess qProcessChild;
// GUI setup
QWidget qWin;
QGridLayout qGrid;
QPushButton qBtnStart(QString::fromUtf8("Start"));
qGrid.addWidget(&qBtnStart, 0, 0);
QPushButton qBtnStop(QString::fromUtf8("Stop"));
qBtnStop.setEnabled(false);
qGrid.addWidget(&qBtnStop, 0, 1);
QLabel qLblInput(QString::fromUtf8("Input: "));
qLblInput.setEnabled(false);
qGrid.addWidget(&qLblInput, 0, 2);
QLineEdit qInput;
qInput.setEnabled(false);
qGrid.addWidget(&qInput, 0, 3);
QTextEdit qTxtLog;
qTxtLog.setReadOnly(true);
qGrid.addWidget(&qTxtLog, 1, 0, 1, 4);
qGrid.setRowStretch(1, 1);
qGrid.setColumnStretch(3, 1);
qWin.setLayout(&qGrid);
qWin.show();
// install signal handlers
QObject::connect(&qBtnStart, &QPushButton::clicked,
[&](bool) {
qProcessChild.start(QString::fromLatin1(childProgram));
});
QObject::connect(&qBtnStop, &QPushButton::clicked,
[&](bool) {
qProcessChild.kill();
});
QObject::connect(&qInput, &QLineEdit::returnPressed,
[&](){
QString text = qInput.text() + '\n';
qProcessChild.write(text.toLatin1());
});
QObject::connect(&qProcessChild, &QProcess::started,
[&]() {
qBtnStart.setEnabled(false);
qBtnStop.setEnabled(true);
qLblInput.setEnabled(true);
qInput.setEnabled(true);
});
QObject::connect(&qProcessChild,
// cast needed because QProcess::finished() is polymorph
(void(QProcess::*)(int))&QProcess::finished,
[&](int) {
qBtnStart.setEnabled(true);
qBtnStop.setEnabled(false);
qLblInput.setEnabled(false);
qInput.setEnabled(false);
qTxtLog.clear();
});
QObject::connect(&qProcessChild, &QProcess::readyReadStandardOutput,
[&]() {
qTxtLog.append(qProcessChild.readAllStandardOutput());
});
QObject::connect(&qProcessChild, &QProcess::readyReadStandardError,
[&]() {
qTxtLog.append(qProcessChild.readAllStandardError());
});
// run application
return app.exec();
}
I compiled and tested this with VS2013 and Qt 5.9.2 on Windows 10 (64 bit).
To illustrate the test session I wrote a QMake project afterwards testQProcessIO.pro
:
SOURCES = testQProcessIO.cc
QT += widgets
and compiled and tested on cygwin again:
$ g++ -std=c++11 -o testQProcessIOChild testQProcessIOChild.cc
$ ./testQProcessIOChild
Initial number of bottles (-1 ... finish): -1
$ qmake-qt5 testQProcessIO.pro
$ make
g++ -c -fno-keep-inline-dllexport -D_GNU_SOURCE -pipe -O2 -Wall -W -D_REENTRANT -DQT_NO_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -I. -isystem /usr/include/qt5 -isystem /usr/include/qt5/QtWidgets -isystem /usr/include/qt5/QtGui -isystem /usr/include/qt5/QtCore -I. -I/usr/lib/qt5/mkspecs/cygwin-g++ -o testQProcessIO.o testQProcessIO.cc
g++ -o testQProcessIO.exe testQProcessIO.o -lQt5Widgets -lQt5Gui -lQt5Core -lGL -lpthread
$ ./testQProcessIO
5.9.2
$
Snapshots of my test sessions: