QCoreApplication

        我們都知道,Qt是一個開源的C++庫,主要用來開發GUI程序,但同時,它也支持控制檯程序的開發。並且,這裏的控制檯程序又分爲Qt控制檯程序和純C++控制檯程序。其中,C++控制檯程序就沒什麼好說的了,就是我們大學的入門程序了,使用cout、stl這樣標準的C++組件;而Qt控制檯程序是和Qt GUI相對的一種程序,它處理可以進行一般的打印輸出外,也可以像GUI程序一樣,支持事件循環、信號和槽的特性。並且,Qt庫針對不同的應用程序類型,提供了不同的類來表示,比如,QCoreApplication表示Qt控制檯程序,QApplication 和 QGuiApplication 表示GUI程序。它們之間的關係爲QCoreApplication 繼承自最頂層的QObject,QGuiApplication 又繼承自QCoreApplication,QApplication又繼承自QGuiApplication。今天,我們主要來學習一下QCoreApplication類。那麼,我們就先新建一個Qt控制檯程序。

啓動Qt Creator,文件->新建文件或項目,選擇Qt Console Application類型,如下:



建好後的工程如下,非常簡單,只有一個工程文件和一個main函數源文件:



剛纔,我們說了,QCoreApplication類表示Qt控制檯程序,它承載了應用程序的事件循環,通過這個事件循環對所以來自操作系統和其他事件源,如網絡,數據庫,的事件進行處理和分發。同時,它還實現了應用程序的初始化和退出清理工作,以及系統級和應用級的常用設置。所以,Qt在生成main函數時就自動爲我們定義了已應用程序對象,其在程序運行的過程中,就代表了整個應用程序本身。但由於該對象是main函數的局部對象,不方便我們在其他地方使用,特別是對於GUI程序來說,我們可能需要在某個窗口類中使用到這個應用程序對象,所以,鑑於此,Qt又在全局空間爲我們提供了已應用程序指針,即qApp,該全局指針指向的就是main函數中定義的應用程序對象,我們可以在任何地方使用這個指針。當然,我們還可以使用QCoreApplication類中的一個靜態成員函數instance(),來獲得代表該應用程序的指針,該函數聲明如下:

QCoreApplication *QCoreApplication::instance()


下面我們就分別看一下QCoreApplication所具有的功能和常用的程序函數。

事件循環和事件處理

在上面QtCreator爲我們自動生成的代碼中,它先定義了一個QCoreApplication類的對象,緊接着就調用了exec() 成員函數,而Qt的事件循環就是開始於此函數的調用。並且,exec() 會一直運行到事件循環結束才返回,比如quit()或exit() 函數被調用時,而exec() 的返回的值即爲調用exit()時所傳入的值,如果是quit() 函數,則相當於exit(0)。另外,我們上面說過,該對象代表 了成功應用程序本身,也是它開啓了整個程序的事件循環,所以,一般情況下,我們推薦儘可能早的在main函數中創建出該對象。

QCoreApplication還爲我們提供了幾個方便的靜態程序函數。比如我們上面已經說過的instance() 函數,它可以返回應用程序對象的指針;sendEvent()、postEvent() 和sendPostedEvent() 用於發送或寄送事件;事件隊列中未處理的事件可以使用removePostedEvents() 或者 flush() 進行處理。

我們還可以將我們的某個操作連接到該類的quit()槽函數上,以此來實現應用程序的退出;也可以相應該類提供的aboutToQuit() 信號,在應用程序退出時做一些清理工作。

應用程序和庫路徑

我們可以使用applicationDirPath() 和 applicationFilePath() 函數來獲得應用程序的執行路徑;而應用程序中所使用的庫所在的路徑可以使用libraryPaths() 獲得,使用setLibraryPaths() 、addLibraryPath()、和removeLibraryPath() 函數進行修改、添加和刪除。

國際化和翻譯

當我們的軟件需要運行在不同的語言環境下時,我們要針對特定的語言環境來改變界面顯示,這是通過Qt的翻譯機制實現的。我們可以使用installTranslator() 和 removeTranslator()來爲應用程序安裝翻譯文件,也可以使用translate() 或 QObject::tr() 和 QObject::trUtf8()對程序中的字符串字面量進行翻譯。

處理命令行參數

還是根據上面QtCreator自動生成的代碼可以看出,在定義QCoreApplication類的對象時,將main函數表示命令行參數信息的argc、argv傳給了QCoreApplication的構造函數。此後,我們就可以使用arguments() 成員函數來訪問這些命令行參數。

其實,Qt庫中還專門爲命令行參數的解析提供了兩個類,一個是QCommandLineOption,表示一個命令行參數選項和值,一個是QCommandLineParser,表示用來解析命令行參數的解析器。關於它們的具體使用,大家可以參考Qt幫助文檔。

區域設置

我們知道,不同的國家和地區,在某些方面的表示是不同的,大家最常見的某過於貨幣的表示了,中國使用人民幣¥,美國使用美元$,英國使用英鎊£,等等。而對應到軟件開發中,除了貨幣,我們面對的更多的是數字的表示,浮點數中的小數點的表示,日期的表示,這些在不同的地區都是不同的。比如,對Qt庫來說,在Unix/Linux平臺上,默認情況下是使用系統的區域設置。而這會在我們使用POSIX函數時發生一些衝突,例如,當在進行float和string的數據類型轉換時。爲了解決這個問題,我們可以通過在實例化QCoreApplication之後調用POSIX函數setlocale(LC_NUMERIC, "C")來重置區域設置。同理,QApplication和QGuiApplication也類似。

常用函數

[signal] void QCoreApplication::aboutToQuit()

該信號會在應用程序將要退出事件循環時發出。比如,軟件在程序的其他地方調用了quit()函數,或者用戶直接關掉了整個桌面回話。通常,我們可以使用這個信號還對應用程序最一些最後的清理工作。但注意,在這個狀態時,不可能進行用戶交互了。還有,這個信號是私有信號,即我們可以在應用程序中響應這個信號,但不能主動發出這個信號。

[static] void QCoreApplication::addLibraryPath(const QString &path)
[static] QStringList QCoreApplication::libraryPaths()
[static] void QCoreApplication::removeLibraryPath(const QString &path)
[static] void QCoreApplication::setLibraryPaths(const QStringList &paths)
這幾個函數用來操作我們的應用程序所使用的庫的位置所在。默認情況下,Qt會去搜索INSTALL/plugins,目錄,其中INSTALL是Qt的安裝目錄。我們可以將我們自己開發的庫放到一個獨立的目錄中,然後使用上面的函數將該目錄設置爲或添加到Qt搜索路徑中。

當QCoreApplication的實例被析構時,庫搜索路徑會被重置爲默認路徑。


[static] QString QCoreApplication::applicationDirPath()
[static] QString QCoreApplication::applicationFilePath()
獲得應用程序可執行文件所在的目錄路徑和可執行文件本身的路徑。打印結果如下:



[static] int QCoreApplication::exec()
這個函數,應該是最重要的函數了,因爲是他開啓了主事件循環,帶動了整個程序的運行。exec() 會一直運行,知道exit() 函數被調用,其返回值就是exit() 函數的參數,如果是通過quit() 函數返回的,其返回值是0。注意,在Windows平臺上,當用戶註銷時,系統會在Qt關閉所有上層窗口之後總結該進程,因此,不能保證應用程序有時間退出事件循環並執行exec() 之後的代碼。所以,一般我麼推薦連接aboutToQuit() 信號去爲應用程序做一些清理工作,而不是直接把與清理工作相關的代碼放到exec() 後面,因爲這樣不一定會被執行到。

通常,我們通過使用0開啓一個QTimer來進行空閒處理(即當事件循環沒有事件要處理時,去執行一個特定的函數)。其實,更高級的空閒處理可以通過QCoreApplication的processEvents() 來實現。

[static] void QCoreApplication::flush()
該函數刷新平臺特定的事件隊列。

比如,如果你正在一個循環中進行圖形變換操作,在循環結束之前不會返回到事件循環,而你有想立即看到對圖形所做的改變,就可以調用這個函數。

[virtual] bool QCoreApplication::notify(QObject *receiver, QEvent *event)
這個函數和exec() 一樣,也是一個很重要的函數,應用程序就是通過這個函數將接收到的事件分發給特定的事件接受者,receiver->event(event)。其返回值就是接受者事件處理器的返回值。還有,應用程序會對所以線程中的所有事件調用這個函數。

其實,說到事件處理,在Qt中共有5中事件處理的方式,重寫該函數,只是其中之一。下面,我們簡單看一下這5 中事件處理方式。

1. 第一種,也是最常用,最通用,最簡單的方式,即在特定類中重寫基類的事件處理函數,比如paintEvent()、mousePressEvent()、mouseMoveEvent()。。。。。。

2. 第二種就是實現notify() 函數,這種方式功能強大,可以讓我們對程序中的事件處理進行完全的控制。但是,這種方式同一時間只能激活一個類,向它分發事件。
3. 在QCoreApplication的對象上安裝事件過濾器。這種事件過濾器是一種全局事件過濾器,可以處理所有控件的事件,所以這種方式的能力和重寫notify() 函數差不多;此外,我們可以在一個QCoreApplication應用程序對象上安裝多個這樣的事件過濾器。全局事件過濾器甚至能監視到發往被禁用的控件的鼠標事件。但是要注意,應用程序級別的事件過濾器只過濾哪些發往存活在主線程中的對象的事件。

4. 重寫QObject::event() 函數。這種方式還能讓我們攔截到tab鍵的按下事件。並且,這種方式的優先級要高於控件上的事件過濾器,即我們可以先與控件過濾器攔截到事件。

5. 在某個特定對象上安裝事件過濾器。這種事件過濾器能攔截到所有的事件,包括Tab和Shift+Tab的press事件,前提是他們沒有改變焦點控件。

最後要注意的是,如果重新了notify( )函數,就必須確保所有處理事件的線程在應用程序對象析構之前停止處理。這包括所使用的第三方庫開啓的線程,但不包括Qt自己的線程。


[static] bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
[static] void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority = Qt::NormalEventPriority)
其中,postEvent()類似於win32的postMessage() ,它把事件對象event及其接受者receiver放到事件隊列中,然後立即返回。注意,要投遞的事件對象必須在堆上進行分配,因爲事件隊列或接手該對象的所有權,並在事件分發到接受者後自動釋放該對象。所以,在事件對象被投遞之後,再訪問該對象也是不安全的。

當程序控制流再返回到主事件循環時,所有存儲在事件隊列中的事件會通過notify() 函數被髮送出去。

另外,根據函數聲明可以看出,在調用postEvent() 時,我們可以爲所投遞的事件指定一個優先級,事件隊列中的事件就是按該優先級被排序的。也就是說,具有高優先級的事件會被排在低優先級事件的後面。事件優先級可以是INT_MIN ~ INT_MAX 之間的任何整數。該函數是線程安全的

同理,sendEvent() 類似於win32的sendMessage() 函數,它直接把事件event通過notify()發送給接受者receiver。返回值就是接受者的事件處理器的返回值。事件對象不會在發送事件完成後自動被釋放,所以,一般的做法是在棧上定義該事件對象。例子如下:

  QMouseEvent event(QEvent::MouseButtonPress, pos, 0, 0, 0);
  QApplication::sendEvent(mainWindow, &event);

[static] void QCoreApplication::processEvents(QEventLoop::ProcessEventsFlags flags = QEventLoop::AllEvents)
[static] void QCoreApplication::processEvents(QEventLoop::ProcessEventsFlags flags, int maxtime)
在GUI程序開發中,如果遇到某個計算過程非常漫長,比如一個很大的循環,這會導致我們的界面在這段時間內無法正常處理用戶數據而出現界面假死的現象。通常,對於這種情況,我們會把該計算過程放到一個單獨的線程中。在Qt中,我們有了另一種方法,即在循環中調用上面的兩個方法之一。

processEvent() 會去處理所有屬於當前線程爲處理的事件。從而,可以在漫長的計算過程中抽出事件處理用戶的界面輸入,防止界面假死。

同時,根據其重載形式,我們也可以爲本次事件處理指定一個事件上限。當達到這個事件上限或者已經沒有可處理的事件,就返回計算過程。

至於,剩下的函數,比如和庫路徑有關的函數、和字符串翻譯即國際化有關的函數,就要大家根據需要自行研讀Qt的幫助文檔了,在此就不一一細說了。




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章