In Qt 5.3 I've been using the Loader QML element for loading screens, loading the view from a QML file in the background. Now I'm trying to load a string of QML dynamically. Qt.createQmlObject enables me to do so, but I can't seem to get the Loader element to play along.
Seems like Loader only takes a URL (QUrl) or component (QQmlComponent), but Qt.createQmlObject creates an object (QObject).
I'm new to QML, but from my understanding components are reusable elements, similar to classes, and objects are the instances of those classes. I thus can't seem to wrap my head around why Loader wouldn't work with objects.
How can I show a loading screen while (asynchronously) parsing and initializing a string of QML?
Example QML code:
Item {
Rectangle {id: content}
Loader {id: loader}
Component.onCompleted: {
var obj = Qt.createQmlObject('import QtQuick 2.3; Rectangle {}', content);
loader.source = obj; // Throws error.
}
}
It's not possible using the current API. As Loader's documentation says, it loads objects via a URL that points to a QML file or a Component:
import QtQuick 2.0
Item {
Rectangle {
id: content
anchors.fill: parent
color: "grey"
Loader {
id: loader
sourceComponent: myComponent
anchors.fill: parent
anchors.margins: 40
}
}
property Component myComponent: Qt.createComponent("MyComponent.qml", Component.Asynchronous)
}
MyComponent.qml:
import QtQuick 2.2
Rectangle {
color: "red"
}
Related
I am building a mapping program that eventually wants to display a different item on the map based on a value within the model. To facilitate this, I'm using Loader in my MapItemView delegate.
Unfortunately, when I tested this, the Loader method displayed nothing.
MapItemDelegate.qml
MapQuickItem {
id: waypoint
anchorPoint.x: image.width/2
anchorPoint.y: image.height/2
coordinate: task.waypoint //task extends QObject with members taskname and waypoint
sourceItem: Grid: {
columns: 1
rows:2
horizontalItemAlignment: Grid.AlignHCenter
Image {
id: image
source: "Waypoint.png"
height: 32
width: 32
}
Text {
id: text
text: task.taskname
}
}
}
main.qml
MapItemView {
id: taskview
model: tasklistmodel //Extends AbstractListModel to contain task objects
delegate: Component {
//Desired method, fails to display.
Loader {
source:"MapItemDelegate.qml"
// Will eventually be:
// source: task.typename+"MapItemDelegate.qml" to facilitate several types.
}
//Control method. Works fine, but does not meet my requirements.
MapItemDelegate {
//Nothing further needed.
}
}
}
How do I get MapItemView to actually display MapQuickItems loaded with Loader? Is there another method of dynamically loading delegates for display?
As it turns out, using a Loader within a delegate of MapItemView is not possible, as the API demands the Component contain a single mapping object. While Loader did load a mapping object, it is not in itself a mapping object, which caused the object to fail to display.
What I ended up doing is implementing a custom QSortFilterProxyModel that overrides filterAcceptsRow to filter tasklistmodel to match the task.typename property, then editing my MapItemView as follows:
MapItemView {
id:taskview
model: TaskListFilterModel { // custom extension of QSortFilterProxyModel
acceptType:"dothisthingtype"
sourceModel: tasklistmodel
}
delegate:MapItemDelegate {
}
}
With that, I can add a MapItemView for each type of task I plan to add in the future. While it's far less elegant and way more work than I had hoped to achieve with Loader, it does at least meet my requirements.
main.qml:
import QtQuick 2.11
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
ApplicationWindow {
id: window
x: 200
y: 200
visible: true
Component {
id: firstViewComponent
FirstView {
id: firstView
}
}
StackView {
id: stackView
anchors.fill: parent
Component.onCompleted: push(firstViewComponent)
}
Timer {
interval: 1000
running: true
onTriggered: stackView.pop()
}
}
FirstView.qml:
Rectangle {
id: view
StackView.onDeactivating: console.log('view: view is deactivating')
ListModel {
id: aModel
ListElement {
name: 'Element 0'
}
ListElement {
name: 'Element 1'
}
}
ListView {
id: listView
model: aModel
delegate: Rectangle {
id: listViewDelegate
Connections {
target: view.StackView // <---- DOESN'T WORK
onDeactivating: console.log('delegate ' + index + ': needs to do some housekeeping now')
}
}
}
}
I have a view that is instantiated by a StackView in main.qml. The StackView attaches a signal StackView.onDeactivating to the view. Is there any way to attach to the signal from an object other than the one the signal is attached to? I need to do some cleanup in listViewDelegate when the view is popped.
I could have view emit its own signal, and have the delegate respond to that signal. But what I'm wondering is if there is a way to connect to the attached signal: StackView.onDeactivating from a different object (listViewDelegate).
Yes and no. The Qt documentation partially addresses this: A Note About Accessing Attached Properties and Signal Handlers
It is not possible to directly access the property from a child. Attached properties need to be explicitly read by the class providing them. For your example, the parent class (StackView) simply searches for all attached properties it does provide in the child item (FirstView) as soon as it gets added, and handles all found properties/signals etc. by connecting them internally to whatever logic provides them.
However, nothing prevents you from gettings the attached property from the parent item, as long as you refer to it by id:
sampleProp: view.StackView.someProperty
The thing is: This indirect access only works this way for properties and nor for signals, as you can't simply refer to the attached object via view.StackView - so sadly, you are stuck with forwarding the signal to the child elements indirectly by creating a second signal in the root item and emitting it when the attached signal gets emitted.
I have a QML application where I'm creating lists of elements received from JavaScript. Using the details from this answer I'm populating the model as a JS array, and it works great. However, I'd like it so that when properties of the JavaScript objects change that ListView items driven from them update live.
Here's a simple test app showing the problem. The ListView is properly populated with MyRow instances showing the correct id/title, but when the rand property is changed by the timer, the List rows are unchanged (they show 0 for the last item).
MyRow.qml
import QtQuick 2.0
import QtQuick.Layouts 1.3
Rectangle {
property var obj
color:'#eeeeff'; height:20
RowLayout {
anchors.fill:parent
Text { text:obj.id }
Text { text:obj.title; Layout.fillWidth:true }
Text { text:obj.rand }
}
}
main.qml
import QtQuick 2.7
import QtQuick.Window 2.2
Window {
id:app; visible:true; width:200; height:100
property var database: ({"17":"World","42":"Hello"})
property var objById: ({})
function getObj(id){
if (!objById[id]) objById[id] = { id:id, title:database[id], rand:0 };
return objById[id];
}
ListView {
id:mylist
anchors.fill:parent
model: [42,17] // object ids
delegate: MyRow {
width:parent.width
obj:getObj(mylist.model[index])
}
}
Timer { // Update every object's rand value every second
interval:1000; running:true; repeat:true
onTriggered: {
Object.keys(objById).forEach(function(id){
objById[id].rand = Math.random()*100<<0;
})
}
}
}
How can I get the delegate's Text items to update their text when properties of the objects change?
The easiest (only?) way to get property bindings to work properly is to create real Qt objects to hook the values to. If you don't want to use a ListModel (because you want to quickly populate a model with items from a master library), then you can use createObject() to generate objects and pass them to your delegate.
Here's an updated main.qml that works as desired:
Window {
// ...same as above...
Component { // Creates real Qt objects with bindable properties
id:objFactory
QtObject {
property int id
property string title
property int rand:0
}
}
function getObj(id){
if (!objById[id])
objById[id] = objFactory.createObject( app, {id:id,title:database[id]} );
return objById[id];
}
// ...same as above...
}
Additionally, you may wish to change the property var obj in MyRow.qml to a more specific property QtObject obj (or a more specific object type, depending on what you pass in).
Finally, note that it's slightly cleaner/simpler to use modelData instead of mylist.model[index]:
ListView {
anchors.fill:parent
model: [42,17] // object ids
delegate: MyRow {
width:parent.width
obj:getObj(modelData)
}
}
I have an application which needs the screen to be switched between multiple available screens. I am checking if this is possible with loader in qml.
The issue i am facing is connecting signals from loaded item.
I use an application example in qt documentation and found CreateConnection in application qml cannot have if condition.
I also tried to make it signal slot connection in a function and call in on source change of loader, but that too did not work.
Application.qml
import QtQuick 2.0
Item {
width: 100; height: 100
Loader {
id: myLoader
source: "MyItem.qml"
}
Connections {
target: myLoader.item
// here i tried using if (myLoader.item == "qrc:MyItemOne.qml") , but can't use if
onChangeToSecond: {
myLoader.source = "MyItemTwo.qml"
}
onChangeToFirst: {
myLoader.source = "MyItemOne.qml"
}
}
}
MyItemOne.qml
import QtQuick 2.0
Rectangle {
id: myItem
signal changeToSecond()
width: 100; height: 100
MouseArea {
anchors.fill: parent
onClicked: myItem.changeToSecond()
}
}
MyItemTwo.qml
import QtQuick 2.0
Rectangle {
id: myItem
signal changeToFirst()
width: 100; height: 100
MouseArea {
anchors.fill: parent
onClicked: myItem.changeToFirst()
}
}
Someone knows any way to use loader for this, or loader should not be used for this?
Your code works fine if I use MyItemOne.qml as the initial value for the myLoader.source (Qt5.6.0). However, it will print out a warning:
QML Connections: Cannot assign to non-existent property "onChangeToFirst"
which happens because MyItemOne does not define the changeToFirst signal. The ignoreUnknownSignals property of Connections element can be used to suppress the warning, or both screens should define the same set of signals.
Loader can be used if it does not matter that the previous view is always fully unloaded when switching.
Ok like if I'm trying to make something like a menu for a simple game. How can I make it so that when the start button is clicked it loads a different QML file?
You can use Qt.createComponent() or Loader. For example:
import QtQuick 1.0
Item {
MyButton {
onClicked: loader.source = "MyGameFile.qml"
}
Loader {
id: loader
anchors.fill: parent
}
}