在Windows上为现有的基于控制台的应用程序创建QT应用程序作为GUI

时间:2017-07-20 06:12:50

标签: c++ qt shell user-interface command-line

我正在尝试使用Qt为现有应用程序设置一个GUI,该应用程序应在Windows命令行中运行。它不只是运行应用程序 system()命令,但我需要通过命令行与现有应用程序进行交互。

当我启动现有可执行文件时,system()命令会阻止GUI。如何在后台运行此可执行文件并通过我自己的GUI元素(如按钮)触发一些输入?

我想为一些同事简化这个命令行工具的使用。

它主要用于Windows。

感谢您的帮助。

2 个答案:

答案 0 :(得分:1)

出于好奇,我和QProcess一起玩。

我真的很容易接受一切顺利和直接的事情(很遗憾地记得我们过去没有Qt的时候有多困难)。

因此,我可以提供我的小rather MCVE进行演示。

首先,我制作了一个简单的控制台应用程序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;
}

我这样做有一些提供的东西:

  • 标准输入的使用
  • 标准输出的使用
  • 使用标准错误
  • 某些特定的耗时行为(查看父流程是否以及何时作出反应)。

其次,我将Qt GUI应用程序testQProcessIO.cc作为包装器:

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

我在Windows 10(64位)上使用VS2013和Qt 5.9.2对其进行了编译和测试。

为了说明测试会话,我之后写了一个QMake项目testQProcessIO.pro

SOURCES = testQProcessIO.cc

QT += widgets

再次在cygwin上编译和测试:

$ 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

$

我的测试会话快照:

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

答案 1 :(得分:1)

我找到了满足我需求的解决方案,可以做我想做的事情。实际上我有点失望。我认为这会更复杂。

首先我必须说它是QtQuick应用程序..也许我应该早点说出来。

我只是将流程函数外包给另一个类。该类通过qmlRegisterType<>()函数传递给QML。我将来自进程(QProcess)的一些信号连接到我自己的类中的插槽,并编写了我自己的函数来处理从控制台应用程序读取/写入数据。使用QML - onClicked事件,我可以将我的参数和字符串传递给控制台应用程序。通过一些应用程序逻辑,我可以处理输入/输出请求和时间。

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

通过将Cpp-Class注册到QML,我能够通过QML中的点击事件触发读/写功能 - MouseAreaButton