qml 可以很容易地通過 c++ 代碼中定義的功能進行擴充。由于 qml 引擎與 qt 元對象系統的緊密內建,qobject 派生類适當暴露的任何功能都可以從 qml 代碼通路,這使得 c++ 中的資料和函數可以直接從 qml 中通路,通常不需要太多修改,甚至不用修改。
通過元對象系統,qml 引擎具有内省 qobject 執行個體的能力。這意味着,任何 qml 代碼都可以通路 qobject 派生類 的以下成員:
屬性
函數
信号
一般來說,無論 qobject 派生類是否被注冊到 qml 類型系統,這些成員都可以從 qml 中通路。但是,如果 qml 引擎需要通路這個類的附加類型資訊(例如,如果類本身被用作一個函數參數或屬性,或者以這種方式使用它的一個枚舉類型),那麼該類可能需要被注冊。
<a href="#%e7%ae%80%e8%bf%b0">簡述</a>
<a href="#%e6%9a%b4%e9%9c%b2%e5%b1%9e%e6%80%a7">暴露屬性</a>
<a href="#%e5%9f%ba%e6%9c%ac%e7%b1%bb%e5%9e%8b%e5%b1%9e%e6%80%a7">基本類型屬性</a>
<a href="#%e5%af%b9%e8%b1%a1%e7%b1%bb%e5%9e%8b%e5%b1%9e%e6%80%a7">對象類型屬性</a>
<a href="#%e5%88%86%e7%bb%84%e5%b1%9e%e6%80%a7">分組屬性</a>
<a href="#%e5%af%b9%e8%b1%a1%e5%88%97%e8%a1%a8%e7%b1%bb%e5%9e%8b%e5%b1%9e%e6%80%a7">對象清單類型屬性</a>
<a href="#%e6%9a%b4%e9%9c%b2%e5%87%bd%e6%95%b0">暴露函數</a>
<a href="#%e6%9a%b4%e9%9c%b2%e4%bf%a1%e5%8f%b7">暴露信号</a>
任何的 qobject 派生類都可以使用 q_property() 宏來指定屬性。屬性是類的資料成員,具有關聯的 read 讀取函數以及可選的 write 寫入函數。
qobject 派生類的所有屬性都可以從 qml 通路。
例如,下面是包含 name 屬性的 person 類。正如 q_property 宏所指定那樣,該屬性可通過 name() 函數來讀取,setname() 來寫入:
為了通路執行個體中的資料,需要設定上下文屬性,将資料暴露給由 qml 引擎執行個體化的 qml 元件。
通過調用 qqmlcontext::setcontextproperty() 來定義和更新上下文屬性,允許以名稱将資料顯式地綁定到上下文。
然後,就可以從 qml 中通路了:
加載完成,name 的值就會被顯示出來,如下所示:
為了盡可能增強與 qml 的互動性,任何可寫的屬性都應該有一個相關的 notify 信号,隻要屬性值發生改變就發出該信号。這允許該屬性應用于屬性綁定,屬性綁定是 qml 的一個重要特性,每當其依賴的任何關系值發生改變,就會通過自動更新屬性來強制執行屬性之間的關系。
上述示例中,name 屬性相關的 notify 信号是 namechanged。這意味着無論何時發出該信号(就像在 person::setname() 改變 name 時一樣),将會通知 qml 引擎,必須更新涉及 name 屬性的任何綁定。反過來,引擎将通過調用 person::name() 來更新 text 屬性。
如果 name 屬性是可寫的,但沒有相關的 notify 信号,則 name 值将由 person::name() 傳回的初始值來初始化,但當該屬性後續發生任何更改時不會進行相應的更新。此外,綁定到該屬性的任何嘗試都會産生運作時警告。
信号命名建議: 将 notify 信号命名為 <code><property>changed</code> 的形式,其中 <code><property></code> 是屬性的名稱。由 qml 引擎生成的關聯的屬性更改信号處理程式将始終采用 <code>on<property>changed</code> 的形式,而無需關心相關 c++ 信号的名稱,是以建議信号名稱遵循此約定,以避免任何混淆。
使用 notify 信号的注意事項
為了防止循環或過度評估,應確定屬性更改信号僅在屬性值更改時發出。此外,如果一個屬性或屬性組不常被用到,則允許對若幹屬性使用相同的 notify 信号。不過,使用時應注意,確定性能不會受到影響。
notify 信号确實會有較小的開銷。有時,有些屬性的值僅在對象構造階段設定,随後就再也不會改變。最常見的情況是當一個類型使用分組屬性時,分組屬性對象被配置設定一次,并且隻有在銷毀對象時才會釋放。在這種情況下,屬性聲明時應該使用 constant 屬性,而不是 notify 信号。
constant 屬性應該僅用于那些僅在類構造函數中設值置,并且随後不再改變的屬性,而所有可能會在綁定中使用的其他屬性應該使用 notify 信号。
如果對象類型已經被注冊到 qml 類型系統,則可以從 qml 通路對象類型屬性。
例如,person 類型有一個 idcard * 類型的屬性:
任何 qobject 的派生類都可以被注冊為 qml 對象類型。一旦使用 qml 類型系統注冊,該類就可以像 qml 中的任何其他對象類型(例如:item)一樣被聲明和執行個體化。一旦被建立,類執行個體便可以從 qml 中操作,作為 c++ 類型的屬性暴露給 qml 解釋,任何 qobject 派生類的屬性、方法和信号都可以從 qml 代碼通路。
要将 qobject 派生類注冊為可執行個體化的 qml 對象類型,需要調用 qmlregistertype() 将類注冊為特定類型命名空間中的 qml 類型。然後用戶端可以導入該命名空間,以便使用該類型。
例如,将 c++ 類型 person 注冊為名為 person(雙引号中的名稱 - 盡量見名知義,對象類型首字母大寫,例如:per) 的 qml 類型,其在版本号為 1.0 的 people 命名空間中可用:
由于 idcard 是 person 的對象類型屬性,是以要通路 idcard 的附加資訊,也需要被注冊,完整的代碼如下:
一旦被注冊,通過導入指定的類型命名空間和版本号便可以在 qml 中使用該類型:
這裡,我們隻顯示身份證的有效日期,如下所示:
如果對象類型屬性是隻讀的,則可以在 qml 中作為分組屬性來通路,這種方式可用于暴露一個類型的一組相關屬性。
修改上述示例:
這時,就不能再這麼使用了:
因為 idcard 是一個隻讀的對象屬性,而非可寫的,是以會提示如下錯誤:
invalid property assignment: “idcard” is a read-only property
正确的姿勢是使用分組屬性文法,為 idcard 屬性指派:
或者:
對象類型屬性與分組屬性的差別:
分組屬性:隻讀,隻能在構造時由父對象初始化為一個有效值,生命周期由 c++ 父對象嚴格控制。其子屬性可以從 qml 中修改,但是分組屬性對象本身不能改變。
對象類型屬性:可以随時被配置設定新的對象值,通過 qml 代碼自由建立和銷毀。
如果屬性包含 qobject 派生類清單,也可以暴露給 qml。但是,屬性類型應該使用 qqmllistproperty,而非 <code>qlist<t></code>。這是因為 qlist 不是 qobject 的派生類型,是以不能通過 qt 元對象系統提供必要的 qml 屬性特性。例如,當清單被修改時的信号通知。
qqmllistproperty 是一個模闆類,可以很友善的由一個 qlist 值構造。
例如,company 類有一個類型為 qqmllistproperty 的 persons 屬性,存儲了一個 person 執行個體清單:
注冊同上:
在 qml 中,我們定義一個 listview 用于顯示清單中人的姓名:
效果如下:
注意: qqmllistproperty 的模闆類類型 - 在這種情況下,是 person - 必須向 qml 類型系統注冊。
qml 可以通路 qobject 派生類的函數,但是函數需要滿足以下條件之一:
使用 q_invokable() 宏标記的 public 函數
public 槽函數
現在,為人添加一些基本的行為。例如:eat、walk。。。吃飽了才有力氣減肥,每天三萬步,身體倍棒,吃嘛嘛香。
和前面一樣,要在 qml 中使用 person,需要将其設定為 main.qml 的上下文屬性:
然後,就可以在 qml 中使用這個執行個體通路這兩個函數:
點選滑鼠,标簽上顯示函數的傳回值,如下所示:
如果 c++ 函數的參數包含 qobject* 類型,參數值可以從 qml 中傳遞,使用一個對象 id 或引用該對象的一個 javascript var 值。
qml 支援重載的 c++ 函數調用,如果函數具有相同名稱和不同參數,則将根據提供的參數的數量和類型調用正确的函數。
當從 qml 中的 javascript 表達式通路 c++ 函數時,函數的傳回值将轉換為對應的 javascript 值。
qml 代碼可以通路 qobject 派生類的任何 public 信号。
qml 引擎會為 qobject 派生類的信号自動地建立一個名為 <code>on<signal></code> 的信号處理程式,其中 <code><signal></code> 是信号的名稱,首字母大寫。信号傳遞的所有參數在信号處理程式中都是可用的,通過參數名來通路。
前面,我們為人添加了走路的行為,其實三萬步也是蠻多了。走完之後,人會發出一個信号:我累了,需要休息,具體的休息時間由參數 minute 決定:
注冊就不再贅述了,和前面類似:
qml 中聲明的 person 對象可以使用名為 ontired 的信号處理程式來接收 tired() 信号,并使用 minute 參數值:
點選滑鼠,标簽上顯示信号的參數值,如下所示:
與屬性值和方法參數一樣,信号的參數的類型也必須能夠被 qml 引擎支援。值得注意的是,使用未注冊的類型不會生成錯誤,但是參數值不能從處理程式中通路。
注意: 如果類中包含多個名稱相同的信号,隻有最後一個信号可以被 qml 通路,因為具有相同名稱不同參數的信号無法被區分開來。