2011年10月13日 星期四

在Qt下呼叫外部指令

這裡透過Qt的QProcess來呼叫外部程式,並且把結果顯示在QTextEdit
main.cpp

#include <QApplication>
#include <QTextEdit>
#include <QProcess>

int main (
int argc,
char *argv[]
)
{
QApplication app (argc, argv);

QTextEdit *TextEdit = new QTextEdit;

TextEdit->show ();

QProcess *proc = new QProcess;

// proc->startDetached ("ping 168.95.1.1");
proc->start ("ping 168.95.1.1");

if (proc->waitForFinished (-1)) {
TextEdit->append (proc->readAll ());
} else {
TextEdit->append ("Error");
}

return app.exec ();
}

不過,以上的程式有一個缺點,就是只有當外部指令結束之後,才會把結果顯示在QTextEdit裡面,這個時候會造成畫面當掉的感覺~

透過processEvent ()
可以適時的更新Qt的畫面~
但是仍會頓頓的~
main.cpp
#include <QApplication>
#include <QTextEdit>
#include <QProcess>

int main (
int argc,
char *argv[]
)
{
QApplication app (argc, argv);

QTextEdit *TextEdit = new QTextEdit;

TextEdit->show ();

QProcess *proc = new QProcess (TextEdit);

int x = 0;


// proc->startDetached ("ping 168.95.1.1 -t");
proc->start ("ping 168.95.1.1 -t");

while (proc->state () == QProcess::Running) {
x++;
// while (1) {
if (proc->waitForReadyRead (-1)) {
if (x == 10) {
proc->close ();
break;
}
TextEdit->append (proc->readAll ());
app.processEvents ();
} else {
TextEdit->append ("Error");
}
}

return app.exec ();
}


最後,透過signslot,當有資料可讀的時候,才去讀資料,終於解決會頓頓的情況了。
以下是sample code
MyTextEdit.h
#ifndef _MY_TEXT_EDIT_H_
#define _MY_TEXT_EDIT_H_

#include <QTextEdit>

class QProcess;

class MyTextEdit : public QTextEdit {
Q_OBJECT

public:
MyTextEdit (QWidget *parent = 0);
void StartCmd ();

public slots:
void AppendContextFromStandOut ();
void AppendContextFromStandErr ();
void AppendContext ();

private:
QProcess *MyProc;
};

#endif //#ifndef _MY_TEXT_EDIT_H_


MyTextEdit.cpp
#include "MyTextEdit.h"
#include <QProcess>

MyTextEdit::MyTextEdit (QWidget *parent) : QTextEdit (parent) {
// do nothing
}

void MyTextEdit::AppendContextFromStandOut () {
this->append (MyProc->readAllStandardOutput ());
}

void MyTextEdit::AppendContextFromStandErr () {
this->append (MyProc->readAllStandardError ());
}

void MyTextEdit::AppendContext () {
this->append (MyProc->readAll ());
}

void MyTextEdit::StartCmd () {

MyProc = new QProcess (this);

// connect (MyProc, SIGNAL(readyReadStandardOutput ()), this, SLOT(AppendContextFromStandOut ()));
// connect (MyProc, SIGNAL(readyReadStandardError ()), this, SLOT(AppendContextFromStandErr ()));
connect (MyProc, SIGNAL(readyRead ()), this, SLOT(AppendContext ()));

MyProc->start ("ping 168.95.1.1 -t");
}


main.cpp
#include <QApplication>
#include "MyTextEdit.h"

int main (
int argc,
char *argv[]
)
{
QApplication app (argc, argv);

MyTextEdit *TextEdit = new MyTextEdit;

TextEdit->show ();

TextEdit->StartCmd ();

return app.exec ();
}

最後輸出如下

原版的如下:


耶,怎麼多出那麼多空白行呢?
為了去了解,輸出的內容是什麼,使用一個很好用的函式QByteArray::toHex ()
有以下三點發現
1. Qt還是使用Unix換行的方式,只有0x0a("\n");而在Windows下的換行為0x0a("\r"),0x0d("\n")。因此,必需要移除掉多餘的0x0d
2. 我們由QProcess讀取出來的output本身就有換行符號(0x0a),而每做一個QTextEdit::append ()前,也會跳到新的一行,因此,QTextEdit::append ()多的這一個換行就變成是多餘的了~所以,必需用QTextEdit::insertPlainText ()來取代
3. 因為,QTextEdit::insertPlainText ()是由目前鼠標的位置直接加入字串。所以必需另外,把QTextEdit設定為disable,避免讓使用著移動滑鼠來改變鼠標的位置。
只需要修改MyTextEdit.cpp,程式碼如下:
#include "MyTextEdit.h"
#include <QProcess>
#include <QChar>

MyTextEdit::MyTextEdit (QWidget *parent) : QTextEdit (parent) {
this->setEnabled (false);
}

void MyTextEdit::AppendContextFromStandOut () {
this->append (MyProc->readAllStandardOutput ());
}

void MyTextEdit::AppendContextFromStandErr () {
this->append (MyProc->readAllStandardError ());
}

void MyTextEdit::AppendContext () {

QByteArray TempByteArray(MyProc->readAll ());

QString TempString(TempByteArray);

TempString.remove (QChar(0x0d));

// this->append (TempByteArray.toHex ());
// this->append (TempString);
this->insertPlainText (TempString);

}

void MyTextEdit::StartCmd () {

MyProc = new QProcess (this);

// connect (MyProc, SIGNAL(readyReadStandardOutput ()), this, SLOT(AppendContextFromStandOut ()));
// connect (MyProc, SIGNAL(readyReadStandardError ()), this, SLOT(AppendContextFromStandErr ()));
connect (MyProc, SIGNAL(readyRead ()), this, SLOT(AppendContext ()));

MyProc->start ("ping 168.95.1.1");
}

結果如下:


但是,直接把0x0d("\r")這字元移除掉,看起來不是很好的辦法,理論上應該是把"\r\n"直接取代成"\n"比較合理
修改MyTextEdit.cpp結果如下:
#include "MyTextEdit.h"
#include <QProcess>
#include <QChar>

MyTextEdit::MyTextEdit (QWidget *parent) : QTextEdit (parent) {
this->setEnabled (false);
}

void MyTextEdit::AppendContextFromStandOut () {
this->append (MyProc->readAllStandardOutput ());
}

void MyTextEdit::AppendContextFromStandErr () {
this->append (MyProc->readAllStandardError ());
}

void MyTextEdit::AppendContext () {

QByteArray TempByteArray(MyProc->readAll ());

QByteArray WinNewLine ("\r\n"), UnixNewLine ("\n");

TempByteArray.replace (WinNewLine, UnixNewLine);

QString TempString(TempByteArray);

// this->append (TempByteArray.toHex ());
// this->append (TempString);
this->insertPlainText (TempString);

}

void MyTextEdit::StartCmd () {

MyProc = new QProcess (this);

// connect (MyProc, SIGNAL(readyReadStandardOutput ()), this, SLOT(AppendContextFromStandOut ()));
// connect (MyProc, SIGNAL(readyReadStandardError ()), this, SLOT(AppendContextFromStandErr ()));
connect (MyProc, SIGNAL(readyRead ()), this, SLOT(AppendContext ()));

MyProc->start ("ping 168.95.1.1");
}


在繼續補足其它功能的時候,意外發現,我已經把視窗關掉了,為什麼我執行的外部指令還在執行哩~

原因就是,我在main.cpp裡面是宣告一個MyTextEdit的指標變數,並且指到我建立的記憶體空間(new QTextEdit),當主程式關掉之後,MyTextEdit的指標變數會release給系統(因為,存活的範圍只有在main的{}裡面),但是,MyTextEdit建立的記憶體空間就不會release給系統,除非delete掉這一個空間。

要解決一個方法,就是在main.cpp裡面,宣告一個物件而不是宣告一個指標來指到一個要求的記憶體位址。
main.cpp
#include <QApplication>
#include "MyTextEdit.h"

int main (
int argc,
char *argv[]
)
{
QApplication app (argc, argv);

MyTextEdit TextEdit;

TextEdit.show ();

TextEdit.StartCmd ();

return app.exec ();
}

MyTextEdit.h
#ifndef _MY_TEXT_EDIT_H_
#define _MY_TEXT_EDIT_H_

#include <QTextEdit>

class QProcess;

class MyTextEdit : public QTextEdit {
Q_OBJECT

public:
MyTextEdit (QWidget *parent = 0);
void StartCmd ();
bool IsScrollDown ();

public slots:
void AppendContext ();
void MoveScrollDown ();

private:
QProcess *MyProc;
};

#endif //#ifndef _MY_TEXT_EDIT_H_

MyTextEdit.cpp
#include "MyTextEdit.h"
#include <QProcess>
#include <QChar>
#include <QScrollBar>
#include <QDebug>

MyTextEdit::MyTextEdit (QWidget *parent) : QTextEdit (parent) {
MyProc = NULL;
setReadOnly (true);
// setTextInteractionFlags (textInteractionFlags() & Qt::NoTextInteraction);
// setTextInteractionFlags(Qt::TextSelectableByMouse | Qt::TextSelectableByKeyboard);
}

void MyTextEdit::AppendContext () {

bool KeepDownScroll = false;

int OrgScrollBarValue = 0;

//
// Get the output string
//

QByteArray AppendByteArray(MyProc->readAll ());

//
// Get the original ScrollBar value
//

OrgScrollBarValue = verticalScrollBar ()->value ();

QByteArray WinNewLine ("\r\n"), UnixNewLine ("\n");

//
// Replace "\r\n" by "\n"
//

AppendByteArray.replace (WinNewLine, UnixNewLine);

QString AppendString(AppendByteArray);

// this->append (AppendByteArray.toHex ());

if (IsScrollDown ()) {
if (!(textCursor ().hasSelection () || textCursor ().hasComplexSelection())) {
//
// When user select some range or the scroll is already under the buttom
// set the scroll under the buttom
//

KeepDownScroll = true;
}
}

//
// In order to append string in the end of text
// have to get the end cursor of the text
// After append the string, return the cursor to the original status
//

QTextCursor EndCursor, OriginalCursor;
EndCursor = OriginalCursor = textCursor ();
EndCursor.movePosition (QTextCursor::End);
setTextCursor (EndCursor);

//
// Append the string in the end of text
//

insertPlainText (AppendString);

//
// return the cursor to the original status
//

setTextCursor (OriginalCursor);

//
// Set the scrollbar value be the original value
//

QScrollBar *ScrollBar = verticalScrollBar ();
ScrollBar->setValue (OrgScrollBarValue);

//
// When user select some range or the scroll is already under the buttom
// set the scroll under the buttom
//

if (KeepDownScroll) {
MoveScrollDown ();
}

}

void MyTextEdit::StartCmd () {

MyProc = new QProcess (this);

connect (MyProc, SIGNAL(readyRead ()), this, SLOT(AppendContext ()));

MyProc->start ("ping 168.95.1.1 -t");
}

void MyTextEdit::MoveScrollDown () {

// Method 1
// QTextCursor c = textCursor ();
// c.movePosition (QTextCursor::End);
// setTextCursor (c);

// Method 2
QScrollBar *ScrollBar = verticalScrollBar ();
ScrollBar->setValue (ScrollBar->maximum ());

}

bool MyTextEdit::IsScrollDown () {

QScrollBar *ScrollBar = verticalScrollBar ();

if (ScrollBar->value () == ScrollBar->maximum ()) {
return true;
} else {
return false;
}
}


結果如下:
1. 當原本右邊的ScrollBar原本就在底部的話,就算文字內容增加,ScrollBar仍會保持在底部

2. 當使用者有圈選內容的話,就算文字內容增加,ScrollBar會保持在原來的位置,若原本的ScrollBar是在底部的話,會因為文字內容增加,而讓ScrollBar離開底部

3. 當使用者移動ScrollBar離開底部時,當文字內容增加,ScrollBar仍會保持在原來的位置。但當使用者把ScrollBar拉回底部時,當文字內容增加,ScrollBar會依然保持在底部。


參考資料:
QT下实现对linux 的Shell 调用 的几种方法 QProcess AND QThread
QTextEdit Class Reference
QProcess Class Reference
QProcess运行外部程序
怎么处理QT的UI界面假死的情况
QT程序 避免假死、无反应现象
QT中使用QProcess启用外部程序||exe
关于窗口调用外部程序及接收返回内容的调试
QT下解决换行符、回车符与Windows不一致的问题
解决QProcess對象調用execute執行cmd命令不支持中文和空格的問題
VCard
Qt - QTextEdit自動捲到底
QT中使用QProcess启用外部程序

沒有留言: