以下內容為本人的著作,如需要轉載,請宣告原文鏈接 微信公眾號「englyf」https://www.cnblogs.com/englyf/p/16733091.html
初識Qt Quick
很高興可以來到這一章,終于可以開始講講最近幾年Qt的熱門技術Quick這一塊了,
啥是Qt?
哦,這是一個宣稱可以跨任意平臺,開發各種場景應用軟體的開發框架,從三個維度來講,就是開發庫framework,集成開發平臺IDE,以及成熟的開發思維模式,
Qt Quick最早出現在Qt的4.7版本中,目標是在UI設計者與開發者之間搭建一個更高效合作平臺,給開發者更好的UI開發體驗,雖然幾經易手,Qt在digia公司這些年的努力迭代更新下,Qt Quick終于迎來了成熟穩定的版本(這也是我愿意在最近的專案里轉用它的原因),
至于Qt Quick和老一套開發核心Qwidget的區別,其中最重點的就是提供了新的UI描述語言QML(Qt Meta-object Language,Qt元物件描述語言),QML乍看起來有點像json,但是核心思想卻是模仿web頁面,沒錯,在QML檔案中允許搭配Javascript代碼,就可以輔助實作豐富的UI互動邏輯,
如果你以往習慣QWidget開發,那么Qt Quick真的非常值得上手試試,
好了,口水吐多了招人厭,下面直入廬山一窺真面目!
手剝一個簡單的功能程式開發栗子
在Qt開發程序中,Qt官方IDE(Qt Creator)提供了好幾種工程構建工具,比如簡單易懂的qmake,火上天的cmake,還有貌似沒人聽說過的Qbs,而目前Qt主推的構建方式就是cmake,下面要講的例子也是用cmake,
1.開發環境配置
Win10
Qt 6.2.4
Qt Creator 8.0.1
Mingw 11.2.0 64bit
Cmake 3.23.2
這里選用的Qt版本是寫作時最新的LTS版本,LTS意思就是官方長期支持更新,比如說,一兩年內還會發布一下補丁和安全更新,至于新功能特性就別想了,
2.創建Qt Quick工程
先用Qt Creator創建一個簡單的quick工程,工程構建描述的內容就保存在工程根目錄的組態檔CMakeLists.txt中,如下:
cmake_minimum_required(VERSION 3.16)
project(instance VERSION 0.1 LANGUAGES CXX)
set(CMAKE_AUTOMOC ON)
#set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(Qt6 6.2 COMPONENTS Quick REQUIRED)
file(GLOB_RECURSE SOURCE_FILES
./src/*.cpp
./src/*.h
)
qt_add_resources(SOURCE_FILES instance.qrc)
qt_add_executable(instance
${SOURCE_FILES}
)
set_target_properties(instance PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
target_link_libraries(instance
PRIVATE Qt6::Quick)
install(TARGETS instance
BUNDLE DESTINATION .
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR})
cmake_minimum_required用于宣告當前的組態檔適用于的cmake最低版本,同時為了防止使用過于低級的版本來構建當前工程,避免某些指令不支持或者不兼容,
project用于宣告當前工程名稱,和開發語言,CXX代表了C++,
CMAKE_AUTOMOC用于標記是否開啟自動MOC,Qt不僅僅是開發庫,它同時也對開發語言(比如C++)做了拓展,那么原始碼檔案中就會多多少少會包含有通用編譯器無法識別的部分內容,MOC就是用于對Qt的擴展內容進行轉換的工具,
CMAKE_AUTORCC用于標記是否開啟自動RCC,在Qt工程中會包含有被最終輸出的執行程式所需要的資源內容,比如音視頻,圖片等等,那么為了高效呼叫這些資源,勢必需要對原本的資源檔案進行處理再保存到額外的二進制檔案中,甚至內嵌到執行檔案中,RCC就是對這些資源檔案進行處理和再輸出工具,
由于我的例子工程中需要用到MOC和RCC,所以CMAKE_AUTOMOC和CMAKE_AUTORCC都打開,
CMAKE_AUTOUIC用于標記是否開啟自動UIC,如果開發界面用的技術堆疊是QWidget,那么在Qt工程中就需要創建.ui檔案并保存設計內容到其中,編譯的時候也需要用UIC把.ui檔案轉換成.h檔案,不過,這里用Quick技術堆疊開發界面,因此無需打開CMAKE_AUTOUIC,在最前面添加#表示注釋掉該行陳述句(該行陳述句會被決議器忽略),
用find_package匯入Qt的Quick模塊,
由于本工程需要用到多個C++源檔案,所以這里采用了遞回參考檔案的方式把特定檔案夾下面的所有.cpp、.h等檔案都囊括進來,需要輔以通配符*,所有被囊括的檔案路徑被追加到動態陣列SOURCE_FILES中,方便后邊參考,語法格式如下:
file(GLOB_RECURSE <variable> [FOLLOW_SYMLINKS]
[LIST_DIRECTORIES true|false] [RELATIVE <path>]
[<globbing-expressions>...])
格式里的variable實際使用SOURCE_FILES代替,
qt_add_resources的作用是呼叫RCC對資源檔案(.qrc)編譯成qrc_開頭的源檔案再輸出,并且把輸出的原始碼檔案路徑追加到動態陣列SOURCE_FILES中,
當然,動態陣列SOURCE_FILES這個名字可以按照需求自定義設定,這里取名為源檔案,
qt_add_executable指明構建的目標是二進制檔案instance,參考的源檔案來自于動態陣列SOURCE_FILES,
target_link_libraries用于指明構建時鏈接Qt6::Quick的相關庫,
剩余的陳述句都是Qt Creator創建工程時自動添加的內容,這里略過,
然后看看我的工程目錄結構在Qt Creator中的展示:

如果用VSCODE打開工程目錄,可以看到:

3.使用元物件描述檔案(QML)描述界面
使用Qt Creator自動創建的Quick應用,除了會自動生成組態檔CMakeLists.txt之外,還包含了main.cpp和main.qml檔案,原始碼只實作了啟動之后彈出一個視窗,
這里稍作修改,實作簡單的檔案選擇,以及將選中的檔案路徑名顯示出來,這個功能用QWidget技術堆疊來實作其實是很簡單的啦,不過我們這里目的是演示Quick技術框架怎么用,所以下面來具體看看界面這塊怎么玩:
1)主頁面
// main.qml
import QtQuick
Window {
width: 640
height: 200
visible: true
title: qsTr("Tool V1.0.0")
Viewer {
anchors.fill: parent
}
}
main.qml 這個檔案是首頁元物件的描述檔案,一般QML引擎加載的第一個元物件所在的檔案就命名為main.qml,不過,命名為main.qml不是硬性規定,
首先,可以看到這里通過import匯入了模塊QtQuick,同一行,后邊還可以加上版本號,
Window是一種模塊里預定義的型別,用于表單描述,當然,型別也可以自定義,下面會說到,這里通過對型別Window的實體化來描述一個表單物件,
型別后邊的{}內部包含了型別實體化后的成員屬性、函式、信號、信號處理句柄等,比如width、height、visible、title等都是預定義的屬性,這些屬性如字面意思比較簡單易懂就不一一展開了,大伙要是有興趣可以反饋給我,我再看看意見給大家細聊,
Viewer其實是自定義的型別,這里通過對型別Viewer的實體化來補充添加新的界面元素,Viewer物件內部的屬性anchors.fill: parent描述的是物件在其父物件(Window)中把父物件填充滿,QML一般通過anchors屬性來錨定物件的位置,
上面這個物件里并沒有定義或者參考到函式或者信號等,
2)自定義型別
下面來看看怎么自定義型別
// Viewer.qml
import QtQuick 2.15
import QtQuick.Controls 6.2
import Qt.labs.platform 1.1
Item {
function log(...msg) {
let msgs = "";
msg.forEach((item) => {
if (msgs.length != 0) {
msgs += " ";
}
msgs += item;
});
console.log(msgs);
}
FileDialog {
id: fileDialog
objectName: "fileDialog"
currentFile: selectedFileTextArea.text
onFileChanged: {
log(objectName + ".file =", file.toString().slice(8));
fileMgrInstance.run(file.toString().slice(8));
}
}
Label {
id: fileLable
x: 292
y: 26
text: qsTr("檔案:")
verticalAlignment: Text.AlignVCenter
font.pointSize: 14
}
TextField {
id: selectedFileTextArea
x: 70
y: 70
width: 500
objectName: "selectedFileTextArea"
text: fileDialog.file.toString().slice(8)
font.pointSize: 12
placeholderText: qsTr("選擇檔案")
}
Button {
id: selectFileButton
x: 268
y: 124
width: 105
height: 54
text: qsTr("選擇")
font.pointSize: 10
onClicked: {
log(text, "clicked");
fileDialog.open();
}
}
}
Item是類別庫QtQuick的預定義組件型別,描述的是一個基礎可視組件,quick中所有的可視組件都繼承于它,
一般在QML中自定義型別都會使用基礎的型別Item,然后在其基礎上定制內部屬性、函式、信號、信號處理句柄等,
Item的繼承鏈是這樣的:
Item -> QtObject -> QObject
看到這里,可以猜測一下,其實所有的Quick預定義組件都是繼承于QObject,和QWidget里提供的類別庫太相似了,
function log(...msg)定義了函式log,function是關鍵詞,log是函式名,后邊小括號里的...表示引數不定,這樣子在呼叫log時就可以不限制輸入的引數個數了,
要注意的是,QML內部的函式使用的語法是ECMAScript,也就是我們常常聽到的JavaScript,
FileDialog是類別庫Qt.labs.platform的預定義組件型別,描述的是一個檔案選擇視窗,屬性id,繼承于QObject,用于標記唯一的物件,也就是說所有物件的id都不能重復,無論物件是否處于同一個QML檔案,objectName描述的屬性可用于對物件樹中的物件進行查找,currentFile描述了當前選中的檔案名(包括路徑),在確定最后選中的檔案之前,此屬性也會跟隨選擇而改變,onFileChanged描述了當屬性file值改變時,自動產生的信號的處理句柄(handle),用{}限定處理范圍,log是上面定義的函式呼叫,輸入兩個引數,fileMgrInstance是C++源檔案暴露給QML引擎的特定物件id,通過該id可以呼叫C++中的相應物件的方法屬性(代碼中呼叫了run方法,方法的詳情定義看下文),
C++和QML源檔案之間的物件相互呼叫,會有后續的文章專門介紹,這里不再細聊,敬請關注,
Label是類別庫QtQuick.Controls的預定義組件型別,描述的是一個文本標簽,x、y描述的是坐標,text描述的是顯示文本,qsTr()用于標識文本可被翻譯,類似Qt C++里的tr(),verticalAlignment描述的是垂直排列方式,這里的屬性值標識垂直中間排列,font.pointSize描述字體的大小,
TextField是類別庫QtQuick.Controls的預定義組件型別,描述的是一個單行文本編輯窗,width是幾何寬度,placeholderText描述的是占位符,
Button是類別庫QtQuick.Controls的預定義組件型別,描述的是一個可點擊的按鍵,height描述的是幾何高度,onClicked描述了當信號clicked發生時,該信號的處理句柄(handle),用{}限定處理范圍,這里呼叫了上面的檔案選擇窗的打開函式,
信號的處理句柄(handle)中,在on后邊書寫時信號的首字母需要大寫,
3)預覽界面
什么?代碼沒寫完就可以預覽界面了?
是的,QML檔案支持用工具預覽,非常方便于UI設計程序中的除錯,
打開預覽的方式是呼叫qmlscene或者用Qt Design Studio,如下圖用的是qmlscene,

看看Viewer.qml頁面預覽的實際效果,

4.使用C++代碼實作邏輯處理
Qt Quick使用QML的目的是為了簡化界面的設計開發,而軟體除了界面的互動之外還有大量的后臺邏輯處理功能也需要實作,針對這塊業務,Qt其實還是推薦使用C++,正所謂術業有專攻,畢竟C++對性能的利用還是有兩把刷子的,廢話不多說,馬上看下文,,,
既然如此,那么就來看看負責邏輯處理功能的C++代碼部分,不過,這里假設各位看官已經熟悉C++的各項業務技能,所以下面只針對和QML物件的互動來簡單介紹一下,
后續也會有更加詳細的專題文章介紹這部分,敬請留意哈!
1)QML物件的加載和C++物件傳遞
QML物件的創建和展現是通過QML引擎來加載的,一般每個程式會由單個引擎物件負責管理,不過,QML引擎物件不是直接管理QML物件,而是通過管理背景關系(context)物件來分別管理QML物件,所以在C++里邊如果需要往QML物件傳遞資訊也是直接傳給對應的背景關系物件,然后再在QML物件中通過傳入物件時指定的id名呼叫對應的方法屬性,
// main.cpp
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext>
#include "./FileMgr/FileMgr.h"
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
const QUrl url(u"qrc:/src/QML/main.qml"_qs);
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,
&app, [url](QObject *obj, const QUrl &objUrl) {
qInfo("%s start\n", QCoreApplication::applicationName().toLatin1().data());
if (!obj && url == objUrl)
QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
FileMgr fileMgr;
engine.rootContext()->setContextProperty(QStringLiteral("fileMgrInstance"), &fileMgr);
engine.load(url);
return app.exec();
}
上面的main.cpp檔案代碼中,把物件fileMgr傳入引擎的根背景關系中,并設定id為fileMgrInstance,傳入根背景關系,意味著引擎加載的所有QML物件都可以通過id=fileMgrInstance訪問fileMgr物件內容,這里要注意,fileMgr物件是在C++原始碼中實體化了的,
把C++物件暴露給QML物件的方法,除了上面這種通過背景關系的方式外,還有一種是通過直接往元物件系統(Meta-Object System)注冊型別的方式,這種方式也是最根本的方式,因為Qt Quick框架的底層實作原理就依賴于元物件系統,
使用的介面原型:
template <typename T> int qmlRegisterType(const char *uri, int versionMajor, int versionMinor, const char *qmlName)
如果通過這種注冊的方式實作,上面的栗子可以掰成這樣:
qmlRegisterType<FileMgr>("com.englyf.qmlcomponents", 1, 0, "FileMgrItem");
根據上面的定義,com.englyf.qmlcomponents是命名空間,1.0是命名空間的版本號,FileMgrItem是QML中的型別名,
然后在QML檔案內,匯入并實體化這個類:
import com.englyf.qmlcomponents 1.0
FileMgrItem {
// ...
}
要強調的是,通過注冊的方式,型別的實體化會放在QML里邊做,而C++原始碼就不需要再對類FileMgr作實體化了
2)C++型別的定義
既然Qt Quick依賴于元物件系統,那么對C++型別的定義就有必然的要求了,
C++型別需要繼承于QObject,并且類開頭應該宣告宏Q_OBJECT,這樣才可以使用元物件系統提供的服務,包括信號槽機制等等,
需要被QML物件呼叫的方法應該添加修飾Q_INVOKABLE,這個修飾符表明該方法可被元物件系統呼叫,同時,該方法的引數型別和回傳型別,都推薦使用型別QVariant,
如有開放給QML物件的可訪問屬性,那么也需要對屬性宣告為Q_PROPERTY,這里暫不舉例,可關注后續的專題文章,
// FileMgr.h
#ifndef FILEMGR_H
#define FILEMGR_H
#include <QObject>
#include <QVariant>
class FileMgr : public QObject
{
Q_OBJECT
public:
explicit FileMgr(QObject *parent = nullptr);
Q_INVOKABLE QVariant run(QVariant file);
};
#endif // FILEMGR_H
// FileMgr.cpp
#include "FileMgr.h"
FileMgr::FileMgr(QObject *parent)
: QObject{parent}
{}
QVariant FileMgr::run(QVariant file)
{
QString fileStr = file.toString();
qDebug("C++ get file:%s selected", fileStr.toStdString().data());
return 0;
}
自動化部署
這部分講點高級的內容,以往看到網上的教程都是教初學者部署的時候,進入exe生成的目錄,然后手動呼叫windeployqt執行部署,這個程式是Qt自帶的,會自動把所有依賴的動態庫拷貝過來存放在指定目錄下,
這里就介紹一下怎么在Qt Quick軟體工程編譯結束時自動部署所有依賴項,
首先,debug開發模式下是不需要部署軟體的,那么我們就先切換到release模式下,

然后,在Build的步驟下,Build步驟之后新添加一個Custom Process Step的步驟,

我把配置都拷過來:
Command: windeployqt
Arguments: --qmldir %{ActiveProject:NativePath}\src\QML\ %{ActiveProject:RunConfig:Executable:NativeFilePath}
Working directory: %{Qt:QT_INSTALL_BINS}
由于Qt Quick工程涉及到QML檔案,所以這里需要帶上選項--qmldir,這個選項后邊緊跟著引數值是代碼工程中存放自定義的QML檔案的根目錄,
%{ActiveProject:NativePath}代表著當前工程的主目錄的本地化路徑,
%{ActiveProject:RunConfig:Executable:NativeFilePath}代表著當前工程的exe檔案輸出目錄的本地化路徑,
Working directory項意思是Command命令的作業目錄,這里填上%{Qt:QT_INSTALL_BINS},代表Qt安裝目錄下的bin目錄,
按照上面的介紹程序配置完整,以后如果需要部署輸出,只需要切換到release模式下,然后點擊編譯,等編譯完成就會自動進入部署流程,整個程序就是這么舒心,
生活簡單才是美好,部署也不例外!
到最后,一起來看看跑起來的程式:

轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/509628.html
標籤:其他
