I have a strange problem, that is burried somewhere within a large project. So far I was not able to reproduce it in a MCVE, but as soon as I succeed, I will turn it in.
It is quite a simple missbehavior. Basically I have an QtObject with properties, that I set with initial values as such:
TestObj.qml
QtObject {
id: root
property int val1: { console.log('set val', root); return 42 }
Component.onCompleted: console.log('Constructed Object', this)
}
!!! With this example, I do not reproduce the error !!!
The output in my project would be now:
set val TestObj_QMLTYPE_44(0x33799fa8)
set val TestObj_QMLTYPE_44(0x33799fa8)
Constructed Object TestObj_QMLTYPE_44(0x33799fa8)
So, though the object is only created once, the initial property assignment is performed twice.
As I have no idea, where to look for the culprit, I can't produce a reproducable example, but maybe someone stumbled uppon the same situation already and found a solution.
A solution would be beneficial, as this issue results in multiple instantiations of some objects, that I can not destroy.
Created Bug-Report: maybe they find a way to solve this issue withouth hacky workarounds
The problem are circular references:
Circular references are resulting in a strange behavior when creating objects.
TestObj1.qml
import QtQuick 2.0
QtObject {
property Item paps
property int myVal: { console.log('myVal'); paps.val }
}
TestObj2.qml
import QtQuick 2.0
Item {
id: root
property int val: { console.log('set val', root); return 42 }
Component.onCompleted: console.log('Constructed Object')
TestObj1 {
id: to1
paps: root
}
}
Result:
qml: myVal
qml: set val TestObj_QMLTYPE_4(0x2c0bafb0)
qml: set val TestObj_QMLTYPE_4(0x2c0bafb0)
qml: Constructed Object
The probalbe cause for this is, that the statement {console.log('set val', root); return 42 } has not been processed, when it is allready assigned to myVal, therefore that statement is executed twice.
While this is no problem in normal situations, it might lead to problems, as long as we don't have dynamically created objects in those properties.
TestObj3.qml
import QtQuick 2.0
Item {
id: root
property QtObject obj: { console.log('set obj'); return objPrototype.createObject(root) }
Component.onCompleted: console.log('Constructed Object', obj)
TestObj4 {
id: to1
paps: root
}
Component {
id: objPrototype
QtObject {
id: op
Component.onCompleted: console.log('PropertyObject created', op)
}
}
}
TestObj4.qml
import QtQuick 2.0
QtObject {
property Item paps
property QtObject myObj: paps.obj
Component.onCompleted: console.log(myObj)
}
Result:
qml: set obj
qml: PropertyObject created QObject(0x2c124708)
qml: set obj
qml: PropertyObject created QObject(0x2c1246f8)
qml: Constructed Object QObject(0x2c1246f8)
qml: QObject(0x2c1246f8)
So you can see, the object is indeed created twice.
There are some workarounds:
Don't use circular references - but that would be booring.
Don't use dynamic object creation, as it always produces problems - I could just create the object, and either use property alias obj: myObjectID or property QtObject obj: myObjectID. This has the downside, that if the component is reusable, that the user can't replace that object or the object is still created, wasting memory.
Assign the property in Component.onCompleted if the property is still empty (not overwritten by the user of the reusable component). This has the downside, that the object is not available, uppon creation and a lot of Can't read property ... of null errors will appear. They will hopefully don't break the app though.
Create a nasty binding loop Write: property QtObject obj: (obj ? obj : objPrototype.createObject(root)). It will throw a warning, but c'mon. It's just a binding loop, that will be detected and broken.
Nothing nice in those workarounds, but maybe something usable.
Related
There is a property var1 declared in file1.qml. The property is visible in other qml files, but it is not possible to detect its event onChange.
file1.qml:
Item {
id: obj1Id
property int var1: 0
...
Component.onCompleted: {
var1=2
var1Changed() <<<<<<<<<<<< emit signal "changed"
}
onVar1Changed: {} <<<<<<<<<<<< signal is detected
...
}
file2.qml:
Item {
id: obj2Id
property int local_var: 0
...
Component.onCompleted: {
local_var = obj1Id.var1 <<<<<<<<<<<<<<<<< It is OK
}
onVar1Changed: {} <<<<<<<<<<<<<<<<< Error
obj1Id.onVar1Changed: {} <<<<<<<<<<<<<<<<< Error
...
}
Is it possible this approach or in file2.qml have to add property int local_var: obj1Id.var1 and then onLocal_varChanged: {}?
There are few moments in your code that could bring misunderstanding. Makes sense to figure out it first.
You wrote:
Component.onCompleted: {
var1=2
var1Changed() <<<<<<<<<<<< emit signal "changed"
}
but you do not need to manually emit that signal. QML will do it for you. So, actually nothing will change without that line.
You wrote
property int local_var: 0
...
Component.onCompleted: {
local_var = obj1Id.var1 <<<<<<<<<<<<<<<<< It is OK
}
this is totally valid code, but you need to understand that this is one-time assign and not a binding of a value obj1Id.var1 to local_var, e.g. if you change obj1Id.var1 -- do not expect local_var to be changed respectively.
To make a real binding of a value you may use Binding component or Qt.binding for creating property bindings from JavaScript or as you've proposed -- add property int local_var: obj1Id.var1.
In any case you need to have a valid reference to your obj1Id in your file2.qml.
If bindings approach doesn't fit you -- you can use Connections QML Type.
In your case this code should be placed inside file2.qml:
Connections {
target: obj1Id
onVar1Changed: console.log("obj1Id.var1 changed")
}
The document said we cannot have the same ID in one file. That means we can have the same id in different file,right? I don't know the scope of ID in QML,so i write the code as following to test it.
//a.qml
Item {
id: a_item
x:20;
y:b_item.x // cannot access this id
y:b1.x1 // can access
Item {
id:a1
x:20
Component.onCompleted : a1.x //this a1 is a.qml's a1 not the a1 in main.qml
}
}
//b.qml
Item {
id: b_item
x:20;
property int x1: 30;
}
//main.qml
Item {
a {
id:a1
Component.onCompleted : b1.x = 1 //can access
}
b {
id:b1
}
function() {
a_item.x = 1; // cannot access this id
}
}
My question:
1.
What is the scope of ID in QML? In my test, the result shows that Item cannot access the id of its childen and his brother 's chilren, but can access his parent or brother, right?
2.
the same id in different file just i show in my code, no error and i worked. But how can i differentiate them.
The canonical answer would be:
The scope of ids is the component scope.
And the component scope is:
Each QML component in a QML document defines a logical scope. Each
document has at least one root component, but can also have other
inline sub-components. The component scope is the union of the object
ids within the component and the component's root object's properties.
Which itself is not overly informative on what the scope exactly is and how can you make optimal use of it. A tad more informative:
In QML, component instances connect their component scopes together to
form a scope hierarchy. Component instances can directly access the
component scopes of their ancestors.
Basically, each id in a qml file is implemented sort of like a property of that source's root item. Except it cannot be accessed via someobj.someId, only via someId.
Which means that this id can be accessed by any object that exists in the branch that extends from the root object thanks to qml's dynamic scoping.
That is as long as it is not shadowed by an identically named id or property.
a_item will be visible in a.qml as well as any object that exists in the branch its root Item grows.
It won't be visible from main.qml as that object is further down the tree, where a_item is not defined.
In the same line of thought, b1 can be accessed from a.qml because b1 is defined in main.qml which is where a.qml is instantiated. But b_item will
not be visible from there.
In fact, since a1 and b1 are defined in main.qml which is the root of the entire application tree, those two ids will be visible from every object of the application, as long as it is a part of the object tree and as long as the identifiers are not shadowed. Note that they will not be visible from singletons or parent-less objects, as those are not part of the application object tree.
obj tree a1 b1 a_item b_item
main.qml D D X X
a.qm V V D X
Item a1 V V V X
b.qml V V X D
D - defined here, V - visible here, X - not available
The same is true for properties, although dynamic scoping only works for properties that are defined for the qml file root element, unlike ids which are visible even if they are on a different sub-branch, which is why in the first sentence of this answer I put it as "implemented sort of a property of that source's root item":
Obj
Obj
Obj
id: objid
property objprop
CustomObj
So objid will be visible in CustomObj, but objprop will not be, since it is not an id and not defined in the root object. The id is identical to doing this:
Obj
property objid : _objid
Obj
Obj
id: _objid
All ids from a given sources are visible in the qml source root object's context and subsequently everything else that will eventually drop down to this context as it lookup fails to resolve the identifier in the "higher" contexts.
Finally, keep in mind the subtle trap - it is only possible to use ids across sources if you know for certain that your application will instantiate the objects in a compatible context tree.
For example:
A.qml {
id: objA
B { } // objA will be visible to this object
}
main.qml
A {
B {} // objA will NOT be visible to this object
}
B {} // objA will NOT be visible to this object
The trap continues - context tree comes before object tree - the context in which an object is created matters, and cannot be changed once set (puts certain limits on reparenting depending on context dependencies).
// ObjA.qml
Item {
id: objA
Component {
id: cm
ObjB {}
}
function create() { cm.createObject(objA) }
}
// ObjB.qml
Item {
Component.onCompleted: console.log(objA)
}
// main.qml
Component {
id: cm
Rect {}
}
Obj {
anchors.fill: parent
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if (mouse.button === Qt.LeftButton) {
cm.createObject(parent)
} else {
parent.create()
}
}
}
}
As this practical example illustrates, even though in both cases the newly created object is parented to the same object that has the objA identifier, the object created in main.qml cannot resolve it, because it is created in a context where objA is still not defined, but it works if the object is created in the context of objA, and it will work even if it is burred even higher up the tree.
To put it in a more generic way, an id becomes visible in the context of the source's root object and remains visible in every subsequent sub-context until it is shadowed by an identically named object. Visibility cannot reach down the tree to contexts that exist before the context the id is defined in.
Note the subtle difference - a_item refers to an Item whereas a1 refers to an a. And since a1 is visible inside a.qml it will always refer to that one instance of a that is in main.qml, regardless of which instance of a you might be in, whereas a_item will refer to a different object for each different instance of a. a_item is "relative" and will be different in every different instance of a but a1 is absolute and will always refer to a specific instance of a. This is the case because a1 is a concrete instance whereas a_item is a type / prototype.
// Obj.qml
Item {
id: obj
Component.onCompleted: console.log(obj === oid)
}
// main.qml
Obj { } // false
Obj { id: oid } // true
Dynamic scoping of ids can be quite useful and cut the time it takes to implement a workaround to get access to the stuff you need. Which is also why it is a very good idea to give the id descriptive names rather than just main.
For example, if you have a manager that manages a number of views, each with a number of objects in them, you can quickly get access the respective view for each object and also get access to the manager without having to implement any additional stuff. The rule of thumb is that the manager must come first, then each view should be created in the context of the manager, not necessarily directly in it, but in it nonetheless, and each object should be created in the context of a view. And of course take care not to shadow over things. If you break that rule things will not resolve properly.
View.qml { id: view }
manager
view1
object // view is view1
view2
object // view is view2
view3
object // view is view3
Naturally, this makes sense only in specific purpose designs where you know what the general structure of the context tree is gonna be like. If you are making generic elements that may go just about anywhere, you should absolutely not be relying on accessing ids across sources, and you should implement a more generic usage interface via properties, aliases and whatnot.
The document said we cannot have the same ID in one file. That means we can have the same id in different file,right? I don't know the scope of ID in QML,so i write the code as following to test it.
//a.qml
Item {
id: a_item
x:20;
y:b_item.x // cannot access this id
y:b1.x1 // can access
Item {
id:a1
x:20
Component.onCompleted : a1.x //this a1 is a.qml's a1 not the a1 in main.qml
}
}
//b.qml
Item {
id: b_item
x:20;
property int x1: 30;
}
//main.qml
Item {
a {
id:a1
Component.onCompleted : b1.x = 1 //can access
}
b {
id:b1
}
function() {
a_item.x = 1; // cannot access this id
}
}
My question:
1.
What is the scope of ID in QML? In my test, the result shows that Item cannot access the id of its childen and his brother 's chilren, but can access his parent or brother, right?
2.
the same id in different file just i show in my code, no error and i worked. But how can i differentiate them.
The canonical answer would be:
The scope of ids is the component scope.
And the component scope is:
Each QML component in a QML document defines a logical scope. Each
document has at least one root component, but can also have other
inline sub-components. The component scope is the union of the object
ids within the component and the component's root object's properties.
Which itself is not overly informative on what the scope exactly is and how can you make optimal use of it. A tad more informative:
In QML, component instances connect their component scopes together to
form a scope hierarchy. Component instances can directly access the
component scopes of their ancestors.
Basically, each id in a qml file is implemented sort of like a property of that source's root item. Except it cannot be accessed via someobj.someId, only via someId.
Which means that this id can be accessed by any object that exists in the branch that extends from the root object thanks to qml's dynamic scoping.
That is as long as it is not shadowed by an identically named id or property.
a_item will be visible in a.qml as well as any object that exists in the branch its root Item grows.
It won't be visible from main.qml as that object is further down the tree, where a_item is not defined.
In the same line of thought, b1 can be accessed from a.qml because b1 is defined in main.qml which is where a.qml is instantiated. But b_item will
not be visible from there.
In fact, since a1 and b1 are defined in main.qml which is the root of the entire application tree, those two ids will be visible from every object of the application, as long as it is a part of the object tree and as long as the identifiers are not shadowed. Note that they will not be visible from singletons or parent-less objects, as those are not part of the application object tree.
obj tree a1 b1 a_item b_item
main.qml D D X X
a.qm V V D X
Item a1 V V V X
b.qml V V X D
D - defined here, V - visible here, X - not available
The same is true for properties, although dynamic scoping only works for properties that are defined for the qml file root element, unlike ids which are visible even if they are on a different sub-branch, which is why in the first sentence of this answer I put it as "implemented sort of a property of that source's root item":
Obj
Obj
Obj
id: objid
property objprop
CustomObj
So objid will be visible in CustomObj, but objprop will not be, since it is not an id and not defined in the root object. The id is identical to doing this:
Obj
property objid : _objid
Obj
Obj
id: _objid
All ids from a given sources are visible in the qml source root object's context and subsequently everything else that will eventually drop down to this context as it lookup fails to resolve the identifier in the "higher" contexts.
Finally, keep in mind the subtle trap - it is only possible to use ids across sources if you know for certain that your application will instantiate the objects in a compatible context tree.
For example:
A.qml {
id: objA
B { } // objA will be visible to this object
}
main.qml
A {
B {} // objA will NOT be visible to this object
}
B {} // objA will NOT be visible to this object
The trap continues - context tree comes before object tree - the context in which an object is created matters, and cannot be changed once set (puts certain limits on reparenting depending on context dependencies).
// ObjA.qml
Item {
id: objA
Component {
id: cm
ObjB {}
}
function create() { cm.createObject(objA) }
}
// ObjB.qml
Item {
Component.onCompleted: console.log(objA)
}
// main.qml
Component {
id: cm
Rect {}
}
Obj {
anchors.fill: parent
MouseArea {
anchors.fill: parent
acceptedButtons: Qt.LeftButton | Qt.RightButton
onClicked: {
if (mouse.button === Qt.LeftButton) {
cm.createObject(parent)
} else {
parent.create()
}
}
}
}
As this practical example illustrates, even though in both cases the newly created object is parented to the same object that has the objA identifier, the object created in main.qml cannot resolve it, because it is created in a context where objA is still not defined, but it works if the object is created in the context of objA, and it will work even if it is burred even higher up the tree.
To put it in a more generic way, an id becomes visible in the context of the source's root object and remains visible in every subsequent sub-context until it is shadowed by an identically named object. Visibility cannot reach down the tree to contexts that exist before the context the id is defined in.
Note the subtle difference - a_item refers to an Item whereas a1 refers to an a. And since a1 is visible inside a.qml it will always refer to that one instance of a that is in main.qml, regardless of which instance of a you might be in, whereas a_item will refer to a different object for each different instance of a. a_item is "relative" and will be different in every different instance of a but a1 is absolute and will always refer to a specific instance of a. This is the case because a1 is a concrete instance whereas a_item is a type / prototype.
// Obj.qml
Item {
id: obj
Component.onCompleted: console.log(obj === oid)
}
// main.qml
Obj { } // false
Obj { id: oid } // true
Dynamic scoping of ids can be quite useful and cut the time it takes to implement a workaround to get access to the stuff you need. Which is also why it is a very good idea to give the id descriptive names rather than just main.
For example, if you have a manager that manages a number of views, each with a number of objects in them, you can quickly get access the respective view for each object and also get access to the manager without having to implement any additional stuff. The rule of thumb is that the manager must come first, then each view should be created in the context of the manager, not necessarily directly in it, but in it nonetheless, and each object should be created in the context of a view. And of course take care not to shadow over things. If you break that rule things will not resolve properly.
View.qml { id: view }
manager
view1
object // view is view1
view2
object // view is view2
view3
object // view is view3
Naturally, this makes sense only in specific purpose designs where you know what the general structure of the context tree is gonna be like. If you are making generic elements that may go just about anywhere, you should absolutely not be relying on accessing ids across sources, and you should implement a more generic usage interface via properties, aliases and whatnot.
I am attempting to create something like jQuery's autocomplete as a widget in QML and Qt C++. Toward that end, I created a C++ AutoCompleteListener child of QObject and then register it with:
qmlRegisterType<AutoCompleteListener>(
"foo.AutoCompleteListener",0,1,"AutoCompleteListener");
Then, I instantiate the listener and the AutoCompleteForm like:
import QtQuick 2.5
import com.foo.AutoCompleteListener 0.1
Item {
AutoCompleteForm { id: autocomplete_form }
AutoCompleteListener { id: listener }
}
How can I pass a reference to the QML object AutoCompleteForm into AutoCompleteListener?
I tried passing the autocomplete_form field into:
Q_INVOKABLE void set_autocomplete_form(QQmlComponent *autocomplete_form);
on the onCompleted signal:
Item {
AutoCompleteForm {
id: autocomplete_form
Component.onCompleted: {
console.log("AutoCompleteForm completed");
listener.set_autocomplete_form(autocomplete_form);
}
}
AutoCompleteListener {
id: listener
Component.onCompleted: {
console.log("AutoCompleteListener completed");
}
}
}
However, the reference is a nullptr even though both AutoCompleteListener and AutoCompleteForm have been instantiated:
Instantiating AutoCompleteListener and parent is QObject(0x0)
qml: AutoCompleteListener completed
qml: AutoCompleteForm completed
Setting autocomplete_form = QObject(0x0)
How can I get a reference to the AutoCompleteForm or AutoCompleteListener's QML parent? I want to avoid crawling down the entire QML hierarchy with something like:
QObject* f = mView.rootObject();->findChild<QObject *>("AutoCompleteForm");
I plan to support having multiple AutoComplete widgets instantiated in parallel so a relative path (../AutoCompleteForm) to manipulate the QML objects seems better than having to crawl through the tree.
How can I pass a QML object reference into Qt C++?
You can't, the language was not designed for that. But you can get references from within C++ using findChild and findChildren. But read below for your real solution.
Also, I think your question is about an XY Problem. You have a problem X and you think Y solves it, so you ask for Y.
Correct solution for your original problem:
I am attempting to create something like jQuery's autocomplete as a
widget in QML and Qt C++.
In order to solve your real problem you need to use property bindings correctly. QML is a declarative language and self obsession with imperative programming makes it difficult to be straightforward.
Use this pattern for QML:
AutocompleteForm{
id: form
text: "Search here..."
suggestedTerms: helper.results
}
AutoCompleteHelper{
id: helper
searchFor: form.text
}
And for C++ implement
class AutoCompleteHelper : public QObject
{
Q_OBJECT
Q_PROPERTY(QString searchFor READ searchFor WRITE setSearchFor NOTIFY searchForChanged)
Q_PROPERTY(QStringList results READ results NOTIFY resultsChanged)
public:
AutoCompleteHelper() {}
virtual ~AutoCompleteHelper() {}
QString searchFor() const { return m_searchFor; }
QStringList results() const { return m_results; }
public slots:
void setSearchFor(QString searchFor)
{
if (m_searchFor == searchFor)
return;
m_searchFor = searchFor;
emit searchForChanged();
// 1. Search for it...
// 2. Some time later fill m_results
// 3. Then: emit resultsChanged()
}
signals:
void searchForChanged();
void resultsChanged();
private:
QString m_searchFor;
QStringList m_results;
};
Then you will see it magically works, because as soon as you change the form.text the binding sets the value in helper.searchFor, which then immediately fires the C++ slot where you can react even instantaneously, and in C++ the emission of resultsChanged() magically fills form.suggestedTerms.
You will find this pattern is extremely efficient so you'll even want to delay it by restarting a timer in C++. Furthermore, it is also beautifully declarative and clean.
How can I get a reference to the AutoCompleteForm or
AutoCompleteListener's QML parent?
The only alternative to rootObject()->findChild() appears to be QQmlProperty::read. There is several of read() function overloads so you can specify the context more precise. You have to provide the name for the object you would like to fetch and make it a property of some root object to start with. I like the general article on this subject of interfacing between QML and C++.
To accomplish precisely what you want or access the parent of certain known property you can try QQmlContext::parentContext together with QQmlProperty::read that accepts the context and see if the empty object name allows to resolve the object then.
You can pass it as a QVariant:
Q_INVOKABLE void QmlLink::pass_object(QVariant v)
{
YourObject* tempObject = (YourObject*) v.value<void *>();
...
}
qml:
my_bound_property.pass_object(the_object);
I have a PyQt5 property with proper notify-function:
class FriendsWidgetBackend(QObject):
messageCompanionChanged = pyqtSignal()
def __init__(self, parent=None):
QObject.__init__(self, parent)
self._current_message_companion = None
#pyqtProperty('QVariant', notify=messageCompanionChanged)
def message_companion(self):
return QVariant(self._current_message_companion)
#message_companion.setter
def message_companion(self, message_companion):
self._current_message_companion = message_companion
self.messageCompanionChanged.emit()
Everything works until I'll assign to this variable undefined - when it refuses to update variable in qml after emiting messageCompanionChanged signal. I've even tried to set a breakpoint - it updates from application start until undefined assignment:
Item {
id: rootItem
property QtObject friendsWidgetBackend
onVisibleChanged: {
if (!visible) {
friendsWidgetBackend.message_companion = undefined
}
}
}
I'd say after some experience of C# that QML disconnects binding after such assignment. How can I avoid this behavior?
P.S. My binding example:
Column {
visible: friendsWidgetBackend.message_companion ? true : false
}
P.P.S. It does not work even without binding - just tried to connect to a messageCompanionChanged signal from QML - it does not update value after emitting.
P.P.P.S. If i change undefined to empty js object like {} or ({}) binding will work but this is not what I need.