How to loop through unknown keys of a dict in qml code? - qt

I have a small problem with qml code on how to display a dicitonary. On the python side I just have to declare it as QVariantMap
from PySide2.QtCore import QObject, Signal
saved = Signal("QVariantMap")
I have managed to connect the signal well, and if i know the keys of the dictionary in advance i can display it well if I initialize it as property variant and then for example write saved["a"]
property variant saved: {"a": 0.0, "b": 0.0, "c": 0.0, "d": 0.0};
however my signal might come with less keys than the total. let's say it comes with keys "a" and "b". and I don't know in advance which keys (of the 4 total) will be in the signal. is there a way in qml to loop through its keys and then display their values??
The qt version I use is QtQuick 2.12
Thank you if you have a tip!

Related

How are QML property dependencies determined? (and how to manipulate them)

A property that is bound to an expression is updated when something in the expression changes.
This is called a dependency.
EDIT:
To clarify:
I'm interested in details on how Qt determines a list of dependencies
Dependencies on simple bindings such as x: y are more or less obvious
The question is about less obvious cases such as x: myItemId["y"] and x: myFunction(z) where myFunction(p) { if (p) return myItemId.y }
Sometimes QML engine is able to detect change even if the expression is a function call without arguments, other times it cannot do that (for example mapToItem(item,0,0).x).
Another example of imperfection is that setting JS array item value without reassigning the array itself doesn't normally produce onXxxxxChanged signal or update anything referring to that array value.
An expression with unused result (x: {myForcedDependency; return myActualCalculation()}) is sometimes suggested to force a dependency.
According to this KDAB article and Qt source code, a binding expression is not only evaluated but any properties "accessed" during that are "captured" in something called a "guard", then every guard properties onXxxxxChanged() signals are connected, but actual details of this process are unclear.
So my questions are:
Are there any defined rules of dependency resolution?
How does it really work?
How deeply does QQmlEngine/V8 scan "accesses" into functions called by the binding expression and what may prevent it from doing that?
Is dependency-detection only based on the first attempt at property resolution?
Are all possible code paths checked even if execution never reached there yet?
Are non-trivial accesses determined in those cases, such as object["property"] syntax?
What if some unexecuted code is (currently) erroneous (and does not produce an error but cannot be properly analyzed)?
How can the dependency resolution process be influenced?
Is there a way to avoid or block a dependency?
As far as I understand an intermediate "filter" property that only actually changes its value when it's necessary to update is the intended way, correct?
Is there an intended way to force a dependency?
Is manually emitting "XxxxxChanged" signal the correct/supported way to force an update?
Is adding an unused reference a legal/intended way to do it or undefined behavior based on the current implementation quirk?
Any information would be useful, although I did read the official documentation on QML properties, QML bindings and JavaScript expressions and didn't find any concrete explanation - if you refer to the official documentation please quote relevant parts.
Please note that I'm not asking you to test if any of this works on your system, but if it's supposed to work - if it can be relied on
It makes more sense if you just think of bindings as connected signals. If you have something like this:
property int x: y
It's just like doing this in C++:
connect(this, &SomeClass::yChanged, [this]() { x = y; });
The same goes for expressions:
property int x: y + z
would be equivalent to:
connect(this, &SomeClass::yChanged, [this]() { x = y + z; });
connect(this, &SomeClass::zChanged, [this]() { x = y + z; });
And the same with function calls:
property int x: someFunc()
function someFunc() {
return y;
}
The only time bindings don't update is when there is no onChanged signal to connect to, or the onChanged signal doesn't get emitted for whatever reason.
property int x: cppObject.invokable()
In the above case, the only property that x is able to connect to is cppObject. If invokable references other properties, those won't be connected to x and therefore the binding won't update.
property var array: [1, 2, 3]
property int x: array[0]
function updateArray() {
array = [2, 4, 6]
arrayChanged() // Manually call the onChanged signal to update `x`
}
var properties do not notify by default (for some reason). So in this case, we have to manually call the changed signal, but then the binding will still work.
For a property var, onChanged is emitted only when there is a direct assignment to the var itself, not to a property of some object it refers to. This also excludes modification of array contents, as JS arrays are JS objects.
This is consistent with QML being a JS extension. In JS you can modify prop in this code, because const only means variable will always refer to the same object:
const variable = { prop: 'value' };
Just like only direct assignments to const variables are regarded as change attempts when JS enforces const, QML only emits onChanged on direct assignments to a property var.
Coming from C++, I like to compare JS variables with object value to pointers:
SomeClass *variable = new SomeClass();
SomeClass *const variable = new SomeClass(); //const pointer to mutable object
Again, a change in the referred object is not regarded as a change in the variable.

QMap datatype in QML

I am using Qt 5.9.4 opensource. Is there any QML type similar to QMap?
Currently I am using a C++, which is holding a QMap. Using Q_INVOKABLE methods, retrieving information from QMap in the QML code.
Question:
Will this create any performance issue, as i m retrieving data from C++ in QML? Is there any way to have map datatype in QML itself?
You can simply use JavaScript objects - they are close to maps/dictionaries, for example:
var myMap = {x: "Test", y: "Test 2"};
myMap["z"] = "Test 3"
console.log(JSON.stringify(myMap))
outputs {"x":"Test","y":"Test 2","z":"Test 3"}.
Note: JSON.stringify is used just to make output more readable, it is not a part of my solution.
If you make your QMap a QProperty you will be able to access it directly from the qml. As far as any performance concerns this documentation covers that nicely in regards to QProperties. https://doc.qt.io/qt-5/qtquick-performance.html

How to get the typename of a QObject instance parsing QML?

I have got a QQuickView which has loaded a qml file like the following.
Rectangle { width: 100; height: 100 }
Then I am retrieving the root object via QObject *root = view->rootObject().
Now I want to get the class name from this object.
The following code results into "QQuickRectangle"
root->metaObject()->className()
But what I want is "Rectangle" just like the typename in the qml file.
Any idea?
Edit: I want to build a treeview with the object hirarchie of a qml file like QtCreator.
There is a pattern there, for qml types implemented in C++ the name would be QQuickSomething, for qml types implemented in qml the name would be Something_QMLTYPE_X_MAYBEMORESTUFF(objAddress).
So you can do some basic string editing depending on the result you get to isolate the actual type name:
QString name = QString(root->metaObject()->className());
if (name.contains("QQuick")) name.remove("QQuick");
else if (name.contains("QMLTYPE")) name.remove(QRegExp("_QMLTYPE_[0-9]*.*"));
// else it might be just a QObject or your on custom type you should handle
Edit: I want to build a treeview with the object hirarchie of a qml
file like QtCreator.
Unless you are willing to dig into and use private APIs, it would likely be easier and also more useful to have your own custom model to drive both a view and the actual object tree. Also, QML is quite easy to parse, I'd personally buckle down and write a parses faster than it would take me to get into the existing one, especially if all that is needed is an object tree outline, but YMMV.
There is "better" information kept on this (QQmlType & QQmlMetaType), but it is not accessible through any public API that I can think of.
Can you explain what you would like to do with it? Maybe there's an alternative.
QtQuick doesn't provide some special metadata for QML items. It looks that QtQuick uses item types internally only while parsing the source.
The known workaround is objectName:
Rectangle {
objectName: "Rectangle"
}
and so:
QString className = item->objectName();

Invalid signal parameter type: MouseEvent

If I try and use a MouseEvent as an arg in a QML defined signal, I get the following error on load:
Invalid signal parameter type: MouseEvent
There is conflicting information in the Qt docs regarding this, in the QML signal syntax documentation it states that:
The allowed parameter types are the same as those listed under Defining Property Attributes [...] any QML object type can be used as a property type.
Whilst in the QML/C++ interaction documentation it states that:
When a QML object type is used as a signal parameter, the parameter should use var as the type
Setting the argument to use var does work, but this seems unnecessary according to the QML documentation. There was a bug regarding this in the distant past but it was apparently resolved in v5.0.0. So I am doing something wrong, or is this a regression?
Edit
A simple demonstration:
import QtQuick 2.3
Item {
signal sig( MouseEvent mouse )
}
Use QtObject instead
signal sig(QtObject mouse)
Note: This works because QtObject is plain QObject which is base of all Qt objects

QDataWidgetMapper and multiple delegates

mapper = QtGui.QDataWidgetMapper()
mapper.setModel(my_table_model)
mapper.addMapping(widgetA, 0) #mapping widget to a column
mapper.addMapping(widgetB, 1) #mapping widget to a column
mapper.setItemDelegate(MyDelegateA(widgetA)) #Hmm. Where is the 'column' parameter?
mapper.setItemDelegate(MyDelegateB(widgetB)) #now itemDelegate is rewritten, MyDelegateB will be used
So... How do I set up mutiple delegates for a single QDataWidgetMapper? As far as I understand there is no QDataWidgetMapper.setItemDelegateForColumn() Or do I have to create some delegate factory, which will use appropriate delegates?
Thanks!
You have to use one single delegate and handle the way behavior of the different widgets in the setEditorData and setModelData functions of the delegate. For an example (C++ but straight forward) check this article from Qt Quarterly.
Ok, I got it. (At least, it works for me). So, the main idea is this class (a simplified version), which keeps a list of real delegate instances and routes data to\from them:
class DelegateProxy(QtGui.QStyledItemDelegate):
def __init__(self, delegates, parent=None):
QtGui.QStyledItemDelegate.__init__(self, parent)
self.delegates = delegates
def setEditorData(self, editor, index):
delegate = self.delegates[index.column()]
delegate.setEditorData(editor, index)
def setModelData(self, editor, model, index):
delegate = self.delegates[index.column()]
delegate.setModelData(editor, model, index)
Fully working example is in the pastebin
I found this problem too, and it really sucks. I'm right now trying to subclass QtGui.QDataWidgetMapper in order to workaround this, the subclass having its own addMapping() with a delegate argument, a dict to store the delegate for each widget, and a matching meta-delegate that calls the appropiate delegate for each case.
The weirdest thing about this is the problem also existed in earlier versions of Qt 4 in QAbstractItemView (i.e tables and trees) and later was fixed adding the setItemDelegateForColumn() method, but QDataWidgetMapper didn't get the fix.
An alternative could be using more than a mapper, and connect them to keep them in sync if necessary, but it is a bit messy, specially if you need lots of different special delegates:
mainMapper = QtGui.QDataWidgetMapper()
mainMapper.setModel(my_table_model)
auxMapper1 = QtGui.QDataWidgetMapper()
auxMapper1.setModel(my_table_model)
# If you move the index in the main mapper, the auxiliary will follow
mainMapper.currentIndexChanged.connect(auxMapper1.setCurrentIndex)
mainMapper.addMapping(widgetA, 0) #mapping widget to a column
auxMapper1.addMapping(widgetB, 1) #mapping widget to a column
mainMapper.setItemDelegate(MyDelegateA(widgetA))
auxMapper1.setItemDelegate(MyDelegateB(widgetB))

Resources