背景
使用了 QXmpp 實作 IM,QXmpp 需要與 伺服器保持聯通,方法就是 ping。app 發送 ping 後固定時間内若沒有收到響應,則認為伺服器失連,則斷開連結;同樣,伺服器發送 ping 給 client,固定時間内若沒有收到響應,則認為 app 失聯,則斷開連結。
問題:
1、發送檔案時,需要阻塞使用者輸入,是以選擇 模态 顯示 QFileDialog。
2、模态顯示 QFileDialog 時,windows 下 QXmpp 阻塞、無法收發消息,導緻與 伺服器斷鍊。
分析:
QXmpp 收發消息,是通過 Qt event 實作的。當 模态顯示 QFileDialog 時,也會阻塞 event 線程,導緻本該傳給 QXmpp 的 event 阻塞,QXmpp 無法給 伺服器發送 pong,導緻伺服器斷開連結。
是以,隻要 QXmpp 能收到 event 事件就可以解決了。
方法有2:
1、非模态 QFileDialog,不阻塞 event loop。非模态顯示 QFileDialog,主界面覆寫一層,接收并過濾掉使用者的 鍵盤、滑鼠等事件
可行,但改動大,棄用。
2、event loop 阻塞,但仍然傳遞 event 給 QXmpp
建立 QFileDialog 子類,重載 exec() 方法,執行 QFileDialog::exec() 之前 啟動 QObject 定時器,QFileDialog不會阻塞自身的定時器事件,是以可在 QObject 的定時器事件裡傳遞 event。
查詢官網文檔:
void QCoreApplication::processEvents(QEventLoop::ProcessEventsFlags, flags = QEventLoop::AllEvents)
Processes all pending events for the calling thread according to the specified flags until there are no more events to process. You can call this function occasionally when your program is busy performing a long operation (e.g. copying a file).
即,調用 QCoreApplication::processEvents 可在阻塞情況下繼續傳遞 event 事件,參數 flags 可選:QEventLoop::ExcludeUserInputEvents,即,阻塞所有的使用者輸入事件。
可行,改動小,但要測試 processEvents 調用間隔,設為 50ms 時使用者感覺不出。
代碼:
class AKeyFileDialog : public QFileDialog {
Q_OBJECT
public:
AKeyFileDialog(QWidget *parent, Qt::WindowFlags f) : QFileDialog(parent, f) {}
explicit AKeyFileDialog(QWidget *parent = 0,
const QString &caption = QString(),
const QString &directory = QString(),
const QString &filter = QString()) : QFileDialog(parent, caption, directory, filter) {
}
void timerEvent(QTimerEvent *e) {
if (QApplication::hasPendingEvents()) {
QApplication::processEvents(QEventLoop::ExcludeUserInputEvents);
}
}
public Q_SLOTS:
virtual int exec() Q_DECL_OVERRIDE {
startTimer(50);
return QFileDialog::exec();
}
};
以上