2017-01-22 25 views
0

現在在我的C++/QML應用,即使每個QML視圖只使用所有可用屬性的子集,所有的人都通過QDeclarativeContext::setContextPropertyQDeclarativeViewQDeclarativeEngine顯示的根上下文暴露QML文件 - 這很醜陋。使用QDeclarativeContext層次與QDeclarativeView

按照Qt documentation

Additional data that should only be available to a subset of component instances should be added to sub-contexts parented to the root context.

所以我想這樣做。但是,我還沒有找到進一步的文檔或信息如何實際使用子上下文。不幸的是,僅僅創建一個新的子上下文是不夠的。

實施例:

我有一個很簡單的QML文件

import QtQuick 1.0 
Rectangle { 
    visible: true 

    width: 800 
    height: 600 

    Text { 
     text: message.text 
     anchors.centerIn: parent 
    } 
} 

訪問稱爲message上下文屬性,該屬性被設爲一個QDeclarativeView的發動機的根上下文的子上下文內在main

int main(int argc, char *argv[]) 
{ 
    QApplication app(argc, argv); 

    QWidget widget{nullptr, Qt::FramelessWindowHint}; 
    widget.resize(800, 600); 

    MessageHolder message{"Hello World"}; 

    // ceeate the new context and add the message to context properties 
    QDeclarativeContext message_context{view.engine()}; 
    message_context.setContextProperty("message", &message); 

    // what needs to go here so that the new context is actually used? 

    view.setSource(QUrl{"path/to/main.qml"}); 
    view.show(); 
    widget.show(); 

    return app.exec(); 
} 

上面使用的MessageHolder是一個簡單的類,它只是提供交流它被設置在建設這樣一個對象的onstant的QString:

class MessageHolder : public QObject { 
    Q_OBJECT 
    Q_PROPERTY(QString text READ text CONSTANT) 
public: 
    MessageHolder(QString text); 

    QString text(); 
private: 
    QString _text; 
}; 

正如上面已經指出,這種遺憾的是不工作,QML發出警告

/path/to/main.qml:9: ReferenceError: Can't find variable: message`

如果我不是message屬性添加到根上下文一切工作正常。在創建環境和設置視圖的來源以使其工作之間,我需要做些什麼?


更深入的我真正想要做

在我的應用程序使用MVVM架構,其中每個QML視圖都有一個對應的C++視圖模型(在MVVM,而不是意識的解釋Qt術語)。

在任何給定時刻只有激活很多不同的ViewModels的一個,但是所有這些應用程序的啓動過程中實例化,然後通過上下文屬性暴露給QML。

最核心的問題我有,這是爲每個的ViewModels的唯一名稱的需要,從而也強烈耦合QML意見單一視圖模型。改爲使用名稱爲viewmodel的單個通用上下文屬性會更好。

但是,這裏是問題:當一個新的視圖模型變爲活動狀態時,我必須在更改QML視圖之前或之後將viewmodel context屬性重新設置爲新的viewmodel。但更改上下文屬性會導致重新評估當前活動的所有綁定。因此,當我第一次更改上下文屬性,然後認爲,舊視圖會嘗試更新,即使新的視圖模型不具備所需的性能,如果我第一次更改視圖,然後選擇上下文屬性的新視圖將在加載時評估其所有綁定,但由於上下文屬性尚未更新,所以必要的屬性尚不可用。

視覺上這不是一個問題,因爲在第一種情況下打破現在的觀點是要被卸載反正在第二種情況下,一旦新的上下文屬性設置新的視圖會立即重新評估所有屬性。儘管如此,在任何一種情況下,QML都會因爲缺少屬性而發出警告,這首先不好,因爲它混亂了日誌文件;其次,因爲我通過Q_ASSERT將警告視爲調試版本中的致命錯誤,以便發現QML視圖中的錯誤或視覺模型在我的持續集成系統的早期沒有人工檢查。

但後來我在Qt documentation遇到下列事實跌跌撞撞:

While QML objects instantiated in a context are not strictly owned by that context, their bindings are. If a context is destroyed, the property bindings of outstanding QML objects will stop evaluating.

這正是我需要的,以便從垂死的觀點重新評估現在是無效的綁定停止QML。因此,我想在子上下文中設置viewmodel上下文屬性,我將在視圖更改時銷燬並重新創建。

回答

1

嗯,不要將屬性設置爲根(對象)上下文,而是可以將其添加到不是根對象的特定對象的上下文中。

您可以使用此靜態方法獲取任何對象的情況下:如果你創建了一些任意上下文你在你的代碼做

auto ctx = QQmlEngine::contextForObject(someQObjectPtr); 

每個QML對象都有其背景,顯然,這不符合任何事情,可以理解,它不會按預期工作。

雖然你想要做的事情聽起來有點落後。

我會做的是有另一個模型或所有模型的基本列表,然後有一個property model activeModel並將其設置爲我想要顯示的任何模型,並使用activeModel作爲視圖,從而更改主動模型會自動更新視圖。如果你想更加明確,你可以在改變模型之前手動銷燬視圖。

此外,如this recent question所示,對象破壞本身不會立即在QML中發生,它會延遲,並且仍然會導致重新評估被認爲不再相關的對象的綁定。所以你原來的計劃甚至可能不起作用,除了成爲你不應該擺在首位的設計問題的不必要補丁。總而言之,我不會推薦使用上下文屬性來完成比向QML公開單個對象更動態的任何內容。當你有很多東西需要自己的代碼行和自己的名字時,它只是不可擴展和可維護的。

UPDATE:

Additional data that should only be available to a subset of component instances should be added to sub-contexts parented to the root context.

您錯誤地解釋這一點。它不會告訴你去手動創建自己的上下文。我不熟悉任何means to manually specify which context you want from QML,上下文是隱含的 - 您所處的上下文 - 當前對象的上下文,或者如果無法找到該屬性,則將上下文一直搜索到根上下文。這並不意味着你應該無條件地創建上下文樹,然後刪除它們,在qml引擎下拉下地毯。

因此,正如我最初的答案所述,如果你不想在根上下文中註冊屬性,你應該選擇一個分支的上下文。然後,該屬性將僅對該子樹中的對象可見。但是這不會真正解決您遇到的問題,至少不是以合理,高效和優雅的方式解決問題。

這個解決方案冒着重複自己的風險,顯得非常簡單明顯 - 摺疊視圖,更改模型,重建視圖。沒有必要嘗試和做你不知道的不可能的事情。銷燬上下文的唯一方法是銷燬其對象,當視圖被銷燬時,將不會有對象使用無效數據更新綁定。粗略地從C++中刪除QML對象不會以任何方式幫助您避免QML的設計限制,它只會導致應用程序崩潰。

這個簡單的例子圍繞generic object model構建,清楚地說明了這些「剩餘」綁定僅僅是QML內部設計的產物,在我個人的經驗中,這些設計有缺陷,缺陷和設計限制。我本人已經有了我的(相當不公平的)鬥爭份額並且努力了,但是如果經驗教會了我什麼,那就是去嘗試解決它,因爲有時QML的工作方式與邏輯和理性無關,所以你的期望是多麼合理和合乎邏輯(如果在這個特殊情況下)是完全不相關的。

List { 
    id: models 
    List { 
     property Component delegate : Rectangle { 
     width: 200 
     height: 50 
     color: object.apple 
     } 
     QtObject { property color apple: "green" } 
     QtObject { property color apple: "red" } 
     QtObject { property color apple: "yellow"} 
    } 
    List { 
     property Component delegate : Rectangle { 
     width: 200 
     height: 50 
     color: "red" 
     Text { 
      anchors.centerIn: parent 
      text: object.pepper 
     } 
     } 
     QtObject { property string pepper: "sweet" } 
     QtObject { property string pepper: "spycy" } 
     QtObject { property string pepper: "hot" } 
    } 
    } 

    Row { 
    ListView { 
     width: 200 
     height: 200 
     model: models 
     delegate: Rectangle { 
     width: 200 
     height: 50 
     color: "grey" 
     Text { 
      anchors.centerIn: parent 
      text: index 
     } 
     MouseArea { 
      anchors.fill: parent 
      onClicked: { 
      currentModel.model = null // remove line and suffer 
      currentModel.model = object 
      } 
     } 
     } 
    } 
    ListView { 
     id: currentModel 
     width: 200 
     height: 200 
     model: null 
     delegate: model ? model.delegate : null 
    } 
    } 

該實現是列表或模型的模型,如果您願意的話。模型列表中的每個模型都帶有自己的委託,以及具有自己的一組屬性的對象。有兩個列表視圖,第一個列出所有可用模型,第二個列出活動模型的內容(如果有的話)。如果沒有currentModel.model = null行,每次切換當前模型時,都會收到有關未定義屬性的控制檯錯誤,因爲這正是QML的工作原理。實際上,在這種情況下,解決方案非常幸運,在切換到另一個模型之前,模型就像模型一樣簡單,這條線一切都很好,視圖元素在模型切換之前就被銷燬了,沒有它,項目依然存在一個事件循環週期足以產生錯誤,並且這些週期不是來自當前列表視圖項目,而是來自那些正在消失的事件,因此即使你使用C++中的數據,那些對象由列表視圖管理,會徘徊在足以造成麻煩,並在delete的情況下,它不會是控制檯錯誤,但一個應用程序崩潰。

+0

我相信我缺乏QDeclarativeView,QDeclarativeEngine和QDeclarativeContext一起工作的背景,因此無法追隨你的培養目標。你可能可以修改我的簡單例子來使用'contextForObject',使message屬性正確地暴露給QML文件嗎? – Corristo

+0

關於你的最後一段:不必爲每個視圖模型設置獨特的名稱和設置上下文屬性,這正是我**不想再做的事情。負責視圖排序的Controller類已經知道下一個要加載哪個QML以及哪個視圖模型對應於它,所以重新設置一個通用的'viewmodel'上下文屬性對我來說似乎是合理的。 – Corristo

+0

關於倒數第二段:由QML擁有的對象的對象銷燬可能不是立即的,但有一個'std :: unique_ptr '將子上下文作爲C++類中的成員保存。 – Corristo