動態字段無法雙向繫結 充分理解QML的屬性繫結

2021-10-16 01:38:53 字數 3566 閱讀 6679

大家新年好!這是豬年的第一篇。這裡我想給大家講講qt qml裡非常重要的乙個概念:屬性繫結(property binding)。現代化的開發語言、框架都講究自動化、智慧型化,在筆者看來,屬性繫結則是qml中這方面的代表。用好屬性繫結可以極大提高我們的開發效率。本文首先介紹何為qml屬性繫結,然後用通俗易懂的說法來闡釋其底層原理,最後和大家討論開發時幾個常見的問題。

在qml中實現屬性繫結有三種方法,每種方法都有其各自的優缺點。

這是最常見的繫結方法,在定義屬性時使用qml的冒號語法,所以筆者稱之為「冒號繫結」。

例如下面的qml**就實現了乙個屬性繫結:

textfield

button

上面第4行**將textfieldtext屬性繫結到了buttontext上。只要前者發生變化(例如使用者輸入、修改),按鈕上的文字就會跟著變動。

這種方法的優點是簡單方便,是三種方法中**量最少的。

缺點是缺乏彈性,控制能力小,主要有兩方面:

qml中專門提供了乙個型別binding來實現屬性繫結。上面的例子如果改用biding來寫則**如下:

textfield

button

binding

這種方法的優點主要有兩個:

這兩個可以說完美解決了上面第一種屬性繫結的問題。

該方法有兩個缺點:

所以只在需要的時候用該方法。

這是最後一種屬性繫結方法。上面的例子改用該方法的話**如下:

textfield

button

component.oncompleted:);

}

該方法的好處是可以寫在任何js執行**裡。

缺點是只能執行時繫結。由於前兩種方法都是qml語法申明,qml執行引擎在初始化的時候有機會使用jit技術和cache技術進行優化,而動態執行的js語句是沒法進行這種優化的,因此這種方法的執行效率是三種方法中最低的。

在實際開發中,筆者發現很多人會用屬性繫結,但經常出錯,發生意外的解綁、迴圈繫結等問題。究其原因,往往是因為對qml屬性繫結的底層原理不甚清楚,一旦程式變複雜很容易糊塗。

所謂屬性繫結的原理,用直白點的話來說,就是:為什麼乙個屬性變化了,和它繫結的屬效能跟著變化?

我們還是用上節中冒號繫結的例子。當我們寫下text: textfield.text這行**的時候,qml引擎實際上做了下面這些事情:

1.構造乙個槽函式。 該槽函式執行時會幹兩件事情:

計算冒號右邊的表示式的值。

將該值通過呼叫左邊屬性的write方法賦值。

假設buttontext屬性的write方法為settext,用c++的形式描述這個槽函式大概是下面這樣子:

public slots:

void textbindingslot()();");

settext(newtext);

}

2.掃瞄冒號右邊的表示式,找到所有具有notify訊號的屬性。 所謂notify訊號是指qt定義屬性時候的notify字段,具體可以參看文件qt property system。在這個例子中,textfield.text是乙個具有notify訊號的屬性,也就是說它被修改後發射notify訊號。這裡假設該訊號為textchanged()

3.將這些屬性的notify訊號和上面的槽函式連線。 使用connect連線:connect(textfield, signal(textchanged()), button, slot(textbindingslot));

所以當任何冒號左邊表示式裡的具有notify訊號屬性值改變時,相關訊號發射,然後構造的槽函式得到執行,計算出新的值,最後將該值賦給被繫結的屬性。

上述過程對於第二和第三種繫結方法也是大體一樣的。

根據上述過程描述,我們也得出另乙個結論:qml的屬性繫結是單向的。所謂單向,是指textfield.text的改變會引起button.text的改變;但反過來則不會,因為並沒有經過上述的構造過程。

下面就筆者在實際開發中遇到的幾個問題展開討論。

根據上節闡述的繫結原理,導致繫結不起作用的原因可能有兩個:

仔細檢視屬性定義,是否新增了notify字段?所有屬性繫結執行、推演的原動力都是這個notify訊號,如果你的自定義屬性沒有定義該訊號,那屬性繫結很多不會起作用。

如果你的屬性值是在c++中被修改,那是否發**notify指定的訊號?有很多人定義了notify訊號,但是在修改屬性值的時候,卻忘記發射這個訊號。這個是非常常見的錯誤。

注意:屬性繫結的原動力是那個notify訊號。其實不管屬性值是否真的改變,只要你發**該訊號,屬性繫結都會被重新計算一次(只不過如果值確實沒變,每次計算結果也不變)。

這也是很常見的錯誤,它往往是在js**中直接對屬性賦值導致的,例如下面的**:

textfield

button

function func()

正如前面冒號繫結裡說的,buttontext屬性已經成功繫結到了textfieldtext屬性上。但是一旦func函式執行,buttontext屬性被直接賦值,實際上就和之前構造出來的槽函式(上節中的textbindingslot)解了綁。

如果直接賦值不可避免,又想在賦值之後重新繫結,那麼可以用第三種繫結方法進行再繫結。或者一開始就用binding來繫結,用它的when屬性來控制繫結是否起作用。

前面我們提到了qml的屬性繫結是單向的,但如果我們確實需要做雙向繫結該怎麼辦?

對於都是qt quick自帶型別,可以很簡單,例如:

textfield

button

也就是說,上面各自做一次屬性繫結即可。

但如果是自定義屬性,要特別注意write部分要檢查屬性值是否真的被修改;只有真的被修改才往外發射notify訊號,否則很可能進入死迴圈。例如下面的write函式:

void settext(qstring newtext)

VUE關於物件動態新增屬性無法雙向繫結問題

在專案中遇到的問題,因為物件屬性不固定,需要到資料庫中讀取,然後動態的給物件新增屬性,在新增屬性的過程中發現新增的屬性在雙向繫結時不能生效,房頂方法有三種,如下圖 上邊有三種給物件新增屬性並賦值的方法,只有第一種可以實現值的雙向繫結,但是跟需求不符合,需要新增的屬性不是固定的,後來採用的方法是建立乙...

1120 MVVM框架是如何實現雙向資料繫結剖析。

思路 1.model影響檢視 編譯時註冊watcher,在註冊watcher,呼叫get,通過observer資料劫持get方法,將多個觀察者統一管理起來。當改變資料時,呼叫set方法,將收攏的對應觀察者的upadte方法更新。2.檢視影響model 編譯時註冊wather,node節點繫結對應的事...

Vue3 0為什麼使用Proxy實現雙向繫結

object.defineproperty只能劫持物件的屬性,而proxy是直接 物件 由於object.defineproperty只能對屬性進行劫持,需要遍歷物件的每個屬性。而proxy可以直接 物件。object.defineproperty對新增屬性需要手動進行observe,由於objec...