用VS Code搞Qt6:至簡窗口部件——QWidget
在正題開始之前,老周照例扯點別的。嗯,咱們扯一下在 VS 2022 下結合 CMake 開發 Qt6 時的環境變量設置問題。在VS Code 中,通夠通過 CMake Tools 擴展的配置來設置環境,但在VS 裏面,CMake 項目只是一個文件夾,然後通過 .json 文件來配置一些參數,不能像 VS Code 那樣設置環境變量。
當然,如果你嫌麻煩,最簡單粗暴的方法,就是在系統級或用戶級別直配置全局環境變量。這樣所有開發工具都能共享這些環境變量。但有時候,你不希望把 Qt 庫的路徑放上去,因為配置的環境變量多,怕搞得亂。
好了,F話不多說了。下面老周就說說如何在單個項目中配置環境變量。
方法一:千古傳統做法,打開命令行或寫個腳本,先設置環境變量,set PATH=k:\Libs\Qtlib;%PATH%,然後在命令行中啟動 VS。
方法二:這個好一點。先在VS中建以 CMake 為基礎的項目。然後會產生一個預設配置文件—— CMakePresets.json。打開此文件,大概這樣:
{ "version": 3, "configurePresets": [ { "name": "windows-base", "hidden": true, "generator": "Ninja", "binaryDir": "${sourceDir}/out/build/${presetName}", "installDir": "${sourceDir}/out/install/${presetName}", "cacheVariables": { "CMAKE_C_COMPILER": "cl.exe", "CMAKE_CXX_COMPILER": "cl.exe" }, "condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Windows" } }, { "name": "x64-debug", "displayName": "x64 Debug", "inherits": "windows-base", "architecture": { "value": "x64", "strategy": "external" }, "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" } }, { "name": "x64-release", "displayName": "x64 Release", "inherits": "x64-debug", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" } }, { "name": "x86-debug", "displayName": "x86 Debug", "inherits": "windows-base", "architecture": { "value": "x86", "strategy": "external" }, "cacheVariables": { "CMAKE_BUILD_TYPE": "Debug" } }, { "name": "x86-release", "displayName": "x86 Release", "inherits": "x86-debug", "cacheVariables": { "CMAKE_BUILD_TYPE": "Release" } } ] }
看它的鬼樣子好像很復(可)雜(怕),其實就是一組生成/調試運行的配置。配置和 VS 標準一樣,都是「F5 雙俠」 —— Debug 或 Release。但是,這兩種調試運行有許多配置項是共享的,於是,此配置文檔先弄個叫「windows-base」的配置,把一些共用的選項放在這兒,然後其他配置會繼承它。比如,「x64-debug」,它通過 “inherits”: “windows-base” 明確了它是繼承 「windows-base」 配置的。
咱們直奔重點,把環境變量設置在 「windows-base」 配置中,這樣後面的所有配置都能繼承,因為環境是必須的。在「windows-base」中加入「environment」 節點,它的值是一個 JObject,其中,Key 是環境變量的名稱,Value 是環境變量的值。
我們的目標是配置 PATH 環境變量。
"name": "windows-base", …… "environment": { "PATH": "G:\\Kits\\Qt6\\installed;G:\\Kits\\Qt6\\installed\\bin;$penv{PATH}" }, "condition": { "type": "equals", "lhs": "${hostSystemName}", "rhs": "Windows" } },
需要添加兩個目錄,一個是 Qt 所在的目錄,一個是 Qt 目錄下的 bin 子目錄(加這個後在調用運行時就不會找不到 .dll 了)。注意最後的 $penv 宏,此處不能用 $env 宏來引用變量,因為我們要的效果是在 PATH 變量中添加路徑,而不能把原有的路徑替換,等同於把這兩個目錄追加到現有的目錄列表中。CMake 的變量不能循環引用自身(PATH引用了自己),所以要用 $penv 宏,而不用 $env 宏來引用原有的 PATH 變量。
保存文件,這樣在按【F5】運行項目時就不會報錯了,寫代碼時也不會找不到頭文件了。
以上是佔了大段篇幅的題外話,因為網上沒有相關的內容,所以老周就寫出來,以供大夥伴們參考。
===================================================================================
今天咱們的主角是一個名為 QWidget 的類。它表示一個最最最基本的窗口部件(控件)類,它也是所有部件的公共基類。Qt 裏面 99.993% 的類名都有 「Q」 開頭,比如 QObject、QString。QWidget 類在 Widgets 庫中,屬於 QtBase 模塊的一部分,鐵三角之一。
QWidget 君雖然是公共基類,但它是可以直接使用的。
咱們用一個例子來看看直接用 QWidget 類會在用戶界面上呈現什麼。
CMake 文件:
cmake_minimum_required(VERSION 3.20.0) project(myApp LANGUAGES CXX) set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # 導入庫 find_package(Qt6 REQUIRED COMPONENTS Core Gui Widgets) # 添加源代碼 add_executable(myApp WIN32 app.cpp) # 鏈接庫 target_link_libraries(myApp PRIVATE Qt6::Core Qt6::Gui Qt6::Widgets)
新建一個文件,命名為 app.cpp,它是主代碼文件。
包含 QApplication、QWidget 兩個頭文件。
#include <QApplication>
#include <QWidget>
Qt 為兼顧 C++ 新標準,提供不帶 .h 後綴的頭文件。當然,你用帶擴展名的頭文件也可以。
#include <qapplication.h>
#include <qwidget.h>
其實 QApplication 頭文件裏面就是寫了一行 include ,把帶擴展名的文件包含。
#include "qapplication.h"
其他的頭文件的結構類似,說白了就是不帶擴展名的頭文件里包含了帶擴展名的頭文件。照這麼看來,直接寫 qapplication.h 可能更省事。
Qt 有一點讓我不太高興。它每個類一個頭文件,要是代碼里用到 20 個類,就要寫 20 條 include。所以,如果要包含的頭文件多的話,可以單獨寫在一個頭文件中,然後項目的代碼文件包含它。比如,我建個頭文件叫 comm.h。
#pragma once #include <QApplication> #include <QWidget>
老周在第一行加了個 once 指令,這個是告訴編譯器,如果頭文件出現重複包含,那麼只用一次就行,這樣重複包含不會報錯。但,#pragma once 在 VC++ 編譯器中能用,在 g++ 或其他編譯器中不一定能用。如果你寫的是跨平台應用,可以改為:
#ifndef _COMM_H_ #define _COMM_H_ #include <QApplication> #include <QWidget> #endif
_COMM_H_ 是個符號,你可以自由定義。它的意思就是如果沒有定義 _COMM_H_ 宏那就執行後面的代碼,如果已經定義了就不執行了。因為後面的代碼中加了一行 #define … 所以,這段代碼也能保證頭文件只會包含一次。
重點:很多朋友發現頭文件中的代碼報錯,並且找不 QApplication、QWidget 等頭文件。這是 CMake 在你家後院放火導致的。這時候你要打開 CMakeLists.txt 文件,在 add_executable 命令中,把 comm.h 文件加入到代碼文件列表中。
add_executable(myApp WIN32 app.cpp comm.h)
現在打開 app.cpp,包含 comm.h 文件。
寫 main 函數,要帶命令行參數的(不帶也行,但一般會帶上)。
int main(int argc, char **argv) { }
argc 是命令行參數的個數,argv 才是命令參數,是一個 char* 數組,一個指針表示字符串,再加個指針,char** 表示字符串數組。所以,你也可以這樣寫:
int main(int argc, char *argv[]) { }
這樣更容易看出它是個數組了。
接着實例化 QApplication 類。所有 Qt 應用程序都需要這個,需要它不會鏈接到 Widgets 上。畢竟窗口程序不是控制台,不能一執行就完了,它要停下來,不斷接收和響應用戶的操作。exec 方法啟動消息循環,不斷讀取和處理消息,直接應用程序退出。
int main(int argc, char **argv) { QApplication app(argc, argv); return app.exec(); }
命令行參數會傳遞給 app 實例。
在 exec 方法調用之前初始化 QWidget 對象。不要在 exec 調用之後寫,那樣是不會執行的,因為 exec 方法開啟的就是個死循環。
QApplication app(argc, argv); QWidget wdg; wdg.show(); return app.exec();
這樣的程序特簡單,那麼,運行之後會出啥呢。看看。
哦,懂了,原來直接實例化 QWidget,出來的是一個空白的、最簡單的窗口。
那,咱們改一下標題欄文本。
QWidget wdg; wdg.setWindowTitle("一款高大上逼格全開的智障應用"); wdg.show();
再運行一下,標題欄變了。
咦,窗口左上角那個默認圖標怎麼像個櫥櫃似的,不好看,能不能換一個。好,說換就換。
// comm.h #ifndef _COMM_H_ #define _COMM_H_ #include <QApplication> #include <QWidget> #include <QIcon> #endif // app.cpp QWidget wdg; wdg.setWindowTitle("一款高大上逼格全開的智障應用"); // 設置圖標 wdg.setWindowIcon(QIcon("534.png")); wdg.show();
找一張圖片,PNG 或 ico 格式都行,放在可執行文件所在目錄下,命名為 534。上面代碼中用的是相對路徑,所以圖片文件不是放在項目根目錄下,而是 .exe 文件所在的目錄下。
現在,窗口的圖標變了。
我們還可以把窗口移動到指定的坐標上,再設置它的大小。
// 位置 wdg.move(340, 400); // 尺寸 wdg.resize(336, 286); wdg.show();
還有個叫 setGeometry 的方法,直接一步到位,同時設置窗口的位置和大小。
wdg.setGeometry(521, 219, 320, 265);
四個值依次是 X坐標、Y坐標、寬度、高度。
我們也可以從 QWidget 類派生,然後將它封裝為一個窗口,裏面放着其他控件。
比如,寫一個 MyForm 類,從 QWidget 派生,裏面有一個標籤和一個按鈕。
// MyForm.h #ifndef _MYFORM_ #define _MYFORM_ #include "comm.h" #include <QLabel> #include <QPushButton> class MyForm : public QWidget { public: explicit MyForm(QWidget *parent = nullptr); ~MyForm(); //析構函數 private: QLabel *lb; QPushButton *btn; void initUI(void); }; #endif // MyForm.cpp #include "MyForm.h" MyForm::MyForm(QWidget *parent) : QWidget::QWidget(parent) { // 標題 setWindowTitle("主窗口"); // 大小 resize(300, 200); initUI(); } MyForm::~MyForm() { } // 初始化UI void MyForm::initUI() { lb = new QLabel("一些文本", this); btn = new QPushButton("一隻按鈕", this); // 定位控件 lb -> move(25,24); btn -> move(25, 68); }
構造函數加上 explicit 關鍵字,要求實例化 MyForm 類是必須調用構造函數,不能隱式賦值。隱式賦值就是當構造函數只有一個參數時,可以在實例化時用參數的值直接賦值。比如
class Pig { public: Pig(int v); };
那麼在實例化時可以這樣:
Pig pp = 100;
我們這裡,MyForm 不允許這樣做,所以加個 explicit 關鍵字。
在 app.cpp 中,用 MyForm 類來呈現窗口。
#include "comm.h" #include "MyForm.h" int main(int argc, char **argv) { QApplication app(argc, argv); MyForm fw; fw.show(); return app.exec(); }
show 方法是從 QWidget 類繼承的,不是咱們自己寫的。
CMakeLists.txt 文件也要加上代碼文件。
add_executable(myApp WIN32
app.cpp
comm.h
MyForm.h
MyForm.cpp)
行了,看看,這個窗口不空白了。
好了,今天老周就水到這兒了。