#(77):QML 語法
前面我們已經見識過 QML 文檔。一個 QML 文檔分為 import 和對象聲明兩部分。如果你要使用 Qt Quick,就需要 import QtQuick 2。QML 是一種聲明語言,用于描述程序界面。QML 將用戶界面分解成一塊塊小的元素,每一元素都由很多組件構成。QML 定義了用戶界面元素的外觀和行為;更復雜的邏輯則可以結合 JavaScript 腳本實現。這有點類似于 HTML 和 JavaScript 的關系,前者用來顯示界面,后者用來定義行為。我們這部分文章有些來自于?[QmlBook](http://qmlbook.org/),在此表示感謝!
QML 在最簡單的元素關系是層次關系。子元素處于相對于父元素的坐標系統中。也就是說,子元素的 x 和 y 的坐標值始終相對于父元素。這一點比起 Graphics View Framework 要簡單得多。

下面我們使用一個簡單的示例文檔來了解 QML 的語法:
~~~
// rectangle.qml
import QtQuick 2.0
// 根元素:Rectangle
Rectangle {
// 命名根元素
id: root // 聲明屬性:<name>: <value>
width: 120; height: 240
color: "#D8D8D8" // 顏色屬性
// 聲明一個嵌套元素(根元素的子元素)
Image {
id: rocket
x: (parent.width - width)/2; y: 40 // 使用 parent 引用父元素
source: 'assets/rocket.png'
}
// 根元素的另一個子元素
Text {
// 該元素未命名
y: rocket.y + rocket.height + 20 // 使用 id 引用元素
width: root.width // 使用 id 引用元素
horizontalAlignment: Text.AlignHCenter
text: 'Rocket'
}
}
~~~
第一個需要注意的是?import?語句。前面我們簡單介紹過,QML?文檔總要有?import?部分,用于指定該文檔所需要引入的模塊。通常這是一個模塊名和版本號,比如這里的`QtQuick 2.0`。當然,我們也可以引入自己的模塊或者其他文件,具體細節會在后面的章節中詳細介紹。
QML文檔的第二個部分是 QML 元素。一個 QML 文檔有且只有一個根元素,類似 XML 文檔的規定。QML 文檔中的元素同樣類似 XML 文檔,構成一棵樹。在我們的例子中,這個根元素就是`Rectangle`元素。QML 元素使用 {} 包圍起來。{} 之中是該元素的屬性;屬性以鍵值對`name : value`的形式給出。這十分類似與 JSON 語法。QML 元素可以有一個`id`屬性,作為該元素的名字。以后我們可以直接用這個名字指代該元素,相當于該元素的指針。需要注意的是,`id`屬性在整個 QML 文檔中必須是唯一的。QML 元素允許嵌套,一個 QML 元素可以沒有、可以有一個或多個子元素。子元素可以使用`parent`關鍵字訪問其父元素。正如上面的例子中顯示的那樣,我們可以用 id,也可以用`parent`關鍵字訪問其他元素。一個最佳實踐是,將根元素的 id 命名為 root。這樣我們就可以很方便地訪問到根元素。
QML?文檔的注釋使用`//`或者`/* */`。這同 C/C++ 或者 JavaScript 是一致的。
QML 元素的屬性就是鍵值對,這同 JSON 是一致的。屬性是一些預定義的類型,也可以有自己的初始值。比如下面的代碼:
~~~
Text {
// (1) 標識符
id: thisLabel
// (2) x、y 坐標
x: 24; y: 16
// (3) 綁定
height: 2 * width
// (4) 自定義屬性
property int times: 24
// (5) 屬性別名
property alias anotherTimes: times
// (6) 文本和值
text: "Greetings " + times
// (7) 字體屬性組
font.family: "Ubuntu"
font.pixelSize: 24
// (8) 附加屬性 KeyNavigation
KeyNavigation.tab: otherLabel
// (9) 屬性值改變的信號處理回調
onHeightChanged: console.log('height:', height)
// 接收鍵盤事件需要設置 focus
focus: true
// 根據 focus 值改變顏色
color: focus?"red":"black"
}
~~~
標識符 id 用于在 QML 文檔中引用這個元素。id 并不是一個字符串,而是一個特殊的標識符類型,這是 QML 語法的一部分。如前文所述,id 在文檔中必須是唯一的,并且一旦指定,不允許重新設置為另外的元素。因此,id 很像 C++ 的指針。和指針類似,id 也不能以數字開頭,具體規則同 C++ 指針的命名一致。id 看起來同其它屬性沒有什么區別,但是,我們不能使用`id`反查出具體的值。例如,`aElement.id`是不允許的。
元素 id 應該在 QML 文檔中是唯一的。實際上,QML 提供了一種動態作用域(dynamic-scoping)的機制,后加載的文檔會覆蓋掉前面加載的文檔的相同 id。這看起來能夠“更改” id 的指向,其意義是構成一個 id 的查詢鏈。如果當前文檔沒有找到這個 id,那么可以在之前加載的文檔中找到。這很像全局變量。不過,這種代碼很難維護,因為這種機制意味著你的代碼依賴于文檔的加載順序。不幸的是,我們沒有辦法關閉這種機制。因此,在選用 id 時,我們一定要注意唯一性這個要求,否則很有可能出現一些很難調試的問題。
屬性的值由其類型決定。如果一個屬性沒有給值,則會使用屬性的默認值。我們可以通過查看文檔找到屬性默認值究竟是什么。
屬性可以依賴于其它屬性,這種行為叫作綁定。綁定類似信號槽機制。當所依賴的屬性發生變化時,綁定到這個屬性的屬性會得到通知,并且自動更新自己的值。例如上面的`height: 2 * width`。`height`依賴于`width`屬性。當`width`改變時,`height`會自動發生變化,將自身的值更新為`width`新值的兩倍。`text`屬性也是一個綁定的例子。注意,`int`類型的屬性會自動轉換成字符串;并且在值變化時,綁定依然成立。
系統提供的屬性肯定是不夠的。所以 QML 允許我們自定義屬性。我們可以使用`property`關鍵字聲明一個自定義屬性,后面是屬性類型和屬性名,最后是屬性值。聲明自定義屬性的語法是`property <type> <name> : <value>`。如果沒有默認值,那么將給出系統類型的默認值。
我們也可以聲明一個默認屬性,例如:
~~~
// MyLabel.qml
import QtQuick 2.0
Text {
default property var defaultText
text: "Hello, " + defaultText.text
}
~~~
在 MyLabel 中,我們聲明了一個默認屬性`defaultText`。注意這個屬性的類型是`var`。這是一種通用類型,可以保存任何類型的屬性值。
默認屬性的含義在于,如果一個子元素在父元素中,但是沒有賦值給父元素的任何屬性,那么它就成為這個默認屬性。利用上面的`MyLabel`,我們可以有如下的代碼:
~~~
MyLabel {
Text { text: "world" }
}
~~~
MyLabel.qml 實際可以直接引入到另外的 QML 文檔,當做一個獨立的元素使用。所以,我們可以把 MyLabel 作為根元素。注意 MyLabel 的子元素 Text 沒有賦值給 MyLabel 的任何屬性,所以,它將自動成為默認屬性 defaultText 的值。因此,上面的代碼其實等價于:
~~~
MyLabel {
defaultText:Text { text: "world" }
}
~~~
如果仔細查看代碼你會發現,這種默認屬性的寫法很像嵌套元素。其實嵌套元素正是利用這種默認屬性實現的。所有可以嵌套元素的元素都有一個名為`data`的默認屬性。所以這些嵌套的子元素都是添加到了`data`屬性中。
屬性也可以有別名。我們使用`alias`關鍵字聲明屬性的別名:`property alias <name> : <reference>`。別名和引用類似,只是給一個屬性另外一個名字。C++ 教程里面經常說,“引用即別名”,這里就是“別名即引用”。這種技術對于導出屬性非常有用。例如,我們希望讓一個子元素的屬性外部可用,那么就可以給這個屬性一個別名,讓外部文檔通過這個別名訪問這個屬性。別名不需要特別聲明屬性類型,它使用被引用屬性的類型或者 id。需要注意的是,屬性別名在組件完全初始化之后才可用。因此,下面的代碼是非法的:
~~~
property alias myLabel: label
myLabel.text: "error" // 錯誤!此時組件還沒有初始化
property alias myLabelText: myLabel.text // 錯誤!不能為屬性別名的屬性創建別名
~~~
屬性也可以分組。分組可以讓屬性更具結構化。上面示例中的`font`屬性另外一種寫法是:
~~~
font { family: "Ubuntu"; pixelSize: 24 }
~~~
有些屬性可以附加到元素本身,其語法是`<Element>.<property>: <value>`。
每一個屬性都可以發出信號,因而都可以關聯信號處理函數。這個處理函數將在屬性值變化時調用。這種值變化的信號槽命名為 on + 屬性名 + Changed,其中屬性名要首字母大寫。例如上面的例子中,`height`屬性變化時對應的槽函數名字就是`onHeightChanged`。
QML 和 JavaScript 關系密切。我們將在后面的文章中詳細解釋,不過現在可以先看個簡單的例子:
~~~
Text {
id: label
x: 24; y: 24
// 自定義屬性,表示空格按下的次數
property int spacePresses: 0
text: "Space pressed: " + spacePresses + " times"
// (1) 文本變化的響應函數
onTextChanged: console.log("text changed to:", text)
// 接收鍵盤事件,需要設置 focus 屬性
focus: true
// (2) 調用 JavaScript 函數
Keys.onSpacePressed: {
increment()
}
// 按下 Esc 鍵清空文本
Keys.onEscapePressed: {
label.text = ''
}
// (3) 一個 JavaScript 函數
function increment() {
spacePresses = spacePresses + 1
}
}
~~~
Text 元素會發出`textChanged`信號。我們使用 on + 信號名,信號名首字母大寫的屬性表示一個槽函數。也就是說,當 Text 元素發出`textChanged`信號時,`onTextChanged`就會被調用。類似的,`onSpacePressed`屬性會在空格鍵按下時被調用。此時,我們調用了一個 JavaScript 函數。
QML 文檔中可以定義 JavaScript 函數,語法同普通 JavaScript 函數一樣。
QML 的綁定機制同 JavaScript 的賦值運算符有一定類似。它們都可以將右面的值賦值給前面。不同之處在于,綁定會在后面的值發生改變時,重新計算前面的值;但是賦值只是一次性的。
- (1)序
- (2)Qt 簡介
- (3)Hello, world!
- (4)信號槽
- (5)自定義信號槽
- (6)Qt 模塊簡介
- (7)MainWindow 簡介
- (8)添加動作
- (9)資源文件
- (10)對象模型
- (11)布局管理器
- (12)菜單欄、工具欄和狀態欄
- (13)對話框簡介
- (14)對話框數據傳遞
- (15)標準對話框 QMessageBox
- (16)深入 Qt5 信號槽新語法
- (17)文件對話框
- (18)事件
- (19)事件的接受與忽略
- (21)事件過濾器
- (22)事件總結
- (23)自定義事件
- (24)Qt 繪制系統簡介
- (25)畫刷和畫筆
- (26)反走樣
- (27)漸變
- (28)坐標系統
- (29)繪制設備
- (30)Graphics View Framework
- (31)貪吃蛇游戲(1)
- (32)貪吃蛇游戲(2)
- (33)貪吃蛇游戲(3)
- (34)貪吃蛇游戲(4)
- (35)文件
- (36)二進制文件讀寫
- (37)文本文件讀寫
- (38)存儲容器
- (39)遍歷容器
- (40)隱式數據共享
- (41)model/view 架構
- (42)QListWidget、QTreeWidget 和 QTableWidget
- (43)QStringListModel
- (44)QFileSystemModel
- (45)模型
- (46)視圖和委托
- (47)視圖選擇
- (48)QSortFilterProxyModel
- (49)自定義只讀模型
- (50)自定義可編輯模型
- (51)布爾表達式樹模型
- (52)使用拖放
- (53)自定義拖放數據
- (54)剪貼板
- (55)數據庫操作
- (56)使用模型操作數據庫
- (57)可視化顯示數據庫數據
- (58)編輯數據庫外鍵
- (59)使用流處理 XML
- (60)使用 DOM 處理 XML
- (61)使用 SAX 處理 XML
- (62)保存 XML
- (63)使用 QJson 處理 JSON
- (64)使用 QJsonDocument 處理 JSON
- (65)訪問網絡(1)
- (66)訪問網絡(2)
- (67)訪問網絡(3)
- (68)訪問網絡(4)
- (69)進程
- (70)進程間通信
- (71)線程簡介
- (72)線程和事件循環
- (73)Qt 線程相關類
- (74)線程和 QObject
- (75)線程總結
- (76)QML 和 QtQuick 2
- (77)QML 語法
- (78)QML 基本元素
- (79)QML 組件
- (80)定位器
- (81)元素布局
- (82)輸入元素
- (83)Qt Quick Controls
- (84)Repeater
- (85)動態視圖
- (86)視圖代理
- (87)模型-視圖高級技術
- (88)Canvas
- (89)Canvas(續)