Qt開發筆記-----基礎篇

来源:https://www.cnblogs.com/weiguanghao/archive/2023/05/21/17418729.html
-Advertisement-
Play Games

## 1.1 為什麼要學 Qt Qt是一個跨平臺的 C++ 圖形用戶界面應用程式框架 Qt 為應用程式開發者提供建立藝術級圖形界面所需的所有功能 Qt 是完全面向對象的,很容易擴展,並且允許真正的組件編程 (1)Qt 發展史 在講解學習 Qt 的必要性之前, 先來瞭解下 Qt 的發展歷史: 1991 ...


1.1 為什麼要學 Qt

Qt是一個跨平臺的 C++ 圖形用戶界面應用程式框架

Qt 為應用程式開發者提供建立藝術級圖形界面所需的所有功能

Qt 是完全面向對象的,很容易擴展,並且允許真正的組件編程

(1)Qt 發展史
在講解學習 Qt 的必要性之前, 先來瞭解下 Qt 的發展歷史:

1991年,Qt 最早由 奇趣科技(TrollTech)開發

1996年,進入商業領域,它也是目前流行的 Linux 桌面環境 KDE 的基礎

2008年,奇趣科技被諾基亞公司收購,Qt 成為諾基亞旗下的編程語言

2012年,Qt 又被 Digia 公司收購

2014年4月,跨平臺的集成開發環境 Qt Creator 3.1.0 發佈,同年5月20日配發了 Qt5.3 正式版,至此 Qt 實現了對 iOS、Android、WP 等各平臺的全面支持

2020年,Qt 團隊宣佈 6.0 版本發佈。

當前 Qt 最新版本為 Qt 6.3

(2)學習 Qt 的必要性
跨平臺

用 Qt 編寫的程式,可以直接編譯運行到不同的平臺:windows、linux、mac

主流的應用級軟體肯定是要跑在這三個平臺的,用 mfc、c# 做的話, 就只能跑在 windows 上,無法輕易移植到 linux , mac 上去

知名軟體都用 Qt

很多知名軟體都是 Qt 開發的

谷歌地圖

Google Earth,是一款谷歌公司開發的虛擬地球軟體,通過它可以瀏覽全球各地的高清晰度衛星圖片

Wireshark

一款免費的網路抓包分析軟體,它的功能非常強大對於開發者而言,可以用它通過抓包分析,來解決開發過程中的很多問題。

VirtualBox

開源的虛擬機軟體,可以在一臺電腦上同時運行兩個操作系統

典型的用法就是:宿主機安裝 windows 系統,然後通過 virtualbox,再安裝一個 linux 系統,這樣就可以同時運行 windows 和 linux 兩個系統,並且在兩個系統之間可以很方便地共用文件

virtualbox 對標的是 Vmware,VMware 也是一個虛擬機軟體,不過它是收費的

VLC

這是一個開源的多媒體播放器

它體積小巧、功能強大,做音視頻開發的小伙伴應該是很熟悉它的

WPS Office

金山公司(Kingsoft)出品的國產辦公軟體套裝,完全相容微軟的 Office 系列

YY 語音

國內第一大游戲語音通訊平臺,可以進行線上多人語音聊天和語音會議

百度查一下還有很多,可以不學 mfc ,但是 Qt 一定要學

代碼層次更加清晰

用 mfc 的話, 系統自動生成的代碼和自己寫的代碼,是雜糅在一起的,比如,當界面的控制項刪除之後,代碼就報錯,需要自己手動去修改源碼

而在 Qt 中,系統自動生成的代碼和自己寫的代碼是完全隔離開的

界面美觀

Qt 支持 QSS, 類似於 web 端的 css, 修改控制項的樣式, 很簡單。

因此,用 Qt 做出來的界面是非常美觀的。

2D/3D開發

支持2D/3D圖形渲染,支持 OpenGL,如果做三維應用,首選推薦 Qt

豐富的API、完善的開發文檔

Qt 包括多達 250 個以上的 C++ 類,並且提供了非常完善的文檔,並且附帶有很多例子,對開發者非常友好

嵌入式開發

嵌入式開發,就是指的嵌入式 linux 開發, Qt 是做嵌入式 linux 界面開發最好的,沒有之一

在後續的課程中,我也會推出 《嵌入式 QT 開發》的教程,會支持正點原子、野火、韋東山的系列的開發板

最好會寫界面

如果學過了 C++,但是連一個簡單的界面做不出來,有點說不過去

當然你可以最強的是用 C++ 做後臺,但是界面還是要掌握的,因為通常工作中會寫一個帶有界面的簡單的測試工具,此時 Qt 就派上用場了

最重要一點 - 工資待遇好

1.2 安裝 Qt Creator

image-20230521143813703

Qt 開發重要有兩種開發環境

Qt Creator

它是 Qt 官方提供的開發環境,並且 creator 本身是跨平臺的,它可以被安裝在 windows、linux、mac 上。

Visual Studio

Visual Studio 是微軟的一個集成開發環境,它號稱宇宙第一IDE

只要在 Visual Studio 中安裝一個 Qt 官方提供的插件,就能進行 Qt 的開發

後面的課程,我們會使用 Qt Creator 為例進行 Qt 課程的講解 ,畢竟是官方推出的開發環境!

(1)QT 重要版本
QT4

嵌入式設備上的圖形界面,很多用的仍是 Qt 4 的版本,Qt 4.8.7 是 Qt4 系列的終結版本,解決了以往的全部 BUG

如果是不得不使用 Qt 4 版本,建議使用 Qt 4.8.7

QT5

5.15 LTS 作為 Qt 5 系列的最後版本,在修複 bug 方面也做了大量工作,是 Qt 5 系列中最好、最穩定的版本。

Qt 6

目前 最新版本為 QT 6.3

(2)Qt Creator 安裝方式:離線/線上
Qt 開發的官方開發環境是 Qt Creator,官方下載地址:https://download.qt.io/

在 5.14 版本之前,官方提供離線的安裝包,但是從 5.15 版本之後,需要線上安裝(這類似於 visual studio 的安裝,也是需要下載一個安裝器先),如下:

5.14 版本:https://download.qt.io/archive/qt/5.14/5.14.2/

img

5.14 版本之前,提供離線安裝包

5.15 版本:https://download.qt.io/archive/qt/5.15/5.15.2/

img

可見,在 5.15 目錄下,移除了離線安裝包,查看其中的 OFFLINE_README.txt,內容如下:

Due to The Qt Company offering changes, open source offline installers are not available any more since Qt 5.15. Read more about offering changes in the https://www.qt.io/blog/qt-offering-changes-2020 blog.

If you need offline installers, please consider our new Qt for Small Business offering: https://www.qt.io/blog/available-now-qt-for-small-businesses

通過查看鏈接:www.qt.io/blog/qt-offering-changes-2020,其中得知,離線安裝包只對商業付費用戶提供,如下:

Starting with Qt 5.15, long term support (LTS) will only be available to commercial customers.
雖然沒有提供離線安裝包,但是可以通過線上的方式安裝

打開如下鏈接,下載線上安裝器:https://download.qt.io/archive/online_installers/4.3/

img

QT官方提供的 - 線上安裝器
之後,就可以雙擊 qt-unified-windows-x86-4.3.0-1-online.exe 開始線上安裝了

(3)線上安裝
下麵列出線上安裝的詳細步驟

登錄 Qt 賬戶

如果沒有 Qt 賬戶,點擊 【註冊】按鈕,根據提示,註冊一個即可

img

​ 登錄 Qt 賬戶
開源義務

同意協議,並勾選個人用戶,否則需要輸入公司/企業名稱

img

禁用信息收集

通常直接選擇第二項,禁止向 Qt 官方發送統計信息

img

禁用信息收集
安裝文件夾

選擇安裝文件夾,並選擇【Custom installation】自定義安裝

img

選擇組件

img

這裡有必要進行下詳細的說明:

右側的類別中:

Archive

All Supported Release

所有的 Qt 發佈版本

LTS

Latest Long-Term Support Releases

長期支持版本

Latest releases

Latest Supported release

最新的正式發佈版

Preview

Latest unofficial release previews, including snapshots, alpha, beta, and RC release

預覽版,包括alpha預覽版,beta測試版,RC(Release Candidate)發行候選版

通常選擇 LTS,就像安裝 linux 時一樣

左側選擇編譯器:

Windows 系統下,Qt 主要有兩種編譯器:

MSVC

MSVC 編譯器(Microsoft Visual C++),是微軟提供的 VC 編譯器,需要 visual studio 環境的支持,由於我安裝了 Visual Studio 2019,因此在安裝時會列出

MinGW

MinGW 編譯器(Minimalist GNU for Windows),也就是 Gcc 編譯器,只不過在 Windows 下作了封裝而已,這個版本不需要 VS 環境的支持,可以獨立生成Windows 平臺的應用程式。

左側其他:

Android

可用於 android 的開發。

不過 android 的開發,通常直接使用谷歌的集成開發環境 Android Studio,使用 kotlin 語言進行開發(以前使用 Java 語言)

Source

Qt 的源碼,如果有需要查看源碼的需求,可以將這個勾選,不過會多占用至少 3GB 的磁碟控制項

Qt charts等

這些時 Qt 一些高級的開發組件,比如 charts 進行圖表的開發,WebEngine 進行 Web 相關的開發

說明:

目前來說,直接勾選 MinGW 的兩個編譯器即可

沒有勾選的組件,即使後面用到,可以再次打開這個安裝器繼續安裝!

許可協議

img準備安裝

準備安裝

img

開始安裝

img

安裝成功

在所有的 7z 壓縮文件提取完畢,並安裝之後,就完成最終的安裝

img

(4)追加其他組件
如果在安裝時,漏掉了某個組件,怎麼辦?難道要卸載重裝?

答案:完全不用,可以追加安裝需要的組件

方法如下:

點擊系統左下角的【開始】菜單,找到【Qt】,然後選擇【Qt Maintenance Tool】或者【Uninstall Qt】

img

打開
登錄 Qt 賬戶:

如果要卸載 Qt,選中【僅卸載】覆選框

img

選擇組件

img在此,可以追加安裝 Qt 源碼,如下:

追加組件

img

1.3 新建 Qt 工程

學習任何的編程語言,創建的第一工程都是列印 hello world,學習 Qt 也不例外

只是 Qt 不是一門語言,而是一個 基於 C++ 的 GUI 開發框架,因此這裡我們創建第一個界面程式

在創建第一個界面程式之前,首先來配置一下 Qt Creator,比如修改主題樣式,深色/淺色主題,代碼區字體的大小等

1、配置 Qt Creator
集成開發環境預設的設置,能夠滿足絕大部分人的需求,但是可能還是有個別選項,不符合我們的需求

因此,第一步都是根據自己的需要,進行簡單的設置

常用的集成開發環境:

# 微軟出品,宇宙第一 IDE,做 c# 開發就會用到
Visual Studio

# 微軟出品並開源的,源代碼編輯器,插件豐富強大,可以支持幾乎所有開發語言
VS Code

# 谷歌官方,用開發 Android 程式,目前 Android 開發的語言,已經由 Java 變為了 Kotlin
Android Studio

# 用於瀏覽C/C++代碼,韋東山老師習慣使用的工具
Source Insight 

(1)設置主題

可以將主題設置為淺色或者深色,比如在晚上的時候,可以將主題調成深色主題,就像手機的白天模式和夜間模式一樣

設置方法:【工具】->【選項】->【環境】

img

(2)設置中英文

【工具】->【選項】->【環境】

img

(3)設置代碼區字體大小

【工具】->【選項】->【文本編輯器】->【Fonts & Colors】

img

還可以設置滑鼠滾輪,來放大和縮小字體

方法:【工具】->【選項】->【文本編輯器】->【Behavior】

很實用和方便的功能,尤其是你在給同事講解代碼時,可以方便地放大代碼區域

img

(4)顯示行號、高亮顯示當前行

方法:【工具】->【選項】->【文本編輯器】->【Display】

顯示行號:方便代碼行數的定位

高亮顯示當前行:方便快速定位當前游標所在的行。這個預設時未勾選的,建議勾選

顯示文件編碼:界面上直觀地顯示文件編碼。這個預設時未勾選的,建議勾選,這樣文件編碼顯示在打開文件的右上角。

img

(5)設置文件預設編碼

文件的預設編碼為 utf-8,建議保持預設,這樣中文不會亂碼

如果是GB2312或者GBK編碼的文件, 使用utf-8編碼來打開就會亂碼

方法:【工具】->【選項】->【文本編輯器】->【Behavior】

img

6)保存時清理

就是在保存文件時,清除多餘的空白字元,使代碼簡潔,並且占用的文件大小也會縮小

在 VS Code 以及 Source Insight 中都有這種設置

方法:【工具】->【選項】->【文本編輯器】->【Behavior】

img

2、新建第一個Qt工程
環境配置好了,就可以開始新建第一個Qt工程了

(1)打開新建工程視窗

在【歡迎】模式下,點擊【Create Project...】或者【文件】菜單,點擊【New Project...】,都可以打開如下新建項目的對話框:

在 Qt 中,Widget 類是所有視窗類的基類,因此要創建基於視窗的應用,就要選擇【Qt Widgets Application】

img

(2)指定項目名稱和保存路徑

img

註意:

作為一名專業的程式員,項目名稱,項目路徑,包括文件明,都不要使用中文,否則可能會報錯,或者出現莫名其妙的問題!

(3)選擇構建系統

img

(4)選擇視窗類

img

(5)國際化

img

6)選擇編譯套件

img

(7)版本控制 常用的版本控制工具有 svn 和 git,通常我們自己使用 svn 或 git 來進行版本管理即可,這裡選擇 none

img

3、項目文件說明
上一步,直接點擊完成,就可以打開如下:

img

下麵對 HelloQt.pro 文件和 main.cpp 文件,進行介紹

img

QT 選項用於指定項目中用到的 Qt 模塊

通常一個視窗程式,肯定要用到的三個模塊就是:core、gui、widgets

那麼,Qt 中還有哪些常用的模塊呢?

# QtCore 模塊是Qt應用程式的基礎,是核心的非圖形類。
# 提供了信號與槽的通信機制,併發和多線程,容器,事件系統
Qt Core

# 最重要的GUI模塊。圖形用戶界面 (GUI) 組件的基類。
Qt GUI

# 包含基於GUI應用程式的典型小部件類,比如按鈕、文本框、標簽等
# 在 Qt5 中, 從 Gui 模塊中分離出來。
Qt widgets

# 網路模塊。用於支持 TCP, UDP, HTTP 通信
Qt Network

# 多媒體模塊。音頻、視頻、廣播和相機功能類。
Qt Multimedia

# Qt Multimedia 的小部件。
Qt Multimedia Widgets

# 資料庫模塊。用於操作資料庫,比如後面會將到的 sqlite、MySQL
Qt SQL

# web引擎模塊。用於 web 相關的開發
Qt WebEngine

############################################################
# 以下是 QML 相關的模塊
# QML 是什麼呢?
# 簡單來說,就是使用類似 js 的語法來構建界面,而不是 widget 的方式

# Qt QML模塊。用於 QML 和 JavaScript 語言。
Qt QML

# 該模塊用於使用 QML2 編寫的 GUI 應用程式。用於構建具有自定義用戶界面的高度動態應用程式的聲明性框架。
Qt Quick

# 提供輕量級 QML 類型,用於為桌面、嵌入式和移動設備創建高性能用戶界面。這些類型採用簡單的樣式架構並且非常高效。
Qt Quick Controls

# 用於從 Qt Quick 應用程式創建系統對話框並與之交互的類型。
Qt Quick Dialogs

# 用於在 Qt Quick 中安排項目的佈局。
Qt Quick Layouts 

(2)main.cpp

main.cpp文件是 Qt 程式的入口文件,其中的 main 函數是入口函數

#include "mywindow.h"

#include <QApplication>

int main(int argc, char *argv[])
{
    // 1. QApplication 是 Qt 框架提供的應用程式類
    // 作用:負責 Qt 中事件的處理,比如滑鼠的單擊事件,鍵盤的輸入事件等
    QApplication a(argc, argv);

    // 2. 創建自己的視窗對象,並調用其 show 方法,將視窗顯示出來
    MyWindow w;
    w.show();

    // 3. 調用 QApplication 類的 exec 方法,應用程式就阻塞在這裡,並不會退出,而是進入到事件迴圈的處理, 直到退出程式(比如點擊了窗體右上角的關閉按鈕)
    return a.exec();
} 

1.4 項目構建流程

第一個 Qt 工程,其中包括 5 個文件:

HelloQt.pro

main.cpp

mywindow.h

mywindow.cpp

mywindow.ui

1、mywindow.h/.cpp/.ui 文件說明
在 main.cpp 中,除了 QApplication 進入事件的迴圈處理以外,還會創建一個 MyWindow 的對象,並顯示出來

int main(int argc, char *argv[])
{
    ...

    // 創建 MyWindow 視窗對象, 並調用其 show 方法,將視窗顯示出來
    MyWindow w;
    w.show();

    ...
} 

接下來,我們按住 Ctrl 鍵的同時,點擊 MyWindow 類,跳轉到該類的定義處(mywindow.h),如下:

#include <QMainWindow>

 QT_BEGIN_NAMESPACE
 // 在此聲明一個MyWindow類,這個類定義在 Ui 命名空間中
 // 因為下麵會定義一個 Ui::MyWindow 類型的指針 *ui
 namespace Ui { class MyWindow; }
 QT_END_NAMESPACE

 // 我們自定義的 MyWindow 類,要繼承自Qt框架提供的QMainWindow/QDialog/QWidget這三個類其中之一,才可以正常顯示出來
 class MyWindow : public QMainWindow
 {
     Q_OBJECT

 public:
     MyWindow(QWidget *parent = nullptr);
     ~MyWindow();

 private:
     // 定義一個 Ui::MyWindow 類型的指針 *ui
     // Ui::MyWindow 這個類定義在 ui_mywindow.h 中(可以 Ctrl+單擊 跳轉過去)
     // 這個 Ui::MyWindow 類,本身是空實現,但是它繼承自 Ui_MyWindow 類

     // Ui_MyWindow 類,是和UI設計界面一一對應的,也就和mywindow.ui這個xml文件一一對應的
     // 至於為什麼一個C++代碼中類的對象,會和mywindow.ui這個xml文件一一對應,本節課下麵就會講到,暫且知道即可
     Ui::MyWindow *ui;
 }; 

接下來,來到 mywindow.cpp 文件:

MyWindow::MyWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MyWindow) // 在初始化列表中,對ui指針進行初始化賦值
{
    // 並將當前視窗,也就是this指針,設置到ui對象中
    // 只有這樣,我們在ui界面中拖拽的控制項才會在隨MyWindow顯示出來。
    ui->setupUi(this);

    // 之後就可以對拖拽的那些控制項進行各種操作了,比如下麵設置按鈕顯示的文字。
    ui->btn_start->setText("啟動");
    ui->btn_stop->setText("停止");
}

MyWindow::~MyWindow()
{
    // 既然在構造函數中 new 了ui對象,在析構中要delete銷毀
    delete ui;
} 

ui_mywindow.h 這個文件是怎麼來的?

為什麼說 Ui::MyWindow 這個c++ 中的類,是和 xml 格式的 mywindow.ui 文件一一對應的?

為什麼上面需要調用 ui->setupUi(this); 才可以將界面上拖拽的按鈕顯示出來

彆著急,下麵馬上就為你揭秘

在揭秘之前,有必要瞭解下 Qt 項目的構建流程

上一節中,我們使用嚮導創建了第一個 Qt 工程,在 Qt Creator 集成開發環境中,一個按鈕或者一個快捷鍵,就可以完成項目的構建和運行。

但是,這些看起來簡單的過程,背後到底發生了什麼呢?

作為一名專業的程式員,我們有必要探討一番,這樣即使以後出現了問題,編譯報錯,我們也能很快地定位到問題所在。

2、需要知道的幾個概念
在瞭解項目構建流程之前,有必要先看幾個概念:① makefile ② make ③ qmake

作為一名 Qt 開發者,需要有 C/C++ 基礎,想必對這幾個概念並不陌生,這裡做一個總結

(1)Makefile
對於一個源文件 main.cpp,如何編譯成可執行文件呢?

g++ -o main main.cpp 

每當 main.cpp 修改之後,就需要重新執行以上編譯命令

更簡單地,可以新建一個 Makefile 文件,其中內容如下:

main:main.cpp
 g++ -o main main.c 

此時,在命令行直接執行 make 就可以直接執行 Makefile,進而執行 g++ -o main main.c 來編譯出可執行文件 main

可見,引入了 Makefile 文件之後,只需執行 make 命令即可

以上只有一個源文件 main.cpp,然而一個實際的工程中,其源文件一般有有很多,並且通常按照功能模塊,放在若幹個目錄中

此時如果每次都手動一條條執行編譯命令,很顯然是不現實的,因此就有了 Makefile

Makefile 文件用於描述整個工程的編譯、鏈接的規則。比如,工程中的哪些源文件需要編譯(根據時間戳,只編譯修改過的文件)以及如何編譯、編譯順序,最終鏈接成目標的可執行文件或目標的庫文件

Makefile 有自己的書寫格式、關鍵字、函數,像 C 語言有自己的格式、關鍵字和函數一樣。

Makefile 文件編寫完畢之後,編譯整個工程你所要做的唯一的一件事就是在 shell 視窗下輸入 make 命令,就可以將整個工程進行 “自動化編譯”,極大提高了效率。

(2)make
make 是一個命令工具,在 shell 視窗下執行 make 命令,它會自動讀取並解釋 makefile 中指令,來完成整個工程的自動編譯,極大的提高了軟體開發的效率。

(3)qmake
手寫 Makefile 是比較困難而且容易出錯,尤其在進行跨平臺開發時,必須針對不同平臺分別編寫 Makefile,會增加跨平臺開發複雜性與困難度。

qmake 會根據項目文件(.pro)裡面的信息,自動生成 Makefile。

因此在 Qt 開發時,我們只需編寫 .pro 文件即可,因為 qmake 會自動根據 .pro 文件生成 Makefile

因此,Qt 項目構建的基本流程是:

qmake

執行 qmake 生成 Makefile

make

執行 make 命令,編譯出可執行程式

運行

運行可執行程式

3、Qt 項目構建流程
瞭解了 Makefile、make、qmake 的基本概念之後,我們知道 Qt 項目的構建流程:

qmake -> make -> run

接下來看下,上一節的 HelloQt 項目是如何編譯出可執行文件,並運行呈現出視窗界面的

(1)構建設置
點擊左側的項目模式,可以打開當前項目的構建設置界面,如下:

img

如果想為生成的目標程式,添加命令行參數,那麼需要用到項目模式的運行設置

點擊左側的項目模式,可以打開當前項目的運行設置界面,如下:

img

此時可以在 main.cpp 中,添加如下代碼來輸出命令行參數,如下:

qDebug() << "參數個數:" << argc;
qDebug() << "參1:" << argv[0];
qDebug() << "參2:" << argv[1];
qDebug() << "參3:" << argv[2]; 

執行結果:

參數個數: 3
參1: E:\qt_project\HelloQt\debug\HelloQt.exe
參2: 123
參3: 456 

構建設置和運行設置之後,來看項目構建時,【編譯輸出】視窗的輸出,如下:

# 1、qmake 生成 Makefile
 23:22:27: 正在啟動 "C:\Qt\5.15.2\mingw81_32\bin\qmake.exe" E:\qt_project\HelloQt\HelloQt.pro -spec win32-g++ "CONFIG+=debug" "CONFIG+=qml_debug"

 23:22:28: 進程"C:\Qt\5.15.2\mingw81_32\bin\qmake.exe"正常退出。
 23:22:28: 正在啟動 "C:\Qt\Tools\mingw810_32\bin\mingw32-make.exe" -f E:/qt_project/HelloQt/Makefile qmake_all

 mingw32-make: Nothing to be done for 'qmake_all'.
 23:22:28: 進程"C:\Qt\Tools\mingw810_32\bin\mingw32-make.exe"正常退出。
 23:22:28: 正在啟動 "C:\Qt\Tools\mingw810_32\bin\mingw32-make.exe" -j4

 C:/Qt/Tools/mingw810_32/bin/mingw32-make -f Makefile.Debug
 mingw32-make[1]: Entering directory 'E:/qt_project/HelloQt'

 # 2、執行 uic.exe,將ui文件(xml格式),轉換為代碼文件(.h)
 C:\Qt\5.15.2\mingw81_32\bin\uic.exe mywindow.ui -o ui_mywindow.h

 # 3、g++ 開始編譯,以生成目標文件
 g++ -c -fno-keep-inline-dllexport -g -std=gnu++1z -Wall -Wextra -Wextra -fexceptions -mthreads -DUNICODE -D_UNICODE -DWIN32 -DMINGW_HAS_SECURE_API=1 -DQT_QML_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_NEEDS_QMAIN -I. -IC:\Qt\5.15.2\mingw81_32\include -IC:\Qt\5.15.2\mingw81_32\include\QtWidgets -IC:\Qt\5.15.2\mingw81_32\include\QtGui -IC:\Qt\5.15.2\mingw81_32\include\QtANGLE -IC:\Qt\5.15.2\mingw81_32\include\QtCore -Idebug -I. -IC:\Qt\5.15.2\mingw81_32\mkspecs\win32-g++  -o debug\main.o main.cpp
 g++ -c -fno-keep-inline-dllexport -g -std=gnu++1z -Wall -Wextra -Wextra -fexceptions -mthreads -DUNICODE -D_UNICODE -DWIN32 -DMINGW_HAS_SECURE_API=1 -DQT_QML_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_NEEDS_QMAIN -I. -IC:\Qt\5.15.2\mingw81_32\include -IC:\Qt\5.15.2\mingw81_32\include\QtWidgets -IC:\Qt\5.15.2\mingw81_32\include\QtGui -IC:\Qt\5.15.2\mingw81_32\include\QtANGLE -IC:\Qt\5.15.2\mingw81_32\include\QtCore -Idebug -I. -IC:\Qt\5.15.2\mingw81_32\mkspecs\win32-g++  -o debug\mywindow.o mywindow.cpp
 g++ -fno-keep-inline-dllexport -g -std=gnu++1z -Wall -Wextra -Wextra -dM -E -o debug\moc_predefs.h C:\Qt\5.15.2\mingw81_32\mkspecs\features\data\dummy.cpp
 C:\Qt\5.15.2\mingw81_32\bin\moc.exe -DUNICODE -D_UNICODE -DWIN32 -DMINGW_HAS_SECURE_API=1 -DQT_QML_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_NEEDS_QMAIN --include E:/qt_project/HelloQt/debug/moc_predefs.h -IC:/Qt/5.15.2/mingw81_32/mkspecs/win32-g++ -IE:/qt_project/HelloQt -IC:/Qt/5.15.2/mingw81_32/include -IC:/Qt/5.15.2/mingw81_32/include/QtWidgets -IC:/Qt/5.15.2/mingw81_32/include/QtGui -IC:/Qt/5.15.2/mingw81_32/include/QtANGLE -IC:/Qt/5.15.2/mingw81_32/include/QtCore -IC:/Qt/Tools/mingw810_32/lib/gcc/i686-w64-mingw32/8.1.0/include/c++ -IC:/Qt/Tools/mingw810_32/lib/gcc/i686-w64-mingw32/8.1.0/include/c++/i686-w64-mingw32 -IC:/Qt/Tools/mingw810_32/lib/gcc/i686-w64-mingw32/8.1.0/include/c++/backward -IC:/Qt/Tools/mingw810_32/lib/gcc/i686-w64-mingw32/8.1.0/include -IC:/Qt/Tools/mingw810_32/lib/gcc/i686-w64-mingw32/8.1.0/include-fixed -IC:/Qt/Tools/mingw810_32/i686-w64-mingw32/include mywindow.h -o debug\moc_mywindow.cpp
 g++ -c -fno-keep-inline-dllexport -g -std=gnu++1z -Wall -Wextra -Wextra -fexceptions -mthreads -DUNICODE -D_UNICODE -DWIN32 -DMINGW_HAS_SECURE_API=1 -DQT_QML_DEBUG -DQT_WIDGETS_LIB -DQT_GUI_LIB -DQT_CORE_LIB -DQT_NEEDS_QMAIN -I. -IC:\Qt\5.15.2\mingw81_32\include -IC:\Qt\5.15.2\mingw81_32\include\QtWidgets -IC:\Qt\5.15.2\mingw81_32\include\QtGui -IC:\Qt\5.15.2\mingw81_32\include\QtANGLE -IC:\Qt\5.15.2\mingw81_32\include\QtCore -Idebug -I. -IC:\Qt\5.15.2\mingw81_32\mkspecs\win32-g++  -o debug\moc_mywindow.o debug\moc_mywindow.cpp
 g++ -Wl,-subsystem,windows -mthreads -o debug\HelloQt.exe debug/main.o debug/mywindow.o debug/moc_mywindow.o  C:\Qt\5.15.2\mingw81_32\lib\libQt5Widgets.a C:\Qt\5.15.2\mingw81_32\lib\libQt5Gui.a C:\Qt\5.15.2\mingw81_32\lib\libQt5Core.a  -lmingw32 C:\Qt\5.15.2\mingw81_32\lib\libqtmain.a -LC:\openssl\lib -LC:\Utils\my_sql\mysql-5.7.25-win32\lib -LC:\Utils\postgresql\pgsql\lib -lshell32

 mingw32-make[1]: Leaving directory 'E:/qt_project/HelloQt'
 23:22:35: 進程"C:\Qt\Tools\mingw810_32\bin\mingw32-make.exe"正常退出。
 23:22:35: Elapsed time: 00:11. 

4、uic 工具

uic 用於將 ui 文件轉換為 .h 文件

我們知道在編譯項目時,編譯器編譯的是 cpp/.h 文件,而 ui 文件本身是 xml 格式的,因此需要將 ui 文件轉換為編譯器可編譯的 cpp/.h 文件

根據以上的編譯輸出可知,uic 工具會將 mywindow.ui 轉換為 ui_mywindow.h,如下:

class Ui_MyWindow
{
public:
    QWidget *centralwidget;
    QPushButton *btn_start;
    QPushButton *btn_stop;
    QMenuBar *menubar;
    QStatusBar *statusbar;

    void setupUi(QMainWindow *MyWindow)
    {
        if (MyWindow->objectName().isEmpty())
            MyWindow->setObjectName(QString::fromUtf8("MyWindow"));

        MyWindow->resize(800, 600);

        // 只有將 centralwidget 的父視窗設置為我們自己定義的MyWindow,才能在MyWindow show出來時,才可以將centralwidget也顯示出來
        centralwidget = new QWidget(MyWindow);
        centralwidget->setObjectName(QString::fromUtf8("centralwidget"));

  // 同理,只有將 btn 的父視窗設置為centralwidget,才能在centralwidget顯示出來時,才可以將btn也顯示出來
        btn_start = new QPushButton(centralwidget);
        btn_start->setObjectName(QString::fromUtf8("btn_start"));
        btn_start->setGeometry(QRect(270, 260, 75, 24));
        btn_stop = new QPushButton(centralwidget);
        btn_stop->setObjectName(QString::fromUtf8("btn_stop"));
        btn_stop->setGeometry(QRect(400, 260, 75, 24));

        MyWindow->setCentralWidget(centralwidget);

        // 同理,只有將 menubar 的父視窗設置為我們自己定義的MyWindow,才能在MyWindow show出來時,才可以將menubar也顯示出來
        menubar = new QMenuBar(MyWindow);
        menubar->setObjectName(QString::fromUtf8("menubar"));
        menubar->setGeometry(QRect(0, 0, 800, 22));
        MyWindow->setMenuBar(menubar);

        // 同理,只有將 statusbar 的父視窗設置為我們自己定義的MyWindow,才能在MyWindow show出來時,才可以將 statusbar 也顯示出來
        statusbar = new QStatusBar(MyWindow);
        statusbar->setObjectName(QString::fromUtf8("statusbar"));
        MyWindow->setStatusBar(statusbar);

        retranslateUi(MyWindow);

        QMetaObject::connectSlotsByName(MyWindow);
    } // setupUi

    void retranslateUi(QMainWindow *MyWindow)
    {
        MyWindow->setWindowTitle(QCoreApplication::translate("MyWindow", "MyWindow", nullptr));
        btn_start->setText(QCoreApplication::translate("MyWindow", "start", nullptr));
        btn_stop->setText(QCoreApplication::translate("MyWindow", "stop", nullptr));
    } // retranslateUi

};

namespace Ui {
    // 這個Ui命名空間中的 MyWindow 類,本身是空實現,但是它繼承自 Ui_MyWindow 類
    // Ui_MyWindow 類就是和 mywindow.ui 這個xml文件一一對應的。

    // 比如在在ui設計界面拖放兩個按鈕,可以在其xml文件中看到兩個按鈕的屬性
    // uic 工具就會根據xml文件中的屬性,在 ui_mywindow.h 文件中,生成對應的代碼
    // 比如在ui設計界面設置兩個按鈕的顯示文字為“start”和“stop”,那麼在生成的這個.h文件中,就會生成對應的代碼,如上↑
    class MyWindow: public Ui_MyWindow {};
} // namespace Ui 

至此,我們重新回到上面的三個問題,應該就可以回答了

ui_mywindow.h 這個文件是怎麼來的?

答:

通過 Qt 提供的 uic 工具,自動將 .ui 文件,轉換為編譯器可以編譯的 .h 文件

為什麼說 Ui::MyWindow 這個 c++ 中的類,是和 xml 格式的 mywindow.ui 文件一一對應的?

答:

uic 工具會去解析 .ui 的 xml 格式文件,依次讀取其中的欄位,比如 width、height、property 等,來生成對應的 c++ 代碼

為什麼上面需要調用 ui->setupUi(this); 才可以將界面上拖拽的按鈕顯示出來

答:

我們自己定義的MyWindow要顯示,直接調用其show方法,如果子控制項要隨我們的視窗一起顯示,那麼子控制項創建時的父視窗就要指定為我們的MyWindow

一句話總結:父視窗顯示時,會將其子視窗一起顯示出來。

img

1.5 標準信號槽

1、什麼是信號槽
(1)信號
首先看一下什麼是事件和信號

以 QPushButton 的單擊事件為例:

按下按鈕,會觸發 mousePressEvent 事件,然後 QPushButton 會發射 pressed() 信號;

鬆開按鈕,會觸發 mouseReleaseEvent 事件,然後 QPushButton 會發射 released() 信號和 clicked() 信號

常用的事件有很多,比如滑鼠的單擊和雙擊事件,滑鼠的移動事件,鍵盤的輸入事件等。事件會專門在後邊進行講解

當某個實例化的對象上產生這些事件時,該實例化對象就會發出特定的信號。

信號的本質就是函數,並且是只需聲明,無需實現的函數

具體一個類有哪些信號,可以查看 Qt 的幫助文檔,以 QPushButton 為例:

首先打開 QPushButton 的幫助說明:

img

接著,跳轉到其父類 QAbstractButton,這裡就有信號了,如下:

img

點擊【signals】可以跳轉到信號處,如下:

img

可以點擊對應的鏈接,查看詳細說明

這裡總結如下:

// 當按鈕被點擊(按下並抬起)之後,發送該信號,其中帶有一個預設參數
// 對於QPushButton 通常不需要傳遞這個預設參數
// 對於可選中/取消選中的按鈕,比如覆選框QCheckBox、單選框QRadioButton 可以通過該參數,獲取其是否選中
void clicked(bool checked = false);

// 當按鈕被按下時,發送該信號
void pressed();

// 當按鈕被抬起時,發送該信號
void released();

// 當按鈕狀態改變時,發送該信號,其中帶有一個參數checked
// checked 用於標識覆選框QCheckBox、單選框QRadioButton是否被選中
void toggled(bool checked); 

(2)槽
我們通常說的 槽,就是槽函數

當點擊了 QPushButton 按鈕之後,通常需要執行對應的操作,比如讓 QMainWindow 視窗最大/最小/正常化顯示,或者關閉視窗

按照以上查看信號的方法,查看 QMainWindow 提供了哪些槽函數,同樣跳轉到其父類 QWidget 中查看,如下:

img

可以點擊對應的鏈接,查看詳細說明,比如:

// 最大化顯示
void showMaximized();

// 最小化顯示
void showMinimized();

// 正常顯示
void showNormal();

// 關閉視窗
bool close(); 

講解了信號和槽之後,如何實現如下效果呢?

效果:點擊按鈕後,實現視窗的最大/最小/正常顯示,或者關閉視窗。

答:這就需要將信號和槽使用 connect 函數進行連接

比如將 QPushButton 按鈕的 clicked() 信號和 QMainWindow 視窗的 close() 槽函數建立連接之後,當點擊了 QPushButton 按鈕後,Qt 框架就

會自動調用 QMainWindow 視窗的 close() 槽函數,從而實現視窗的關閉。

connect 方法是 QObject 類的靜態方法,它有多個重載的方法,如下:

img

Qt4 和 Qt5 中連接信號和槽的方法略有不同,後面會詳細說明。

不過總的來說,connect 函數的一般形式如下:

connect(sender, signal, receiver, slot); 

其中:

sender

發出信號的對象。比如 QPushButton 按鈕

signal

發出的信號。比如 clicked()

receiver

接收信號的對象。比如 QMainWindow 視窗

slot

接收到信號之後,調用的函數

一句話總結:信號槽是對象之間的信息通訊的方式

2、標準信號槽的使用
這裡以一個實際的案例,來演示信號和槽的使用

案例:點擊對應按鈕,實現視窗的最大化/最小化/正常顯示視窗,和關閉視窗

img

(1)新建工程
按照之前的講解,一步步新建一個視窗工程

(2)界面佈局
img

放置按鈕並佈局

放置4個按鈕;

選中視窗,點擊工具欄的【水平佈局】,進行佈局(這樣可以對齊並且自適應視窗大小的變化);

在左右兩側各放一個 Horizontal Spacer(避免按鈕太大,影響美觀);

見名知意

修改窗體的標題為 “標準信號槽-演示”;

放置4個按鈕顯示的文字,以及name

(3)連接信號槽

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 連接信號和槽
    connect(ui->btnMax, SIGNAL(clicked()), this, SLOT(showMaximized()));
    connect(ui->btnNormal, SIGNAL(clicked()), this, SLOT(showNormal()));
    connect(ui->btnMin, SIGNAL(clicked()), this, SLOT(showMinimized()));
    connect(ui->btnClose, SIGNAL(clicked()), this, SLOT(close()));
} 

這樣,點擊按鈕,就實現了預期的功能!

(4)拓展:快捷鍵
向前複製當前行: ctrl + alt + up (向上箭頭)

向後複製當前行:ctrl + alt + down(向下箭頭)

在使用這兩個快捷鍵時:按住ctrl + alt +箭頭,可能與系統圖形的快捷鍵衝突, 導致在 Qt Creator 中無法使用

解決辦法:

大多數人使用的是因特爾的核心顯卡,則用如下方法:

右鍵點擊桌面空白處 -> 圖形選項 -> 快捷鍵 ->禁用,就OK了。

或者,點擊圖形屬性 -> 找到 ctrl+alt+up 和 ctrl+alt+down,禁用掉就好了,如下:

img

1.6 自定義信號槽

自定義信號和槽的條件:

自定義的類,要繼承自 QObject

自定義的類,其中要聲明一個巨集 Q_OBJECT

只有滿足了這兩個條件才可以正常使用信號槽機制(當然,槽函數是全局函數、 Lambda 表達式等無需接收者的時候除外,後邊講解)。

1、自定義信號槽案例
接下來,我們通過一個案例,演示自定義信號槽的使用。

案例:“長官” (Commander)發送一個 “沖” (go) 的信號,然後 “士兵" (Soldier)執行“ 戰鬥” (fight) 的槽函數

(1)創建 Commander 類
在左側項目文件名上右鍵 -> 添加新文件:

img

指定類名和父類

img

點擊完成,即可添加cpp和.h文件到項目中

img

創建完成之後的 commander.cpp 和 commander.h 文件,內容如下:

#include <QObject>

// 1.自定義的類,需要繼承自 QObject 類
class Commander : public QObject
{
    // 2.並且添加 Q_OBJECT 巨集,才能正常使用 Qt 的信號和槽機制
    Q_OBJECT
public:
    explicit Commander(QObject *parent = nullptr);
// 3.在 signals 後面添加自定義的信號即可
signals:

};

#endif // COMMANDER_H

// commander.cpp
#include "commander.h"

Commander::Commander(QObject *parent)
    : QObject{parent}
{

} 

(2)添加自定義信號
在 signals 下麵添加自定義的信號即可

class Commander : public QObject
{
    Q_OBJECT
public:
    explicit Commander(QObject *parent = nullptr);

signals:
    // 1.信號只需聲明,無需實現
    // 2.信號返回值為 void
    void go();
}; 

3)添加 Soldier 類
按照同樣的方法,添加 Soldier 類,創建完成之後的 soldier.h 和 soldier.cpp 文件,內容如下:

// soldier.h
 #ifndef SOLDIER_H
 #define SOLDIER_H

 #include <QObject>

 class Soldier : public QObject
 {
     Q_OBJECT
 public:
     explicit Soldier(QObject *parent = nullptr);

 signals:

 };

 #endif // SOLDIER_H

 // soldier.cpp
 #include "soldier.h"

 Soldier::Soldier(QObject *parent)
     : QObject{parent}
 {

 } 

(4)添加自定義槽
Soldier 士兵類,需要實現一個 fight 的槽函數

class Soldier : public QObject
 {
     Q_OBJECT
 public:
     explicit Soldier(QObject *parent = nullptr);

 signals:

 // 1.通常將槽函數添加到 slots 後面
 // 這個 slots 也可以不寫。不過建議寫上,以指明這是一個槽函數
 // pulic,表示槽函數既可以在當前類及其子類的成員函數中調用,也可以在類外部的其它函數(比如 main() 函數)中調用
 public slots:
    // 2.槽函數的返回值和參數,要和信號保持一致
    // 由於信號無返回值,因此槽函數也無返回值
    // 由於信號無參數,因此槽函數也無參數
    void fight();
 }; 

在 soldier.h 文件中有了槽函數的聲明,還需要在 soldier.cpp 中實現

可以在槽函數聲明處,直接按 alt + enter 快捷鍵,快速生成函數定義

void Soldier::fight()
{
    qDebug() << "fight";
} 

(5)連接自定義的信號和槽
信號和槽都已經定義完畢, 接下來就可以進行連接了

在 mainwindow.cpp 中,定義 Commander 和 Soldier 類的實例,並建立信號和槽的連接,如下:

MainWindow::MainWindow(QWidget *parent)
     : QMainWindow(parent)
     , ui(new Ui::MainWindow)
 {
     ui->setupUi(this);

     // 1. 創建兩個類的實例
     Commander commander;
     Soldier soldier;

     // 2. 建立信號和槽的連接
     connect(&commander, SIGNAL(go()), &soldier, SLOT(fight()));

     // 3. 發送信號
     // emit 可省略
     /*emit*/commander.go();
 } 

這樣,在程式執行後,就可以在【應用程式輸出】視窗看到槽函數執行的結果了:

img

(6)信號和槽的重載
我們知道,信號和槽的本質就是函數,是函數就可以重載,因此,我們可以重載同名的信號,和重載同名的槽函數。

仍然以 Commander 和 Soldier 為例:

在 commander.h 中添加重載的 go 信號

class Commander : public QObject
{
    Q_OBJECT
public:
    explicit Commander(QObject *parent = nullptr);

signals:
    void go();
    void go(QString);
}; 

在 soldier.h 中添加重載的 fight 槽函數:

class Soldier : public QObject
{
    Q_OBJECT
public:
    explicit Soldier(QObject *parent = nullptr);

signals:

public slots:
    void fight();
    void fight(QString);
}; 

在 soldier.cpp 中實現重載的 fight 槽函數:

void Soldier::fight(QString s)
{
    qDebug() << "fight for" << s;
} 

接下來在 mainwindow.cpp 中同時發送重載的兩個 go 信號:

MainWindow::MainWindow(QWidget *parent)
     : QMainWindow(parent)
     , ui(new Ui::MainWindow)
 {
     ui->setupUi(this);

     // 1. 創建兩個類的實例
     Commander commander;
     Soldier soldier;

     // 2. 建立信號和槽的連接
     connect(&commander, SIGNAL(go()), &soldier, SLOT(fight()));
     connect(&commander, SIGNAL(go(QString)), &soldier, SLOT(fight(QString)));

     // 3. 發送信號
     commander.go();
     commander.go("freedom");
 }
 

此時執行程式,就可以在【應用程式輸出】視窗看到兩個重載的槽函數執行的結果了:

img

2、信號槽總結
(1)使用信號和槽的條件
如果要使用信號和槽,需要滿足如下兩個條件

自定義的類,要繼承自 QObject

自定義的類,其中要聲明一個巨集 Q_OBJECT

只有滿足了這兩個條件才可以正常使用信號槽機制。

(2)信號
無需實現

信號的本質是函數,並且只需要聲明,不需要實現;

可以重載

信號本質是函數,因此可以重載;

信號聲明在類頭文件的 signals 域下;

信號返回值類型為 void,參數的類型和個數不限;

信號可以使用 emit 關鍵字發射,emit 也可以省略;

(3)槽
需要實現

槽的本質是函數,需要實現;

可以重載

槽本質是函數,因此可以重載;

槽函數用 slots 關鍵字修飾(其實在 Qt5 中可以省略slots關鍵字,下一節會講解);

返回值

槽函數的返回值,要和信號保持一致。由於信號的返回值為 void,因此槽函數的返回值也是 void

參數

槽函數的參數個數要 <= 信號的參數個數

也就是說:可以在槽函數中選擇忽略信號傳來的數據(也就是槽函數的參數比信號的少),但是不能說信號根本沒有這個數據,你就要在槽函數中使用(就是槽函數的參數比信號的多,這是不允許的)

1.7 信號槽-多種連接方式

信號和槽要建立連接,本質上是通過 connect 函數來連接實現的。

但是從寫法或者操作上來說,有多種方式,以下總結了 5 種方式:

Signal/SLOT(Qt4)

函數地址(Qt5)

UI設計師界面 - 轉到槽

UI設計師界面 - 信號槽編輯器

lambda 表達式

大家可根據自己的喜好自行選擇。

接下來通過一個案例,來演示這 5 種使用方法:

img

首先,我們先把這個界面,快速地搭建起來

快速新建一個基於 QMainWindow 的工程:

img

切換到 mainwindow.ui 文件,按照如下方法,設計好界面:

修改視窗標題為 “信號槽的 5 種連接方式”

拖拽 5 個 QPushButton

修改按鈕的 name:btnMax、btnNormal、btnMin、btnClose、btnSetWindowTitle

佈局視窗:垂直佈局,以使得佈局自適應視窗大小的變化

修改按鈕顯示的文字,如上

設置按鈕字體大小為 15

經過以上幾步,就完成了界面的佈局,如下:

img

接下來就開始實現 5 種連接方式。

1、Signal/SLOT(Qt4)
上一節的就是使用的這個方式

通過 Signal/SLOT 這兩個巨集,將函數名以及對應的參數,轉換為字元串,這是 Qt4 中使用的方式,當然在 Qt5 中也是相容支持它的:

connect(sender, SIGNAL(signal()), receiver, SLOT(slot()));
其中:

sender:信號發送者

Signal(Signal()):發送的信號。Signal巨集將信號轉換成字元串

receiver:信號接收者

SLOT(slot()):槽函數。SLOT巨集將槽函數轉換成字元串

這種方式,編譯器不會做錯誤檢查,即使函數名或者參數寫錯了,也可以編譯通過,這樣就把問題留在了運行階段。

而我們編程開發的一個原則是儘可能早地發現並規避問題,因此這種方式不被推薦。

(1)實現視窗最大化
下麵通過這種方式,實現點擊按鈕,最大化視窗

在 mainwindow.cpp 的構造函數中,使用如下方式連接信號和槽:

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 1.使用 SIGNAL/SLOT 的方式連接信號和槽
    connect(ui->btnMax, SIGNAL(clicked()), this, SLOT(showMaximized()));

}

此時,運行程式,點擊按鈕就可以最大化視窗了!

(2)編譯不檢查
剛纔說過,如果函數的名字寫錯了,在編譯時不會報錯,比如將 showMaximized 不小心寫成了 showMaximize

點擊【構建】菜單->【重新構建】,在【編譯輸出】視窗並不會報錯

而在運行時,在【應用程式輸出】視窗會看到報錯,如下:

22:24:45: Starting E:\qt_project\build-HowToConnectSignalAndSlot-Desktop_Qt_5_15_2_MinGW_32_bit-Debug\debug\HowToConnectSignalAndSlot.exe...
QObject::connect: No such slot MainWindow::showMaximize() in ..\HowToConnectSignalAndSlot\mainwindow.cpp:10
QObject::connect:  (sender name:   'btnMax')
QObject::connect:  (receiver name: 'MainWindow')

7.2 函數地址(Qt5)
這種方式中,信號和槽都使用函數的地址,如下:

connect(sender, &Sender::signal, receiver, &Receiver::slot);

其中:

sender:信號發送者

&Sender::Signal:發送的信號

receiver:信號接收者

&Receiver::slot:槽函數

這種方式,編譯時就會對函數類型,參數個數做檢查。

(1)實現視窗正常顯示
下麵通過這種方式,實現點擊按鈕,正常化顯示視窗

在 mainwindow.cpp 的構造函數中,使用如下方式連接信號和槽:

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

    // 1.使用 SIGNAL/SLOT 的方式連接信號和槽
    connect(ui->btnMax, SIGNAL(clicked()), this, SLOT(showMaximized()));
    
    // 2.使用函數地址的方式連接信號和槽
    connect(ui->btnNormal, &QPushButton::clicked, this, &QMainWindow::showNormal);

}

此時,運行程式,點擊按鈕就可以正常化顯示視窗了!

(2)編譯檢查
如果函數的名字寫錯了,在編譯時就會報錯,比如將 showNormal 不小心寫成了 showNorma,少寫一個字母 L,

在編輯器中就會飄紅報錯,如下:

mainwindow.cpp:14:58: No member named 'showNorma' in 'QMainWindow'; did you mean 'QMainWindow::showNormal'? (fix available)
 qwidget.h:486:10: 'QMainWindow::showNormal' declared here

當然了,點擊【構建】菜單->【重新構建】,在【編譯輸出】視窗也會會報錯,如下:

 ..\HowToConnectSignalAndSlot\mainwindow.cpp: In constructor 'MainWindow::MainWindow(QWidget*)':
 ..\HowToConnectSignalAndSlot\mainwindow.cpp:14:71: error: 'showNorma' is not a member of 'QMainWindow'
      connect(ui->btnNormal, &QPushButton::clicked, this, &QMainWindow::showNorma);
                                                                        ^~~~~~~~~
 mingw32-make[1]: *** [Makefile.Debug:469: debug/mainwindow.o] Error 1

可見,使用這種方式,錯誤在代碼編輯,程式編譯階段就會暴露出來,及早解決掉它。

這是 Qt 推薦的一種連接信號槽的方式!

3、UI設計師界面-轉到槽
下麵使用這種方式,實現點擊 btnMin 按鈕,最小化顯示視窗

在UI設計師界面,右鍵單擊 btnMin,然後選擇【轉到槽...】,彈出如下視窗:

轉到槽
選擇 clicked(),即可生成並跳轉到槽函數,即可在 mainwindow.h 和 mainwindow.cpp 中生成對應的代碼,如下:

// mainwindow.h
class MainWindow : public QMainWindow
{
    Q_OBJECT

 ...

private slots:
    void on_btnMin_clicked();
};

// mainwindow.cpp
void MainWindow::on_btnMin_clicked()
{
    this->showMinimized();
}

此時會根據按鈕的 name 自動生成對應的槽函數,對應關係為:

按鈕的名字:btn1

槽函數的名字為:on_btn1_clicked

註意:如果修改了按鈕的 name,那麼槽函數的名字也要隨之修改。

7.4 UI設計師界面-信號槽編輯器
下麵使用這種方式,實現點擊 btnClose 按鈕,關閉視窗

進入到 UI 設計師界面,【View】菜單 ->【視圖】->【Signal & Slots Editor】,在打開的信號槽編輯器中,點擊綠色的加號+,

就可以連接信號和槽了:

img

此時,我們的代碼文件並沒有修改,而是修改了 mainwindow.ui 文件,如下:

img

5、Lambda 表達式
槽函數還可以直接寫成 lambda 表達式的形式。

C++11 引入了 lambda 表達式,用於定義並創建匿名的函數對象,可以使代碼更加的簡潔

Qt 是基於 C++ 的一個 GUI 框架,它完全支持 C++ 的語法,因此在 Qt 中也是可以使用 lambda 表達式的。

學習 Qt 多多少少都需要瞭解一些 C++ 的語法

不過如果你對 C++ 中的 lambda 表達式不瞭解,也不用擔心,這裡我們先複習一下 C++ lambda 表達式的使用

lamda 表達式在現代語言中,非常的普遍。lamda表達式簡化了我們的一些語法,使得代碼的表達更加的清晰,便於書寫與閱讀。

一些現代語言,比如 java、python、kotlin 等語言都是支持 lambda 表達式的

(1)複習 lambda 表達式
C++ 中的 lambda 表達式,其實就是匿名函數,語法如下:

[capture](parameters) option -> return-type { body }

其中包含 5 個部分:

capture:捕獲列表,可選

捕捉列表總是出現在 lambda 表達式的開始。實際上,是 lambda 引出符,編譯器根據該引出符判斷接下來的代碼是否是 lambda 表達式。

捕捉列表能夠捕獲上下文中的變數,以在 lambda 表達式內使用,主要有如下幾種情況:

# 不捕獲任何變數

[]

# 按引用捕獲外部作用域中所有變數,在 lambda 表達式內使用

[&]

# 按值捕獲外部作用域中所有變數,在 lambda 表達式內使用

# 按值捕獲的變數,在 lambda 表達式內是只讀的,不能修改賦值

[=]

# 捕獲外部作用域中所有變數,其中foo變數按引用捕獲,其他的按值捕獲

[=, &foo]

# 按值捕獲bar變數,同時不捕獲其他變數

[bar]

# 捕獲當前類中的 this 指針

# 捕獲了 this 就可以在 lambda 中使用當前類的成員變數和成員函數。

# 如果已經使用了 & 或者 =,就預設添加此選項。

[this]

parameters:參數列表,可選

option:函數選項,可選

return-type:返回值類型,可選。沒有返回值的時候也可以連同符號一起省略

body:函數體

接下來,在 mainwindow.cpp 的構造函數中,逐一演示這 5 個部分的使用

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);

 ...

    // 演示lambda表達式
    
    // 3.1 匿名函數的定義

#if 0
    []() {
        qDebug() << "lambda...";
    };
#endif

    // 3.2 匿名函數的調用

#if 0
    []() {
        qDebug() << "lambda...";
    }();
#endif

    int a =10;
    
    // 3.3 不捕獲任何變數
    // Variable 'a' cannot be implicitly captured in a lambda with no capture-default specified

#if 0
    []() {
        qDebug() << a;
    }();
#endif

    // 3.4 按引用捕獲

#if 0
    [&]() {
        qDebug() << a++;    // 10
    }();
    qDebug() << a;          // 11
#endif

    // 3.5 按值捕獲
    // 按值捕獲的變數,在 lambda 表達式內是只讀的,不能修改賦值

#if 0
    [=]() {
        // Cannot assign to a variable captured by copy in a non-mutable lambda
        qDebug() << a++;
    }();
#endif

    // 3.6 按值捕獲 + mutalbe 選項
    // 添加 mutable 選項,就可以在 lambda 內修改捕獲的變數了
    // 並且=這種方式,是按值傳遞的,裡面的修改,不會影響外邊。

#if 0
    [=]() mutable {
        qDebug() << a++;    // 10
    }();
    qDebug() << a;          // 10
#endif

    // 3.7 參數
    // 添加 mutable 選項,就可以在 lambda 內修改捕獲的變數了
    // 並且=這種方式,是按值傳遞的,裡面的修改,不會影響外邊。

#if 0
    [](int x, int y) {
        qDebug() << x + y;    // 3
    }(1, 2);
#endif

    // 3.8 返回值
    // 返回值可以省略,編譯器會自動推斷 lambda 表達式的返回值類型
    // 返回值省略時,也可以連同符號`->`一起省略

#if 0
    int sum = [](int x, int y) -> int {
        return x + y;
    }(1, 2);
    qDebug() << sum;    // 3
#endif

#if 1
    int sum = [](int x, int y) {
        return x + y;
    }(1, 2);
    qDebug() << sum;    // 3
#en

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • >本文時間 2023-05-20 >作者:sugerqube漆瓷 `cd`,`vi`,`clear`這些屬於常見常用命令本文不再贅述。 # 安裝命令 `yum install vim`舉例安裝vim `rpm -ivh a.rpm b.rpm c.rpm`舉例安裝a,b,c(涉及包相互依賴) # 用 ...
  • 網上的教學視頻大部分全是以centos為教材底子——沒辦法更換系統了,這樣方便麻! 我參考的文章: https://blog.csdn.net/shengjie87/article/details/106805105#:~:text=%E7%94%A8%E6%8C%82%E8%BD%BD%E5%85 ...
  • Wayland 配置起來確實相對麻煩很多,需要註意很多細節,如果不註意就會出現問題,在這裡說一下可能的現象與解決方法。 根據觀察,這些現象在 GNOME 與 KDE 桌面環境鐘均會出現。 ## 現象 ### App 打開慢 現象為當首次打開一個圖形化的 App 時,需要等待2-3秒鐘才會打開,但是如 ...
  • 上一講我們安裝 etcd 服務端,這一講我們來一起學學如何使用 etcd 客戶端常見的命令。文章內容來源於參考資料,如若侵權,請聯繫刪除,謝謝。 > etcd可通過客戶端命令行工具 etcdctl 對etcd進行請求操作 ```sh # 幫助命令,會列出所有的命令和選項,在記不太清命令的時候,可以使 ...
  • > 本文首發於公眾號:Hunter後端 > 原文鏈接:[es筆記四之中文分詞插件安裝與使用](https://mp.weixin.qq.com/s/aQuwrUzLZDKLv_K8dKeVzw) 前面我們介紹的操作及演示都是基於英語單詞的分詞,但我們大部分使用的肯定都是中文,所以如果需要使用分詞的操 ...
  • 在上一篇博客中,我們介紹了用Python對來實現一個Scheme求值器。然而,我們跳過了部分特殊形式(special forms)和基本過程(primitive procedures)實現的介紹,如特殊形式中的delay、cons-stream,基本過程中的force、streawn-car、str... ...
  • ## 1.自定義starter的作用 在我們的日常開發工作中,經常會有一些獨立於業務之外的配置模塊,比如阿裡雲oss存儲的時候,我們需要一個工具類進行文件上傳。我們經常將其放到一個特定的包下,然後如果另一個工程需要復用這塊功能的時候,需要將代碼硬拷貝到另一個工程,重新集成一遍,這樣會非常麻煩。如果我 ...
  • # 使用 Async Rust 構建簡單的 P2P 節點 ### P2P 簡介 - P2P:peer-to-peer - P2P 是一種網路技術,可以在不同的電腦之間共用各種計算資源,如 CPU、網路帶寬和存儲。 - P2P 是當今用戶線上共用文件(如音樂、圖像和其他數字媒體)的一種非常常用的方法 ...
一周排行
    -Advertisement-
    Play Games
  • 前言 在我們開發過程中基本上不可或缺的用到一些敏感機密數據,比如SQL伺服器的連接串或者是OAuth2的Secret等,這些敏感數據在代碼中是不太安全的,我們不應該在源代碼中存儲密碼和其他的敏感數據,一種推薦的方式是通過Asp.Net Core的機密管理器。 機密管理器 在 ASP.NET Core ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 順序棧的介面程式 目錄順序棧的介面程式頭文件創建順序棧入棧出棧利用棧將10進位轉16進位數驗證 頭文件 #include <stdio.h> #include <stdbool.h> #include <stdlib.h> 創建順序棧 // 指的是順序棧中的元素的數據類型,用戶可以根據需要進行修改 ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • C總結與剖析:關鍵字篇 -- <<C語言深度解剖>> 目錄C總結與剖析:關鍵字篇 -- <<C語言深度解剖>>程式的本質:二進位文件變數1.變數:記憶體上的某個位置開闢的空間2.變數的初始化3.為什麼要有變數4.局部變數與全局變數5.變數的大小由類型決定6.任何一個變數,記憶體賦值都是從低地址開始往高地 ...
  • 如果讓你來做一個有狀態流式應用的故障恢復,你會如何來做呢? 單機和多機會遇到什麼不同的問題? Flink Checkpoint 是做什麼用的?原理是什麼? ...
  • C++ 多級繼承 多級繼承是一種面向對象編程(OOP)特性,允許一個類從多個基類繼承屬性和方法。它使代碼更易於組織和維護,並促進代碼重用。 多級繼承的語法 在 C++ 中,使用 : 符號來指定繼承關係。多級繼承的語法如下: class DerivedClass : public BaseClass1 ...
  • 前言 什麼是SpringCloud? Spring Cloud 是一系列框架的有序集合,它利用 Spring Boot 的開發便利性簡化了分散式系統的開發,比如服務註冊、服務發現、網關、路由、鏈路追蹤等。Spring Cloud 並不是重覆造輪子,而是將市面上開發得比較好的模塊集成進去,進行封裝,從 ...
  • class_template 類模板和函數模板的定義和使用類似,我們已經進行了介紹。有時,有兩個或多個類,其功能是相同的,僅僅是數據類型不同。類模板用於實現類所需數據的類型參數化 template<class NameType, class AgeType> class Person { publi ...
  • 目錄system v IPC簡介共用記憶體需要用到的函數介面shmget函數--獲取對象IDshmat函數--獲得映射空間shmctl函數--釋放資源共用記憶體實現思路註意 system v IPC簡介 消息隊列、共用記憶體和信號量統稱為system v IPC(進程間通信機制),V是羅馬數字5,是UNI ...