前言:
已經從事QT開發幾年了,但對于QML這個東西始終是沒有徹底掌握,一方面實際工作中沒有用到過,其次它的語法對我來說是全新的東西,不像QWidget那一套可以直接在C++中去寫。這就是為什么網上都說qml更簡單,我卻屢屢學不會的原因。
讓我下定決心一定要開始學QML的契機,就是因為最近在找工作,面試官讓我在紙上直接寫一個按鈕,并設置一段文字,結果我猶豫半天,居然沒能動筆。我大概知道是寫button,然后用花括號括起來,但button是否需要大寫,花括號后面有沒有分號,里面的屬性又是什么來著……我一下子陷進了沮喪。
這其實就是沒有真正使用qml寫過代碼的必然結果,即便對此有基本的認知,但別人考到你的時候,實際上是寫不出來的。所以接下來的學習當中,我必須要做好詳細的記錄和筆記,這也是對自己的要求。(偷偷抱怨一句,在這個時代只會qt,是真的難找到工作啊)
話不多說,
一、QML基本了解
QML是Qt框架的聲明式UI開發語言,與傳統的Qt Widgets(基于C++的界面庫)在語法特性、適用場景和視覺效果上存在顯著差異?。
?1.語法特性?: QML采用JSON-like聲明式語法,支持數據綁定和熱重載。? Qt
Widgets使用C++編寫,采用命令式編碼模式。?
2.視覺效果?: QML支持矢量渲染與復雜動畫,可自定義漸變色、圓角等效果。? Qt Widgets基于原生控件,動畫效果有限。?
3.開發場景?: QML適合移動端/嵌入式設備的動態UI(占比65%應用場景)。? Qt Widgets適合傳統桌面軟件(如Office類應用)。?
以上是網上找到的內容,結合我自身的認知,再淺淺談一下。
1.QML常常伴隨Qt Quick這個詞語,看上去好像是同一個東西,實則不然。QML是Quick Markup Language,它是一門語言。而Qt Quick是一個模塊,可以理解為Qt的一個擴展庫,你必須要先引用Qt Quick這個模塊,才能用QML語言來寫代碼。
2.為什么會出現QML這個東西,我猜是為了將界面描述和邏輯代碼徹底分開。如果你深入用QWidget來寫過代碼,你會有個感悟,界面層的代碼和業務功能代碼是深度耦合在一起的,特別是還有信號槽這種東西。而如果我們將界面相關的東西,全部用單獨的文件編寫起來,業務代碼繼續用C++寫,是不是就更清晰明了,分工明確呢。所以說,QML其實是專門做界面+加動畫+小邏輯的語言,為的是不搶C++業務。
3.說到QML的優勢,大家說的基本上可以使用GPU硬件加速和支持動畫效果,但這方面我目前還沒體會到,后面學習再加上。還有一點是,QML更方便應用在嵌入式小設備和移動端。至于“學習成本低”、“語法更簡單”,就見仁見智了。
二、QML工程創建
先說一下,我的QT版本是5.14.2,默認就可以創建Qt Quick工程,如果不行可能是QT安裝的時候沒有安裝對應的模塊,這個就得重新裝下QT了。
咱們先創建新的工程,選Qt Quick的應用工程,這里有好幾個選項,直接選空的即可。后面的步驟和QWidget是一樣的,就不啰嗦了。
創建完工程后,直接編譯運行,成功顯示出空白界面。(還很貼心給了個Hello World的提示)
三、工程結構
在看具體的qml代碼之前,我們先看一下基本的工程目錄結構。
如果是傳統的QWidget工程,代碼一般放在Headers(頭文件)和Sources(源文件)兩個目錄下標里面,但存放qml代碼的qml文件顯然不是。你可以直觀看到,qml文件默認是放在Resources底下的qrc里面的。而qrc一般存放的是資源文件,最常見的就是各種ui圖片。
也就是說,qml作為一種界面描述文件,本質上是當做一種資源來管理的(不知道說得對不對),他區別于其他正式的C++代碼文件,存放在不同的目錄底下。
當然,這里所說的目錄不是文件實際存放的目錄,說得上qt IDE里面顯示的分類目錄哈。
咱們打開工程的pro文件,發現它加載的是quick模塊。這里對比一下傳統QWidget應用,這里夾在的事core和gui模塊。
當然,這并不是二選一的問題,事實上qml是可以和widget混合使用的,比如從一個qwidget界面跳轉到qt quick界面,是完全可以的。我想表達的是,如果我們在qwidget工程中,想要新增一個qml工程,需要自己手動在pro中添加quick模塊,這樣qml才能正常使用哈。
四、C++中打開并顯示QML界面
我們直接把main中的代碼放上來。咋一看很復雜,沒事我們慢慢來。
#include <QGuiApplication>
#include <QQmlApplicationEngine>int main(int argc, char *argv[])
{QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);QGuiApplication app(argc, argv);QQmlApplicationEngine engine;const QUrl url(QStringLiteral("qrc:/main.qml"));QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,&app, [url](QObject *obj, const QUrl &objUrl) {if (!obj && url == objUrl)QCoreApplication::exit(-1);}, Qt::QueuedConnection);engine.load(url);return app.exec();
}
首先是頭文件,這里引用的QGuiApplication,它實際上對應的是QApplication,QGuiApplication比QApplication會更加輕量級,更適合純QML的工程。這里換成QApplication其實也是可以的,但pro要注意把core和gui模塊加上。
第二個頭文件叫QQmlApplicationEngine
QML 引擎,負責解析+加載+實例化你的 main.qml 文件。
我們看main的代碼,第一行是QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling);這是開啟高分辨率自適應,這和qwidget是一樣的,它默認給你添加了。
下一行是QGuiApplication app(argc, argv);這是創建了一個應用對象。
重點是下面的代碼。
QQmlApplicationEngine engine;
const QUrl url(QStringLiteral("qrc:/main.qml"));
engine = QML 解析器 + 加載器 + 對象工廠。
url = 要加載的 QML 文件(qrc 資源系統路徑)。
QObject::connect(&engine, &QQmlApplicationEngine::objectCreated,&app, [url](QObject *obj, const QUrl &objUrl) {if (!obj && url == objUrl)QCoreApplication::exit(-1);
}, Qt::QueuedConnection);
這句信號槽實際上是engine引擎加載url后的結果回調,返回參數是obj和obj的url,這里做了個保險絲判斷,如果 main.qml 加載失敗(文件不存在/語法錯誤),立即退出程序,防止黑屏。
engine.load(url);
return app.exec();
load = 解析 + 實例化整個 QML 對象樹。
exec() = 進入事件循環,直到 QCoreApplication::quit() 或窗口關閉。
最后再說一下我自己的理解。這種寫法更多是初始化一個qml引擎,然后加載url上的文件,把qml描述的界面顯示出來。實際上我更喜歡另一種方法,用到了qt中的QQuickWidget,它是一種組件,可以直接嵌QWidget中。直接調用QQuickWidget中的setSource似乎是一種更方便的做法,當然以我目前的水平,還說不好誰更好。這種方法我在下一篇中會詳細再說。
至此,一個qml界面便顯示出來咯。
五、QML最小可運行實例
我們雙擊main.qml,發現里面有這段代碼。
import QtQuick 2.14
import QtQuick.Window 2.14Window {visible: truewidth: 640height: 480title: qsTr("Hello World")
}
類似于C++代碼,其實還蠻好理解的。
-
import QtQuick 2.14
引入 QtQuick 核心庫(對象、動畫、繪制等基礎類)。 -
import QtQuick.Window 2.14
引入 Window 組件(ApplicationWindow 的簡化版),提供頂層窗口。 -
Window { … }
頂層窗口對象,等價于 QMainWindow 的 QML 版。 -
visible: true
立即顯示窗口(false 則隱藏)。 -
width: 640 / height: 480
窗口初始大小(像素)。 -
title: qsTr("Hello World")
窗口標題,qsTr() 是 Qt 的國際化函數,可被翻譯系統替換。
這 7 行是 QML 的最小可運行模板: 導入 QtQuick 和 Window,創建 640×480 的頂層窗口,標題‘Hello World’,立即顯示。
六、頂層窗口描述
我再補充一點,這里用到的Window事實上并不是固定的,我有見過b站up主用的Rectangle,于是用kimi問了一下:
- Window = 頂層窗口(有標題欄、系統邊框、任務欄圖標)。
- Rectangle = 普通矩形對象(無邊框,必須被別的窗口或組件裝載)。
1. 頂層 vs 被裝載
類 | 作用 | 是否能獨立顯示 |
---|---|---|
Window | 頂層窗口(系統邊框、標題欄) | ? 直接顯示 |
Rectangle | 矩形+顏色+子控件 | ? 必須被 Window/ApplicationWindow 裝載 |
2. 代碼對比
頂層窗口(獨立顯示):
import QtQuick 2.14
import QtQuick.Window 2.14Window { ← 頂層窗口visible: truewidth: 640; height: 480Rectangle { ← 被裝載的矩形width: 200; height: 200color: "red"}
}
僅 Rectangle(無邊框,需被裝載):
import QtQuick 2.14
Rectangle { ← 無邊框矩形width: 200; height: 200color: "red"
}
后者無法獨立顯示——必須被 Window/ApplicationWindow 或別的組件裝載。
3. 常用頂層類對照
QML 類 | 作用 | 備注 |
---|---|---|
Window | 最簡頂層窗口 | 無邊框菜單 |
ApplicationWindow | 帶菜單欄/工具欄的主窗口 | QtQuick.Controls 2 推薦 |
Rectangle | 普通矩形+子控件 | 被裝載對象 |
這就能解釋得通了,因為Rectangle的方式是加載在QQuickWidget控件里面的,不像main.qml中的Window必須是頂層窗口。(我嘗試直接將Window改成Rectangle,結果QQmlApplicationEngine加載返回的信號槽沒有出錯,但界面沒有彈出來,不符合我們的預期。)
這一點跟QMainWindow和QWidget的設計是類似的。只是QWidget不僅可以作為子控件,其實還可以用作頂層窗口,如果做了相關設置,甚至可以變成dialog彈窗,不過這個就不細說了。
七、總結
寫到這里,我已經盡可能說得很詳細,也努力讓自己印象深刻一點,畢竟這其實是自學筆記嘛。如果有人真的讀到這里,有什么問題也可以在評論區提出。
如果可以的話,接下來我會按照自己的節奏,按照自己的理解,一點點更新這個學習筆記,要是能成為一個教程就更棒了。