QML pass QtObject from one qml to another - qt

I've been trying to wrap my head around this one for a while and came up with a few hacks, but none of them seem to be the Right Way. Hopefully this makes sense. Say I have three qml files.
First QML:
...
ListView {
id: patientList
model: patientModel
delegate: E3DPatientListItem {
onClicked: {
if (patientList.currentIndex !== index)
{
patientList.currentIndex = index
detailStackView.push("DetailPanel.qml",
{ "view": view, "ptInfo": model })
...
DetailPanel:
...
Page {
property QtObject ptInfo
Timeline {
ptInfo: ptInfo // <- how do I pass this to Timeline?
...
Timeline.qml
...
Item {
property QtObject ptInfo // <- always null :(
...

...
Page {
property QtObject ptInfo
Timeline {
ptInfo: ptInfo // <- how do I pass this to Timeline?
...
What do you suppose ptInfo: ptInfo is achieving? You are binding the property to its own identifier.
Maybe try not using the same identifier to avoid shadowing, or give the Page and id and then ptInfo: pageid.ptInfo. parent.ptInfo will also work as long as the object has the parent property exposed, keep in mind that QtObject does not.
However, you don't really need to have property QtObject ptInfo in Timeline.qml, as long as a timeline is always instantiated inside a page, you can access ptInfo directly from inside the timeline because of dynamic scoping. Note that dynamic scope properties will only work for properties that are defined in the root element of the particular qml file:
// Obj1.qml
Item {
property int test: 7
Obj2{}
}
// Obj2.qml
Item {
Component.onCompleted: console.log(test) // it will work
}

Related

Is it possible to bind QML ListModel to PathPolyLine .path property?

In my previous question I figured out that QML PathPolyLine.path property can be filled with JS array of objects. But changing that array doesn't lead to PathPolyLine.path property change, thus I have to reassign it every time.
So can this property be bind to QML ListModel? Like QML Repeater.model property. And how to set it properly?
Something like that:
ListModel { id: modelID }
PathPolyLine
{
id: pplID
path: modelID
}
Button
{
property int i: 0
onCicked(): { modelID.append(Qt.point(i, i+10)); i++; }
}
That way button clicks only fill ListModel and doesn't affect PathPolyLine.path property.

Re-using Component of TextField responding to wheel events, with signal handling

I enriched a TextField (displaying a float) so that it can be changed by mouse wheel, all while being still editable by hand.
I found the quirk with forceActiveFocus and onClicked here (I wish I could just let all events pass down the widget stack like in Qt) and use onWheel to change the value (please let me know if this is not the best way to do this):
TextField{
text: cxxObject.floatAttribute.toString()
onEditingFinished: { cxxObject.floatAttribute=parseFloat(text); }
MouseArea{
anchors.fill: parent
propagateComposedEvent: true
onClicked: { parent.forceActiveFocus(); }
onWheel: { parent.text=parseFloat(parent.text)-.5*wheel.angleDelta.y/120;
parent.editingFinished();
}
}
I would like to re-use this component instead of TextField in several places (without copy&paste), so I tried to declare the component like this:
Component{
id: wheeledFloatTextField
property real initValue: 0.
property real dWheel: 0.5
signal editingFinished(real value);
TextField{
text: parent.initValue.toString();
// re-emit signal to the component
// so that user-defined slot can be defined when re-used
onEditingFinished: parent.editingFinished(parseFloat(text));
// validator: ...
MouseArea{
anchors.fill: parent
propagateComposedEvents: true
onClicked: { parent.forceActiveFocus(); }
onWheel: {
parent.text=parseFloat(parent.text)-parent.parent.dWheel*wheel.angleDelta.y/120;
parent.editingFinished();
}
}
}
}
and re-use:
Loader{
sourceComponent: wheeledFloatTextField
initValue: cxxObject.floatAttribute;
onEditingFinished: { cxxObject.floatAttribute=value; }
}
I am however getting (at the line where Component is used):
Component objects cannot declare new properties.
What is wrong? I was some posts (like How do you assign a QML Item to a component property in QML and then use that object inside the component? and https://developer.blackberry.com/native/documentation/dev/custom_components/index.html) from which I am gathering I might need to wrap the inside of Component (which is as-if it were a separate .qml file and does not define a scope) in something like Item or Container but I am not sure what to do. Any hint?
I would like to keep the definition inline first, later move to a separate file.
If you have the component declared in a separate file, you can (should) omit the top-level Component. For maximum reusability of components, it is reccomended to declare them in a separate file.
A Component can not have any properties declared. It is basically stopping the object creation in a prototypical state. That is useful if you want to configure the object, for later creation, for example lazy initialization (delegates).
If you have a property of type Component and you use the myProp: SomeType {...} syntax, it will automatically just create a component from that.
I think the best solution is to put your TextField in a seperate file, and add the properties to the root-node so it is customizable.
File1 (e.g. "CustomTextField.qml")
TextField{
property real initValue: 0.
property real dWheel: 0.5
signal editingFinished(real value);
text: initValue.toString();
// re-emit signal to the component
// so that user-defined slot can be defined when re-used
onEditingFinished: editingFinished(parseFloat(text));
// validator: ...
MouseArea{
anchors.fill: parent
propagateComposedEvents: true
onClicked: { parent.forceActiveFocus(); }
onWheel: {
parent.text=parseFloat(parent.text)-parent.parent.dWheel*wheel.angleDelta.y/120;
parent.editingFinished();
}
}
}
You can then reuse the Component in all known ways like in a Loader:
Loader {
sourceComponent: CustomTextField { // Property type is component, so it automatically creates a Component instead of the full-blown object, until it is loaded.
initValue: 12
dWheel: 42
}
...
}
or without Loader
CustomTextField {
...
}
Of course you can keep it inline, but even then, you have to add the properties to the root-element inside the Component.
Component {
id: componentId // only thing you can set besides one Object in a Component
TextField{
id: componentRoot // You can't reference this id from outside the Component!!!
property real initValue: 0.
property real dWheel: 0.5
signal editingFinished(real value);
text: initValue.toString();
// re-emit signal to the component
// so that user-defined slot can be defined when re-used
onEditingFinished: editingFinished(parseFloat(text));
// validator: ...
MouseArea{
anchors.fill: parent
propagateComposedEvents: true
onClicked: { parent.forceActiveFocus(); }
onWheel: {
parent.text=parseFloat(parent.text)-parent.parent.dWheel*wheel.angleDelta.y/120;
parent.editingFinished();
}
}
}
}
This has the down-side that you will always need a separate object to instantiate the Component, like a Loader which adds overhead and complicates communication in the file between the objects, since to address it, you will need to use: loaderId.item.property which might be expensive in lookup, you need to ensure that item is defined e.t.c.

how to access properties of item inside loader object

I am using a qml Loader component to push a page dynamically into view. Now the page I push has various properties and I would like to access them from the Loader component itself. However, I am unable to create aliases to these properties. So, I have something like:
Loader {
id: loginLoader
source: "qrc:/pages/IdPinLoginPage.qml"
property alias hasNavBar: loginLoader.item.hasNavBar
property alias hasStatusBar: loginLoader.item.hasStatusBar
}
This results in Invalid alias target location. How can I redirect these properties to the parent level in the Loader component?
I recommended you to use sourceComponent instead of source property of the Loader for assign/bind value to loaded item easily.
Also for access loaded item, you can use item property of the loader:
Loader {
id: loader
sourceComponent: Component {
SampleItem {
foo: 13 //assign values to loaded item properties
bar: "aleyk!"
}
}
}
Text {
text: loader.item.bar //Access to loaded item properties
}
SampleItem.qml
import QtQuick 2.6
Item {
property int foo: 0
property string bar: "salam";
}

List delegates driven live by JavaScript data model

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)
}
}

How to pass “this” from a QML element to a JS Function

Similar to the this keyword in C++, I’d like to either have a QML element to pass itself into a JS function, or have it set a property on another element to itself. Is this possible?
For example:
Rectangle{
id:theParent
property var theElement
SomeElement{
id:theChild
MouseArea {
anchors.fill:parent
onClicked: {
someJsFunction(*whatGoesHere*)
parent.theElement=*whatGoesHere*
}
}
Or, Consider this:
Rectangle{
id:theParent
property var theElement
SomeElement{
id:theChild
}
Then, in SomeElement.qml:
Rectangle{
MouseArea {
anchors.fill:parent
onClicked: {
someJsFunction(*whatGoesHere*)
parent.theElement=*whatGoesHere*
}
}
}
In this case, the *whatGoesHere* would be the instance of SomeElement where these are being called from.
Is this possible in QML? I would think the id property would make sense, but according to the docs, you cannot query the value of the id field, and anyway the id wouldn't be available if my SomeElement was described in a separate file, and the whatGoesHere handling above appeared in that separate file rather than in a particular instance.
I have two complementary proposals :
First, for a single usage, pass the ID as it's basically a pointer to the item :
MouseArea {
id: myClicker;
onClicked: { callFunc (myClicker); }
}
Then if you need multiple items to share this behavior, that means you're using MVC so the ID will work exactly the same :
Repeater {
model: 100;
delegate: MouseArea {
id: myClicker;
onClicked: { callFunc (myClicker); }
}
}
That is the classical part.
But to todo even better if you create your own components, keep in mind to create a 'self' helper property that does the 'this' job properly :
MouseArea { // component root definition
id: base;
property var self : base; // bind self on the I
}
Then use it like this :
Repeater {
model: 100;
delegate: MyComponent {
onClicked: { callFunc (self); }
}
}
Use this as often as you want !
Instance of your SomeElement is its id property value i.e. theChild. You can use it as this. No other built-in way exists as far as I can tell. But you can try to add your own QML item hierarchy with property which will return this.
Another way is to get children of some parent and use them. So you get children, locate the child you need and use that particular instance
If you define your element in a separate file, then you can simply assign an id and use it. It will be valid just within the context of that instance:
SomeElement.qml
Rectangle{
id: thisElement
MouseArea {
anchors.fill: parent
onClicked: {
someJsFunction(thisElement);
parent.theElement = thisElement;
}
}
}

Resources