創建第一個程序
????????首先我們打開Qt Creator
????????打開文件->New Projects... 菜單,創建我們的第一個Qt項目
????????選擇 Qt Widgets Application,點擊選擇...按鈕
????????之后,輸入項目名稱QtLearning,并選擇創建路徑,
????????在build system中選擇qmake(默認選項),點擊下一步
????????除了qmake以外,還有CMake和Qbs選項,這三種方式都是Qt支持的編譯方式,現階段看,qmake是最主要的編譯方式,其次是CMake,而Qbs已經逐漸被Qt放棄。雖然現在qmake依然是Qt所采用的的主要編譯方式,但是現在CMake的采用率越來越高,CMake憑借強大的組織能力而越來越受到整個C++陣營的歡迎,現在已經成為了C++社區的主流編譯方式。
????????qmake 和CMake都是通過某種方法來構建MakeFile文件,然后交給其他編譯工具進行編譯。
????????這一步需要把Generate form選項去掉,其他保持默認就可以。點擊下一步,
????????這個畫面是設定多語言支持的,我們保持默認,點擊下一步,
????????這個是選擇編譯器,我們選擇MinGW 64-bit作為我們的編譯器,點擊下一步
????????補充知識:
????????MinGW的全稱是Minimalist GNU on Windows,它是是將經典的開源 C語言 編譯器 GCC 移植到了 Windows 平臺下,并且包含了 Win32API ,因此可以將源代碼編譯為可在 Windows 中運行的可執行程序。GCC編譯器是Linux系統上的主流編譯器,采用這個編譯選項能夠讓我們編寫的代碼具有更好的兼容性。而MinGW除有GCC編譯器外,還集成很多其他的工具,比如MSYS等。
????????MSVC這個是微軟的C++編譯器,如果我們的程序或代碼只是在Windows上運行的話,我們可以選擇這個選項,從而可以更好的利用一下Windows的特性。
????????這個是創建項目的最后一步,選擇版本控制系統,我們不需要選擇,點擊完成。
????????在經過一系列的構建步驟之后,我們就看到了已經構建好的源碼
????????我們看到向導已經幫我們創建了四個文件并且把他們放到了對應的目錄中,
- QtLearning.pro
- maindwindow.h
- main.cpp
- maindwindow.cpp
????????這其中main.cpp是程序的入口,在這個文件中有一個程序的入口函數main,而maindwindow.h和maindwindow.cpp是主窗口的對應代碼,QtLearning.pro是工程文件。
工程文件中都有什么
????????QtLearning.pro是整個項目的工程文件。任何一個 Qt 項目都至少包含一個 pro 文件,此文件負責存儲與當前項目有關的配置信息,比如:
- 項目中用到了哪些模塊?
- 項目中包含哪些源文件,哪些頭文件,它們的存儲路徑是什么?
- 項目使用哪個圖片作為應用程序的圖標?
- 項目最終生成的可執行文件的名稱是什么?
????????一個項目中可能包含上百個源文件,Qt 編譯這些源文件的方法是:先由 qmake 工具根據 pro 文件記錄的配置信息生成相應的 MakeFile文件,然后執行 make 命令完成對整個項目的編譯。也就是說,pro 文件存儲的配置信息是用來告知編譯器如何編譯當前項目的,所以一個Qt項目要想完美運行,既要保證各個源文件中程序的正確性,還要保證 pro 文件中配置信息的合理性。
????????實際開發中,Qt會自動修改工程文件的內容,但有時也需要我們手動修改,例如程序中用到某個第三方庫時,就需要我們手動修改工程文件。所以我們需要了解工程文件的構成。
????????下表列出了一些常用變量并描述了它們的內容。
配置項 | 含 義 |
QT | 指定項目中用到的所有模塊,默認值為 core 和 gui,中間用 += 符號連接。 |
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets | 如果 QT 版本大于 4(Qt5 或更高版本),則需要添加 widgets 模塊,該模塊包含所有控件類。 |
TARGET | 指定程序成功運行后生成的可執行文件的名稱,中間用 = 符號連接。 |
TEMPLATE | 指定如何運行當前程序,默認值為 app,表示當前程序是一個應用程序,可以直接編譯、運行。常用的值還有 lib,表示將當前程序編譯成庫文件。 |
DEFINES | 在程序中新定義一個指定的宏,比如?DEFINES += xxx,如同在程序中添加了 #define xxx 語句。 |
SOURCES | 指定項目中包含的所有 .cpp 源文件。 |
HEADERS | 指定項目中包含的所有 .h 頭文件。 |
FORMS | 指定項目中包含的 ui 文件。 |
INCLUDEPATH | 指定頭文件的存儲路徑,例如:INCLUDEPATH += /opt/ros/include |
CONFIG | 經常對應的值有: release:以 release 模式編譯程序; debug:以 debug 模式編譯程序; warn_on:編譯器輸出盡可能多的警告; c++11:啟動 C++11 標準支持。 例如 CONFIG += c++11。 |
????????QT 用來指明當前項目中用到的所有模塊,它的默認值是 core 和 gui,分別表示引入 Core 模塊和 GUI 模塊:
- Core 模塊包含了 Qt GUI 界面開發的核心功能,其它所有模塊都需要依賴于這個模塊,它是所有 Qt GUI 項目必備的模塊;
- GUI 模塊提供了用于開發 GUI 應用程序的必要的一些類。
????????需要注意的是,每個新創建的 Qt GUI 項目中,都默認包含 Core 模塊和 GUI 模塊,如果項目中用不到它們,可以使用QT -=刪除。
????????例如,刪除項目中包含的 GUI 模塊,只需在 pro 文件中添加一條配置信息:????????
QT -= gui
????????看了這些我們就能夠讀懂QtLearning.pro文件并進行修改了,修改后的文件如下,
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++17
TARGET = MyFirstApp
TEMPLATE = app
SOURCES += \main.cpp \mainwindow.cpp
HEADERS += \mainwindow.h
????????然后我們就可以編譯程序了。
????????當然也可以完全不修改工程文件,直接進行編譯。這里進行簡單的修改只是學習一下如何修改這個工程文件,在我們的程序需要其他第三方的庫的時候后,我們就需要修改這個文件以利用相關的功能。
????????經過編譯之后,我們的第一個程序就可以運行起來了。
????????由于我們沒有添加任何東西,也沒有對主窗口進行設定,所以我們看到的主窗口的標題欄是TARGET的名字,窗口大小可以調整,整個窗口都是空白的。
主窗口頭文件中有什么
????????接下來我們分析mainwindow,我們打開mainwindow.h文件
#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include <QMainWindow>class MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();
};
#endif // MAINWINDOW_H
????????這個文件非常簡單,首先包含了QMainWindow的頭文件,然后就是MainWindow類的定義,它是QMainWindow的子類,關于QMainWindow的內容會在后面說明,接下來有一個Q_OBJECT的宏,然后是構造函數和析構函數。
????????這里需要注意的是Q_OBJECT宏,Q_OBJECT宏是Qt對象模型中的一個關鍵要素。它通常定義在繼承自QObject的類的私有部分,為該類提供元對象(meta-object)系統所需的底層支持。Q_OBJECT宏是Qt框架的核心,用于啟用如信號槽、動態屬性、類型信息等Qt的核心功能。在使用Q_OBJECT宏時,需要注意其與QObject的繼承關系,使用Q_OBJECT宏的類都必須直接或間接的繼承自QObject類。Q_OBJECT宏的生命要緊跟在class語句的之下,其他語句之上,這樣才能夠保證元對象能夠被正常編譯。
元對象系統和元對象編譯器
????????那么什么是元對象系統呢。這里涉及到了兩方面的知識:Qt 元對象系統和元對象編譯器。
????????首先是元對象系統 (Meta-Object System),Qt元對象系統 (Meta-Object System)是Qt框架的基石,它為 QObject 類(以及其子類)提供了一些獨特的功能。以下是元對象系統所提供的主要功能:
- 信號與槽機制:信號槽機制是 Qt 的核心特性之一,為組件之間的通信提供了一種安全且靈活的方法。這種機制使得組件可以在不必了解對方實現細節的情況下,實現解耦式的通信。
- 對象自省 (Introspection):元對象系統允許在運行時查詢關于對象的信息,例如類名、屬性、信號和槽等。這使得 Qt 能夠實現更加靈活的動態行為和強大的工具集成。
- 動態屬性:元對象系統支持在運行時為 QObject 添加和修改動態屬性。這些屬性的值可以在沒有改變類定義的情況下被設置和讀取。
????????要使用 Qt 元對象系統,首先需要在類聲明中添加 Q_OBJECT 宏。此外,該類需要繼承自 QObject(直接或間接繼承都可以)。Q_OBJECT 宏告訴 Qt 元對象編譯器 (MOC) 為類生成所需的元對象代碼,以支持信號槽機制、動態屬性等特性。不包含 Q_OBJECT 宏的類將無法使用元對象系統提供的功能。
????????其次是元對象編譯器 (MOC),元對象編譯器(Meta-Object Compiler,簡稱 MOC)是 Qt 框架中一個獨特的工具,負責生成 QObject 子類的元信息。MOC 是一個預處理程序,它在 C++ 編譯器處理源代碼之前,掃描包含 Q_OBJECT 宏的 QObject 子類源文件,并輸出額外的 C++ 代碼。這些生成的代碼用于實現信號槽機制、動態屬性等元對象系統提供的功能。
????????元對象編譯器 (MOC) 的主要作用是:
- 提供信號槽的實現:元對象編譯器為信號槽機制生成底層實現代碼。當兩個 QObject 子類通過信號和槽連接時,MOC 生成的代碼能夠將信號與槽關聯起來并在需要時執行槽函數。
- 支持運行時類型信息:MOC 生成的代碼包含對象的運行時類型信息。這些信息可以用于實現對象自省,例如查詢對象的類名、屬性、信號和槽等。
- 支持動態屬性:元對象編譯器生成的代碼允許 QObject 子類在運行時添加、修改和訪問動態屬性。
????????MOC 作為一個預處理工具,是 Qt 開發過程中不可或缺的一環。它允許開發者為 QObject 子類添加元數據,從而支持信號槽機制等功能。開發者需要確保 QObject 子類中包含 Q_OBJECT 宏,使得 MOC 能夠正確地掃描并生成所需的元信息。
????????這些知識在我們對Qt程序已經很熟悉之后,需要單獨進行學習,才能夠從更深層次去明白Qt的實現機制,現階段我們只要知道我們添加這行代碼的作用就可以了。
入口主函數中都有什么
????????接下來我們分析一下main.cpp文件,我們看到這個文件中的代碼非常簡單
#include "mainwindow.h"#include <QApplication>int main(int argc, char *argv[])
{QApplication a(argc, argv);MainWindow w;w.show();return a.exec();
}
????????首先它包含了mainwindow的頭文件,然后包含了QApplication的定義。
????????接下來就是整個程序的入口main函數,所有的Qt程序都只能有且只有一個main()函數。
????????main函數的內部,首先創建了一個QApplication 對象,并將命令行參數傳遞給它,通常情況下,我們需要在main函數的開始就創建QApplication的對象,也就是在任何Qt 的窗口系統部件被使用之前創建QApplication對象,然后把命令行交給它處理,這是因為有一些命令行參數是需要QApplication來處理的,它處理之后會將對應的命令行參數從argv中刪除,也就是argv和argc的值和內容會因此而減少。一個應用程序只應該定義一個 QApplication類的實例,我們在程序開始創建的QApplication的實例可以通過全局指針qApp進行訪問,qApp指針可以后續程序中的任何地方進行訪問,他給我們提供了大量有關程序全局的方法,比如取得所有窗口,取得剪貼板的內容等。
????????QApplication類是Qt程序中非常重要的一個類,它負責管理GUI程序的控制流和主要設置,處理QWidget特有的初始化和收尾工作,提供各種事件、剪貼板、窗口、控件等的管理和控制。
????????它的繼承關系如下:
QApplication → QGuiApplication → QCoreApplication → QObject
????????這幾個類看起來都差不多,那么他們應該如何使用呢,
- 對于非GUI的程序,請使用QCoreApplication以避兔不必要地初始化圖形用戶界面所需的資源。
- 對于不基于QWidget的GUI的程序,使用QGuiApplication,因為它不依賴于QtWidgets庫。
- 對于是基于QWidget的GUI應用程序,使用QApplication
????????QApplication的主要職責范圍是:
- 它利用用戶的桌面設定來初始化應用程序。并且它會跟蹤這些屬性,以防用戶全局更改桌面,例如通過某種控制面板。
- 它從底層窗口系統接收事件并通過使用sendEvent() 和postEvent()將事件發送到部件。
- 它解析常見的命令行參數并相應地設置其內部狀態。
- 它定義應用程序的外觀,并將該外觀封裝在對象中。
- 它提供用戶可見的字符串的本地化。
- 它提供了一些特殊的方法,如clipboard()。
- 它知道應用程序的窗口。您可以使用widgetAt()詢問哪個小部件位于某個位置,獲取topLevelWidgets() 列表,關閉所有的窗口 closeAllWindows()等。
- 它管理應用程序的鼠標光標處理,請參閱 setOverrideCursor()。
????????QApplication 對象可通過instance()函數訪問,該函數返回與全局指針qApp等效的指針。
????????在main()函數里,我們接著創建了一個MainWindow 對象,并調用它的show()方法將窗口顯示出來。
????????這個MainWindow是QMainWindow的一個子類,繼承關系如下:
MainWindow → QMainWindow → QWidget → QObject?and?QPaintDevice
????????主窗口(QMainWindow)也是一個非常重要的類,幾乎所有的基于QWidget的GUI程序都會有一個或多個QMainWindow或者是它的子類的實例,以用來與用戶進行交互。QMainWindow提供了用于構建應用程序用戶界面的框架和主窗口的管理,并且它有自己的布局,我們可以在其中添加工具欄(QToolBar)、停靠部件(QDockWidget)、菜單欄(QMenuBar)?和狀態欄(QStatusBar)。
????????主窗口的布局有一個中心區域,可以被任何類型的部件占據。我們可以從下面的布局圖像中看出他們的組成,我們在后面的程序中會對逐一的添加這些內容。QMainWindow除了支持單文檔窗口(SDI),也支持多文檔窗口(MDI),只需要使用一個特殊的QMdiArea作為中心部件就可以了,否則就使用普通的或者是自定義的部件作為中心部件。
????????在main函數的最后,調用QApplication的exec()函數,并將該函數的返回值作為返回值。
????????QApplication的exec()函數是QApplication類中一個非常重要的函數,這個函數使我們的程序進入事件循環并等待,直到exit()函數被調用,exec()函數的返回值就是在exit()函數中設置的(如果是通過quit()函數調用的exit(),那么返回值就是0。
????????只有調用exec()函數才能開始消息循環處理。消息循環從系統接收各種事件,并將這些事件調度給對應的程序部件、窗口等。
????????除了使用模態方法表示部件、窗口之外,在QApplication的exec()在被調用之前不能進行任何用戶交互,因為模態部件也是調用自己的exec()來啟動自己消息循環。
????????這里Qt官方給了我們提了一個建議,如果要執行某些必要的處理,即使在沒有掛起事件時也需要執行特殊處理的時候,讓我們使用一個沒有等待時間的QTimer來進行,從而保證該處理一定會被調用。當然也可以使用processEvents()來實現更高級的空閑處理方案。
????????Qt框架建議我們將清理代碼放在aboutToQuit()信號的處理函數中,而不是將其放在應用程序的main()函數的最后。因為在某些系統平臺上,QApplication的exec()調用可能不會被返回就直接退出了程序,例如:在 Windows 平臺上,當用戶注銷時,系統會在Qt關閉所有頂級窗口后終止進程。因此,不能保證應用程序在調用 QApplication的exec()之后有時間退出其事件循環并在函數結束時執行代碼。
????????到這里為止,我們通過向導創建的第一個Qt程序基本分析完成了,這里面其實給我們提供了大量的信息,我們在重新回顧一下:
- 工程文件(.pro),它的構成方法,組織方式以及一些常用的命令,正式同坐這些,才能夠做成MakeFile文件,進行編譯。
- Q_OBJECT宏,這個是在所有自定義Widget都要添加的東西,它給我們提供了信號槽、動態屬性、類型信息等Qt的核心功能的實現。它的聲明要保證緊跟在class語句的之下
- 元對象系統,這個是Qt的核心功能之一,它的主要提供了信號與槽機制、對象自省 (Introspection)、動態屬性的支持等能力。
- 元對象編譯器 (MOC),它是一個預處理工具,是 Qt 開發過程中不可或缺的一環,它在 C++ 編譯器處理源代碼之前,掃描包含 Q_OBJECT 宏的 QObject 子類源文件,并輸出額外的 C++ 代碼。這些生成的代碼用于實現信號槽機制、動態屬性、運行時類型信息等元對象系統提供的功能。
- QApplication類,這個類給我們提供大量的和操作系統相關的功能,從而避免我們直接和特定的操作系統打交道,這個類也給我們提供了消息循環,保證我們程序的運行。
- QMainWindow類,這是一個很重要的Qt類,它提供了用于構建應用程序用戶界面的框架和主窗口的管理,它支持SDI和MDI不同模式。
????????其實,這些信息其實對于我們理解一套框架是非常有用的,但是由于代碼比較簡單,我們常常會忽略這其中隱藏的重要信息,然后在以后編寫代碼的時候遇到對應的問題,卻不知道從何處開始著手,比如說鼠標事件、鍵盤事件、MDI窗口如何創建等問題。