用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 文件。

#include “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)

行了,看看,這個窗口不空白了。

 

 

好了,今天老周就水到這兒了。