Creating QT Application as GUI for existing consol

2020-02-02 02:28发布

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() command, but i need to interact with the existing application via command line.

The system() command blocks the GUI when i start the existing executable. How can i run this executable in the background and trigger some inputs via my own GUI-Elements such as a button?

I want to simplify the usage of this command line tool for some of my colleagues.

It would be mainly used on windows.

Thanks for your help.

2条回答
Melony?
2楼-- · 2020-02-02 02:46

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:

  • usage of standard input
  • usage of standard output
  • usage of standard error
  • certain specific time consuming behavior (to see whether and when the parent process reacts).

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:

Snapshot of testQProcessIO (build with VS2013, started from Explorer)  Snapshot of testQProcessIO (build with g++, started from cygwin/bash)

查看更多
ら.Afraid
3楼-- · 2020-02-02 02:48

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.

查看更多
登录 后发表回答